Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 199 additions & 1 deletion doc/admin-guide/configuration/hrw4u.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -434,10 +434,58 @@ Groups
...
}

Control Flow
------------

HRW4U conditionals use ``if``, ``elif``, and ``else`` blocks. Each branch
takes a condition expression followed by a ``{ ... }`` body of statements:

.. code-block:: none

if condition {
statement;
} elif other-condition {
statement;
} else {
statement;
}

``elif`` and ``else`` are optional and can be chained. Branches can be nested
to arbitrary depth:

.. code-block:: none

REMAP {
if inbound.status > 399 {
if inbound.status < 500 {
if inbound.status == 404 {
inbound.resp.X-Error = "not-found";
} elif inbound.status == 403 {
inbound.resp.X-Error = "forbidden";
}
} else {
inbound.resp.X-Error = "server-error";
}
}
}

The ``break;`` statement exits the current section immediately, skipping any
remaining statements and branches:

.. code-block:: none

REMAP {
if inbound.req.X-Internal != "1" {
break;
}
# Only reached for internal requests
inbound.req.X-Debug = "on";
}

Condition operators
-------------------

HRW4U supports the following condition operators, which are used in `if (...)` expressions:
HRW4U supports the following condition operators, which are used in ``if`` expressions:

==================== ========================= ============================================
Operator HRW4U Syntax Description
Expand Down Expand Up @@ -494,6 +542,156 @@ Run with `--debug all` to trace:
- Condition evaluations
- State and output emission

Sandbox Policy Enforcement
==========================

Organizations deploying HRW4U across teams can restrict which language features
are permitted using a sandbox configuration file. Features can be **denied**
(compilation fails with an error) or **warned** (compilation succeeds but a
warning is emitted). Both modes support the same feature categories.

Pass the sandbox file with ``--sandbox``:

.. code-block:: none

hrw4u --sandbox /etc/trafficserver/hrw4u-sandbox.yaml rules.hrw4u

The sandbox file is YAML with a single top-level ``sandbox`` key. A JSON
Schema for editor validation and autocomplete is provided at
``tools/hrw4u/schema/sandbox.schema.json``.

.. code-block:: yaml

sandbox:
message: | # optional: shown once after all errors/warnings
...
deny:
sections: [ ... ] # section names, e.g. TXN_START
functions: [ ... ] # function names, e.g. run-plugin
conditions: [ ... ] # condition keys, e.g. geo.
operators: [ ... ] # operator keys, e.g. inbound.conn.dscp
language: [ ... ] # break, variables, in, else, elif
warn:
functions: [ ... ] # same categories as deny
conditions: [ ... ]

All lists are optional. An empty or missing sandbox file permits everything.
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs say “An empty or missing sandbox file permits everything,” but SandboxConfig.load() requires the YAML to contain a top-level sandbox mapping and will raise if the file is empty or lacks that key. Consider rewording to clarify that omitting --sandbox permits everything, and that an empty policy still must be expressed as sandbox: {} (or equivalent) when a file is provided.

Suggested change
All lists are optional. An empty or missing sandbox file permits everything.
All lists are optional. If ``--sandbox`` is omitted, all features are permitted. When a sandbox file
is provided but you want an empty policy, express it as an empty mapping (for example, ``sandbox: {}``).

Copilot uses AI. Check for mistakes.
A feature may not appear in both ``deny`` and ``warn``.

Denied Sections
---------------

The ``sections`` list accepts any of the HRW4U section names listed in the
`Sections`_ table, plus ``VARS`` to deny the variable declaration block.
A denied section causes the entire block to be rejected; the body is not
validated.

Functions
---------

The ``functions`` list accepts any of the statement-function names used in
HRW4U source. The complete set of deniable functions is:

