Traffic Policy Rules
Security Rules
Protect your endpoint with IP restrictions, rate limiting, authentication, and circuit breaking.
IP Restriction
Allow or deny requests from specific IP ranges (IPv4/IPv6 CIDR).
| Field | Description |
|---|---|
| allow | List of allowed CIDRs/IPs |
| deny | List of blocked CIDRs/IPs |
| message | Custom denial message |
Rate Limit
Limit requests per time window. Protect your backend from abuse.
| Field | Description |
|---|---|
| limit | Max requests in window (must be > 0) |
| window | Time window in seconds |
| scope | ip (per-client) or endpoint (global) |
| message | Custom rate limit message |
Verify Webhook
Validate incoming webhook signatures against a shared secret.
| Field | Description |
|---|---|
| secret | HMAC shared secret |
| signature_header | Header containing signature (default: X-Hub-Signature-256) |
| algorithm | sha256 or sha1 |
| signature_prefix | Prefix to strip (e.g., sha256=) |
| message | Custom error message |
JWT Validation
Validate JSON Web Tokens at the edge. Supports HMAC and RSA/ECDSA algorithms.
| Field | Description |
|---|---|
| algorithm | HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512 |
| secret | HMAC secret (for HS* algorithms) |
| jwks_url | JWKS endpoint URL (for RS*/ES* algorithms) |
| token_location | Where to find token: header, cookie, or query |
| token_name | Header/cookie/query param name |
| token_prefix | Prefix to strip (e.g., Bearer ) |
| issuer | Expected iss claim |
| audience | Expected aud claim |
| required_claims | List of claims that must be present |
API Key Validation
Validate API keys at the edge. Manage keys in the API Keys tab.
| Field | Description |
|---|---|
| key_location | header, query, or cookie |
| key_name | Header/param/cookie name (e.g., X-API-Key) |
| message | Custom 401 error message |
Body Validation
Validate JSON request bodies against a JSON Schema (draft 2020-12). Only validates application/json requests.
| Field | Description |
|---|---|
| schema | JSON Schema (max 64KB) |
| max_body_size | Max body bytes to read (0–5MB, default 1MB) |
| message | Custom error prefix |
Circuit Breaker
Stop sending traffic to failing backends. The circuit opens when error rates exceed a threshold, and automatically recovers after a cooldown period.
| Field | Description |
|---|---|
| error_threshold | Error percentage to trigger open (1–100, default 50) |
| window_size | Evaluation window in seconds (1–300, default 30) |
| min_requests | Minimum requests before evaluating (1–10000, default 10) |
| cooldown_time | Seconds to stay open (1–600, default 60) |
| half_open_max | Test requests allowed in half-open state (default 3) |
| error_codes | Status codes considered errors (default: 500, 502, 503, 504) |
| message | Custom 503 message |
Terminate TLS
Decrypt TLS traffic at the edge. Optionally enforce mutual TLS (mTLS) with client certificate verification.
| Field | Description |
|---|---|
| min_version | Minimum TLS version (1.0–1.3) |
| max_version | Maximum TLS version |
| server_certificate | PEM-encoded certificate |
| server_private_key | PEM-encoded private key |
| mutual_tls_certificate_authorities | Trusted CAs for mTLS client verification |
| mutual_tls_verification_strategy | require-and-verify, require-any, or request |
Traffic Rules
Control traffic flow with caching, redirects, custom responses, and webhooks.
Cache Response
Cache responses at the edge (Redis-backed). Serve cached content without hitting your backend.
| Field | Description |
|---|---|
| ttl | Cache TTL in seconds (1–86400) |
| path_pattern | Regex to match paths (empty = all) |
| methods | HTTP methods to cache (GET, HEAD) |
| status_codes | Cacheable statuses (200, 301, 302, 404) |
| max_body_size | Max response body to cache (100KB–10MB) |
| vary_headers | Headers to include in cache key (max 10) |
| ignore_query | Ignore query string in cache key |
| bypass_header | Header that bypasses cache (e.g., X-No-Cache) |
Force HTTPS
Redirect all HTTP requests to HTTPS with a 301 status code. No configuration needed. Also available as a toggle on the Endpoint Overview tab.
Redirect
Redirect requests to a different URL with configurable status code.
| Field | Description |
|---|---|
| url | Destination URL (supports $1, $2 capture groups) |
| pattern | Regex to match URL path (optional) |
| status | HTTP status: 301, 302, 307, or 308 |
Custom Response
Return a custom response without forwarding to your backend. Useful for maintenance pages, health checks, or static content. Subject to AI content safety validation.
| Field | Description |
|---|---|
| status | HTTP status code (100–999) |
| body | Response body (max 100KB) |
| content_type | text/html, application/json, text/plain, text/xml, text/css, or application/javascript |
| headers | Additional response headers |
Custom Error Page
Replace error responses from your backend with custom-branded error pages. Supports template tokens for dynamic content.
| Field | Description |
|---|---|
| status_codes | Specific codes to match (400–599) |
| status_range | Range: 4xx, 5xx, 4xx-5xx |
| content | Error page template (max 10KB) |
| content_type | Content type (default: text/html; charset=utf-8) |
| error_source | Filter: origin, edge, or both |
| response_status_code | Override status code (0 = keep original) |
{{.ClientIP}}, {{.StatusCode}}, {{.Reason}}, {{.Timestamp}}, {{.Hostname}}, {{.Method}}, {{.Path}}, {{.UserAgent}}, {{.RequestID}}
Webhook
Forward request or response data to an external HTTP endpoint. Useful for logging, notifications, or custom processing pipelines.
| Field | Description |
|---|---|
| url | Destination URL (must be HTTP/HTTPS) |
| method | HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD |
| headers | Headers to include |
| body | Body template (supports {{.method}}, {{.path}}, {{.host}}) |
| timeout | Timeout in seconds (0–30, default 5) |
Modification Rules
Modify request and response headers and rewrite URLs.
Add Headers
Inject custom headers into requests (before forwarding) or responses (before returning to client).
| Field | Description |
|---|---|
| headers | Key-value map of headers to inject |
Remove Headers
Strip specific headers from requests or responses.
| Field | Description |
|---|---|
| headers | List of header names to remove |
URL Rewrite
Rewrite request URLs before forwarding to your backend using regex patterns with capture groups.
| Field | Description |
|---|---|
| pattern | Regex to match the selected target |
| replacement | Replacement with $1, $2 capture groups |
| target | What to rewrite: path (default), query, host, or path_query |
# Example: Strip /api/v1 prefix
# Pattern: ^/api/v1/(.*)$
# Replacement: /$1
API Gateway Rules
Additional edge rule types for full API-gateway behavior. Add them from the same Traffic Policy editor.
CORS
Answer CORS preflight (OPTIONS) requests at the edge and add Access-Control-Allow-* headers to responses — no backend changes. Runs in the request phase.
| Field | Description |
|---|---|
| allow_origins | Exact origins (e.g. https://app.example.com) or a single *. * can't be combined with credentials. |
| allow_methods / allow_headers | Allowed methods / request headers (blank = echo what was requested). |
| expose_headers | Response headers exposed to JavaScript. |
| allow_credentials | Allow cookies / Authorization on cross-origin requests. |
| max_age | Seconds a browser may cache the preflight. |
Set Variables
Compute named variables from CEL expressions into a per-request bus. Later rules read them in conditions (vars.tier == "gold") and in header / redirect / custom-response values (${vars.tier}). Runs in the request phase. One name = expression per line.
tier = req.headers["x-tier"]
is_admin = req.path.startsWith("/admin")
# then, in an Add Headers rule: X-Plan: plan-${vars.tier}
Replace Body
Find/replace in the response body (literal or regex) — e.g. rewrite internal URLs or redact strings. Applies to complete, uncompressed responses up to a size cap; streamed and already-compressed responses pass through untouched.
| Field | Description |
|---|---|
| find / replace | Text to find and its replacement ($1.. supported when regex). |
| regex | Treat find as a regular expression. |
| max_body | Skip bodies larger than this (bytes). Default 1 MiB. |
Compress
Compress eligible response bodies at the edge, negotiating br (Brotli, preferred) or gzip from the client's Accept-Encoding. Compressible text/JSON types only; never inflates or double-compresses.
| Field | Description |
|---|---|
| min_size | Don't compress below this many bytes. Default 1024. |
| content_types | Compressible content-type prefixes. Blank = text/JSON/JS/XML/SVG defaults. |
WAF
A web application firewall that inspects each request (URI, headers, and optionally an inline body) against an OWASP-style ruleset via the Coraza engine and blocks or logs matches. Runs in the request phase. The built-in core ruleset covers SQL injection and XSS (libinjection), path traversal / sensitive-file access, OS-command and template injection, Log4Shell/JNDI, and known scanner user-agents — no external OWASP CRS to manage. Advanced users can supply their own Coraza SecLang.
Mode defaults to detect (log only) so turning the WAF on can never break a live endpoint on day one — watch the logs, then switch to block. The compiled engine is cached per ruleset; endpoints without a WAF rule pay nothing.
| Field | Description |
|---|---|
| mode | detect (default, log only) or block (deny matches with 403). |
| ruleset | core (built-in baseline, default), custom (your SecLang only), or core+custom. |
| custom_rules | Coraza SecLang directives (required when ruleset includes custom). Max 64 KB. |
| inspect_body | Also inspect the request body (bounded by max_body_kb, in-memory). Off = URI + headers only. |
| max_body_kb | KB of request body inspected. Default 128, max 1024. |
Bot Management
Match the client's TLS JA3 fingerprint — computed at the edge from the ClientHello — against allow/deny lists to block or log automated clients. Runs in the request phase. Mode defaults to detect (log only). Fingerprinting is only computed when a bot-management rule exists on some endpoint, so endpoints not using it pay nothing.
The JA3 is GREASE-normalized (RFC 8701 random values stripped) so it's stable per client. Run in detect mode first and copy the exact fingerprints the edge logs into your denylist — those match what the matcher computes. The fingerprint is also readable in any rule's CEL guard as conn.ja3 (e.g. conn.ja3 == "e7d705…" && req.path.startsWith("/api")).
| Field | Description |
|---|---|
| mode | detect (default, log only) or block (deny with 403). |
| ja3_denylist | JA3 hashes (32 hex chars) to block — known bad bots. |
| ja3_allowlist | When non-empty, blocks any client whose JA3 is NOT listed (allow-known-clients-only). |
| block_missing | Block when no JA3 could be computed (non-TLS / parse failure). Off by default. |
| message | Optional custom block message. |
Rate limit — flexible keying
The Rate Limit rule's Scope now supports per-IP, per-endpoint, and per header, cookie, or JWT claim value (with a key name). Counting is shared across edges. Header/cookie/JWT-claim scopes bucket by client-controlled values (the JWT claim is not signature-verified) — use them for fair sharing, and pair with a per-IP rule as the hard ceiling against abuse.
Impact preview (blast radius)
In the rule editor, replayable rule types show a Preview impact button. It replays your last ~2 hours of captured traffic through the unsaved rule and reports how many requests would be newly blocked / throttled / redirected, how many previously-successful requests would break, the client IPs affected, and a safe / caution / risky verdict — so you can check the blast radius before you save. Requires request capture enabled on the endpoint.
Configure from the agent CLI
You don't have to build a policy in the dashboard — the agent can attach one to its endpoint at connect time, from shorthand flags and/or a policy file. The rules are exactly the ones above; they're enforced at the edge, so nothing runs on your machine. Attaching a policy requires the traffic-policy entitlement on your plan; without it the agent logs a clear error and keeps serving traffic (the tunnel is never blocked).
Shorthand flags
The most common rules have a dedicated flag (examples on the right). Each compiles to one rule in the phase shown. The --cidr-* flags are comma-separated; the header flags are repeatable.
| Flag | Rule | Phase |
|---|---|---|
| --cidr-allow <cidr,…> | restrict-ips (allow-list) | connect |
| --cidr-deny <cidr,…> | restrict-ips (deny-list) | connect |
| --rate-limit <N/window> | rate-limit, per IP (e.g. 100/1m) | connect |
| --request-header-add <k:v> | add-headers | request |
| --request-header-remove <k> | remove-headers | request |
| --response-header-add <k:v> | add-headers | response |
| --response-header-remove <k> | remove-headers | response |
| --compression | compress-response (gzip/br) | response |
| --force-https | force-https (redirect HTTP→HTTPS) | request |
Policy file
For anything beyond the shorthands, pass a YAML or JSON file with --traffic-policy <file> (see policy.yaml on the right). It can list rules two ways, which may be combined: a flat rules: list, or phase-keyed blocks (on_tcp_connect, on_udp_connect, on_http_request, on_http_response).
| Key | Description |
|---|---|
| type | Required. Any rule type from the catalog above — restrict-ips, rate-limit, add-headers, remove-headers, force-https, compress-response, and the rest. |
| config | Rule-specific settings — the exact fields documented for that rule above (e.g. allow/deny for restrict-ips, limit/window/scope for rate-limit, headers for add-/remove-headers). |
| phase | Optional. on_tcp_connect, on_udp_connect, on_http_request, or on_http_response. See phase resolution below. |
| expression | Optional CEL condition — the rule only runs when it's true, e.g. req.path.startsWith("/api"). Blank = always. |
| name | Optional label shown in the dashboard and logs. Defaults to the rule type. |
Phase resolution. An explicit phase: on the rule wins; otherwise the phase-key block it's nested under; otherwise a default by type — restrict-ips and rate-limit attach at connect (on_udp_connect for UDP tunnels), compress-response at the response, everything else at the request.
The phase-keyed form is equivalent — nest rules under the phase block instead of writing phase: on each rule.
--traffic-policy file and shorthand flags, the file's rules come first and the shorthands are appended — all into one managed policy named ngris-agent.ngris start — a running agent does not re-read it. Re-running replaces the ngris-agent policy; any rules you build in the dashboard (under a different name) are left untouched.# Lock an endpoint to an office network, cap it, and force HTTPS
ngris http 3000 --cidr-allow 203.0.113.0/24 --rate-limit 100/1m --force-https
# Strip a leaky request header; add a security header + compress on the way out
ngris http 3000 --request-header-remove X-Powered-By \
--response-header-add X-Frame-Options:DENY --compression
# Attach with: ngris http 3000 --traffic-policy policy.yaml
rules:
# Allow only two networks (restrict-ips defaults to the connect phase)
- type: restrict-ips
name: office-only
config:
allow: [203.0.113.0/24, 198.51.100.10/32]
message: "Not on the allow-list"
# 100 requests / minute, per client IP
- type: rate-limit
config:
limit: 100
window: 60 # seconds
scope: ip
# Add security headers to every response
- type: add-headers
phase: on_http_response
config:
headers:
X-Frame-Options: DENY
Strict-Transport-Security: "max-age=31536000"
# Force HTTPS, but only on the API path (optional CEL condition)
- type: force-https
expression: req.path.startsWith("/api")
config: {}
on_tcp_connect:
- type: rate-limit
config: { limit: 100, window: 60, scope: ip }
on_http_response:
- type: compress-response
config: {}