Rule format
Rules decide what happens to a request that leaves the sandbox. You normally write them in the web app; a gate that is not logged in reads the same rules from a file (noset run --rules FILE). The gate and the web app’s test use one matcher, so a test shows what the gate does.
A rule
Section titled “A rule”version: 1rules: - id: finance-delete-customer # required, unique, 1 to 128 characters name: Delete a customer # optional: shown to people description: Deleting a customer needs approval decision: ask # allow | deny | ask approvers: {team: finance, count: 2} # ask only: who approves http: method: DELETE # a method or a list; empty: any method host: api.example.com # a host, "*.example.com" or a list; empty: any host path: '/users/[^/]+' # a regular expression for the whole path; empty: any pathUnknown fields are errors, so a typo cannot quietly change what a rule matches.
- method: an HTTP method, upper or lower case.
*,ANYor empty mean any method. - host: a host name or IP address, without port; a rule matches every port.
*.example.comcovers every subdomain ofexample.com, notexample.comitself. A pasted URL is reduced to its host. - path: an RE2 regular expression of at most 500 characters that must match the whole path, as if written
^(?:…)$./admin(/.*)?covers/adminand everything below it. Add(?i)for servers that ignore case. The query string is not part of the path.
Decisions
Section titled “Decisions”| Decision | The gate |
|---|---|
allow |
lets the request through and records it |
deny |
answers 403 and records it |
ask |
holds the request until people decide |
- The strictest decision wins, whatever the order. When several rules match,
denybeatsaskbeatsallow. Anallowrule can never switch off anaskordenyrule. - Several ask rules: when several ask rules match, each one’s approvals are needed, from its own team. One denial denies.
- No rule matches: the request is allowed and not recorded.
How a request is read
Section titled “How a request is read”The gate reads a request every way a server could, so a disguise does not get past a rule:
- Methods: the request method and every override: the
X-HTTP-Method-Override,X-HTTP-MethodandX-Method-Overrideheaders, and_methodin the query string or the body.POST /users/1withX-HTTP-Method-Override: DELETEmatches aDELETErule. - Paths: decoded once and until stable, with
%2Fdecoded and kept, with;paramsdropped and kept, cleaned of.,..and repeated slashes, with and without a trailing slash, and the paths inX-Original-URLand similar headers. - Hosts: a rule on a host name also matches a request sent straight to that name’s addresses.
When unsure, the gate reads more into a request, never less: it asks more often, never less.
What rules do not see: a different endpoint that does the same thing (POST /users/1/erase), and headers that select an account or a tenant. Cover them with their own rules.
Version 2
Section titled “Version 2”version: 2 adds what rules made from templates need. Each addition only narrows a match.
version: 2rules: - id: drive-delete decision: ask approvers: {team: data, count: 1} http: # a list: the rule matches when any of them does - method: DELETE host: www.googleapis.com path: '/drive/v[0-9]+/files/[^/]+' - method: PATCH host: www.googleapis.com path: '/drive/v[0-9]+/files/[^/]+' body: {json: {trashed: true}} # only when the JSON body sets trashed to true - id: s3-batch-delete decision: ask approvers: {team: data, count: 1} http: method: POST host: '*.amazonaws.com' query: {delete: '*'} # the parameter is there, with any valuequery: a parameter name and'*'(present) or a regular expression its whole value must match.body.json: a dotted path and a value the JSON body must have there.body.graphql: GraphQL mutation names, or['*']for any mutation.except: requests inside a match that the rule does not cover.
When the gate cannot read a body (too large, an encoding it cannot undo), every body condition counts as met: the request is asked about rather than let through.
When a request is held
Section titled “When a request is held”- The gate holds an ask request for 60 seconds. Without a decision, the agent gets
403andNoset: waiting for approval (id …), try again later. - Approved: a single-use approval for exactly that request, as it was sent, valid 15 minutes.
- Denied:
403with the approver and the reason. A deny rule answers403withNoset: blocked by rule "…".
Responses carry X-Noset-Decision (pending, denied, blocked) and X-Noset-Request headers, so tools can tell a held request from a real error.