====================== =============================================
Function Description
====================== =============================================
``add-header`` Add a header (``+=`` operator equivalent)
``counter`` Increment an ATS statistics counter
``keep_query`` Keep only specified query parameters
``no-op`` Explicit no-op statement
``remove_query`` Remove specified query parameters
``run-plugin`` Invoke an external remap plugin
``set-body-from`` Set response body from a URL
``set-config`` Override an ATS configuration variable
``set-debug`` Enable per-transaction ATS debug logging
``set-plugin-cntl`` Set a plugin control flag
``set-redirect`` Issue an HTTP redirect response
``skip-remap`` Skip remap processing (open proxy)
====================== =============================================

Conditions and Operators
------------------------

The ``conditions`` and ``operators`` lists use the same dot-notation keys shown
in the `Conditions`_ and `Operators`_ tables above (e.g. ``inbound.req.``,
``geo.``, ``outbound.conn.``).

Entries ending with ``.`` use **prefix matching** — ``geo.`` denies all
``geo.*`` lookups (``geo.city``, ``geo.ASN``, etc.). Entries without a trailing
``.`` are matched exactly.

Language Constructs
-------------------

The ``language`` list accepts a fixed set of constructs:

================ ===================================================
Construct What it controls
================ ===================================================
``break`` The ``break;`` statement (early section exit)
``variables`` The entire ``VARS`` section and all variable usage
``else`` The ``else { ... }`` branch of conditionals
``elif`` The ``elif ... { ... }`` branch of conditionals
``in`` The ``in [...]`` and ``!in [...]`` set membership operators
================ ===================================================

Output
------

When a denied feature is used the error output looks like:

.. code-block:: none

rules.hrw4u:3:4: error: 'set-debug' is denied by sandbox policy (function)

This feature is restricted by CDN-SRE policy.
Contact cdn-sre@example.com for exceptions.

When a warned feature is used the compiler emits a warning but succeeds:

.. code-block:: none

rules.hrw4u:5:4: warning: 'set-config' is warned by sandbox policy (function)

This feature is restricted by CDN-SRE policy.
Contact cdn-sre@example.com for exceptions.

The sandbox message is shown once at the end of the output, regardless of how
many denial errors or warnings were found. Warnings alone do not cause a
non-zero exit code.

Example Configuration
---------------------

A typical policy for a CDN team where remap plugin authors should not have
access to low-level or dangerous features, with transitional warnings for
features being phased out:

.. code-block:: yaml

sandbox:
message: |
This feature is not permitted by CDN-SRE policy.
To request an exception, file a ticket at https://help.example.com/cdn

deny:
# Disallow hooks that run outside the normal remap context
sections:
- TXN_START
- TXN_CLOSE
- PRE_REMAP

# Disallow functions that affect ATS internals or load arbitrary code
functions:
- run-plugin
- skip-remap

warn:
# These functions will be denied in a future release
functions:
- set-debug
- set-config

Examples
========

Expand Down
1 change: 1 addition & 0 deletions tools/hrw4u/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
build/
dist/
uv.lock
*.spec
9 changes: 5 additions & 4 deletions tools/hrw4u/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ UTILS_FILES=src/symbols_base.py \
SRC_FILES_HRW4U=src/visitor.py \
src/symbols.py \
src/suggestions.py \
src/sandbox.py \
src/kg_visitor.py

ALL_HRW4U_FILES=$(SHARED_FILES) $(UTILS_FILES) $(SRC_FILES_HRW4U)
Expand Down Expand Up @@ -169,10 +170,10 @@ test:

# Build standalone binaries (optional)
build: gen
uv run pyinstaller --onefile --name hrw4u --strip $(SCRIPT_HRW4U)
uv run pyinstaller --onefile --name u4wrh --strip $(SCRIPT_U4WRH)
uv run pyinstaller --onefile --name hrw4u-lsp --strip $(SCRIPT_LSP)
uv run pyinstaller --onefile --name hrw4u-kg --strip $(SCRIPT_KG)
uv run pyinstaller --onedir --name hrw4u --strip $(SCRIPT_HRW4U)
uv run pyinstaller --onedir --name u4wrh --strip $(SCRIPT_U4WRH)
uv run pyinstaller --onedir --name hrw4u-lsp --strip $(SCRIPT_LSP)
uv run pyinstaller --onedir --name hrw4u-kg --strip $(SCRIPT_KG)

# Wheel packaging (adjust pyproject to include both packages if desired)
package: gen
Expand Down
2 changes: 2 additions & 0 deletions tools/hrw4u/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ classifiers = [
]
dependencies = [
"antlr4-python3-runtime>=4.9,<5.0",
"pyyaml>=6.0,<7.0",
"rapidfuzz>=3.0,<4.0",
]

Expand Down Expand Up @@ -76,6 +77,7 @@ markers = [
"examples: marks tests for all header_rewrite docs examples",
"reverse: marks tests for reverse conversion (header_rewrite -> hrw4u)",
"ast: marks tests for AST validation",
"sandbox: marks tests for sandbox policy enforcement",
]

[dependency-groups]
Expand Down
136 changes: 136 additions & 0 deletions tools/hrw4u/schema/sandbox.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://trafficserver.apache.org/schemas/hrw4u-sandbox.schema.json",
"title": "HRW4U Sandbox Configuration",
"description": "Policy deny-list and warn-list for the hrw4u compiler (--sandbox FILE).",
"type": "object",
"additionalProperties": false,
"required": ["sandbox"],
"properties": {
"sandbox": {
"type": "object",
"additionalProperties": false,
"properties": {
"message": {
"type": "string",
"description": "Free-form text appended once after all denial errors and warnings. Use this to explain the policy and provide a contact or ticket link."
},
"deny": {
"$ref": "#/$defs/categoryBlock",
"description": "Features listed here are denied: compilation fails with an error."
},
"warn": {
"$ref": "#/$defs/categoryBlock",
"description": "Features listed here produce warnings but compilation succeeds."
}
}
}
},
"$defs": {
"categoryBlock": {
"type": "object",
"additionalProperties": false,
"properties": {
"sections": {
"type": "array",
"description": "HRW4U section names. A denied section rejects the entire block; a warned section emits a warning.",
"items": {
"type": "string",
"enum": [
"TXN_START",
"PRE_REMAP",
"REMAP",
"READ_REQUEST",
"SEND_REQUEST",
"READ_RESPONSE",
"SEND_RESPONSE",
"TXN_CLOSE",
"VARS"
]
},
"uniqueItems": true
},
"functions": {
"type": "array",
"description": "Statement function names.",
"items": {
"type": "string",
"enum": [
"add-header",
"counter",
"keep_query",
"no-op",
"remove_query",
"run-plugin",
"set-body-from",
"set-config",
"set-debug",
"set-plugin-cntl",
"set-redirect",
"skip-remap"
]
},
"uniqueItems": true
},
"conditions": {
"type": "array",
"description": "Condition keys. Entries ending with '.' use prefix matching (e.g. 'geo.' matches all geo.* lookups).",
"items": {
"type": "string"
},
"uniqueItems": true,
"examples": [
["geo.", "tcp.info", "inbound.conn.", "outbound.conn."]
]
},
"operators": {
"type": "array",
"description": "Operator (assignment target) keys. Entries ending with '.' use prefix matching.",
"items": {
"type": "string"
},
"uniqueItems": true,
"examples": [
["inbound.conn.dscp", "inbound.conn.mark", "outbound.conn.dscp", "outbound.conn.mark"]
]
},
"language": {
"type": "array",
"description": "Language constructs.",
"items": {
"type": "string",
"enum": [
"break",
"variables",
"in",
"else",
"elif"
]
},
"uniqueItems": true
},
"modifiers": {
"type": "array",
"description": "Condition and operator modifiers.",
"items": {
"type": "string",
"enum": [
"AND",
"OR",
"NOT",
"NOCASE",
"PRE",
"SUF",
"EXT",
"MID",
"I",
"L",
"QSA"
]
},
"uniqueItems": true
}
}
}
}
}
Loading
Loading