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).

FieldDescription
allowList of allowed CIDRs/IPs
denyList of blocked CIDRs/IPs
messageCustom denial message

Rate Limit

Limit requests per time window. Protect your backend from abuse.

FieldDescription
limitMax requests in window (must be > 0)
windowTime window in seconds
scopeip (per-client) or endpoint (global)
messageCustom rate limit message

Verify Webhook

Validate incoming webhook signatures against a shared secret.

FieldDescription
secretHMAC shared secret
signature_headerHeader containing signature (default: X-Hub-Signature-256)
algorithmsha256 or sha1
signature_prefixPrefix to strip (e.g., sha256=)
messageCustom error message

JWT Validation

Validate JSON Web Tokens at the edge. Supports HMAC and RSA/ECDSA algorithms.

FieldDescription
algorithmHS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512
secretHMAC secret (for HS* algorithms)
jwks_urlJWKS endpoint URL (for RS*/ES* algorithms)
token_locationWhere to find token: header, cookie, or query
token_nameHeader/cookie/query param name
token_prefixPrefix to strip (e.g., Bearer )
issuerExpected iss claim
audienceExpected aud claim
required_claimsList of claims that must be present

API Key Validation

Validate API keys at the edge. Manage keys in the API Keys tab.

FieldDescription
key_locationheader, query, or cookie
key_nameHeader/param/cookie name (e.g., X-API-Key)
messageCustom 401 error message

Body Validation

Validate JSON request bodies against a JSON Schema (draft 2020-12). Only validates application/json requests.

FieldDescription
schemaJSON Schema (max 64KB)
max_body_sizeMax body bytes to read (0–5MB, default 1MB)
messageCustom 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.

FieldDescription
error_thresholdError percentage to trigger open (1–100, default 50)
window_sizeEvaluation window in seconds (1–300, default 30)
min_requestsMinimum requests before evaluating (1–10000, default 10)
cooldown_timeSeconds to stay open (1–600, default 60)
half_open_maxTest requests allowed in half-open state (default 3)
error_codesStatus codes considered errors (default: 500, 502, 503, 504)
messageCustom 503 message

Terminate TLS

Decrypt TLS traffic at the edge. Optionally enforce mutual TLS (mTLS) with client certificate verification.

FieldDescription
min_versionMinimum TLS version (1.0–1.3)
max_versionMaximum TLS version
server_certificatePEM-encoded certificate
server_private_keyPEM-encoded private key
mutual_tls_certificate_authoritiesTrusted CAs for mTLS client verification
mutual_tls_verification_strategyrequire-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.

FieldDescription
ttlCache TTL in seconds (1–86400)
path_patternRegex to match paths (empty = all)
methodsHTTP methods to cache (GET, HEAD)
status_codesCacheable statuses (200, 301, 302, 404)
max_body_sizeMax response body to cache (100KB–10MB)
vary_headersHeaders to include in cache key (max 10)
ignore_queryIgnore query string in cache key
bypass_headerHeader 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.

FieldDescription
urlDestination URL (supports $1, $2 capture groups)
patternRegex to match URL path (optional)
statusHTTP 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.

FieldDescription
statusHTTP status code (100–999)
bodyResponse body (max 100KB)
content_typetext/html, application/json, text/plain, text/xml, text/css, or application/javascript
headersAdditional response headers

Custom Error Page

Replace error responses from your backend with custom-branded error pages. Supports template tokens for dynamic content.

FieldDescription
status_codesSpecific codes to match (400–599)
status_rangeRange: 4xx, 5xx, 4xx-5xx
contentError page template (max 10KB)
content_typeContent type (default: text/html; charset=utf-8)
error_sourceFilter: origin, edge, or both
response_status_codeOverride status code (0 = keep original)
Template Tokens: {{.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.

FieldDescription
urlDestination URL (must be HTTP/HTTPS)
methodHTTP method: GET, POST, PUT, PATCH, DELETE, HEAD
headersHeaders to include
bodyBody template (supports {{.method}}, {{.path}}, {{.host}})
timeoutTimeout 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).

FieldDescription
headersKey-value map of headers to inject

Remove Headers

Strip specific headers from requests or responses.

FieldDescription
headersList of header names to remove

URL Rewrite

Rewrite request URLs before forwarding to your backend using regex patterns with capture groups.

FieldDescription
patternRegex to match the selected target
replacementReplacement with $1, $2 capture groups
targetWhat to rewrite: path (default), query, host, or path_query
Terminal
# 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.

FieldDescription
allow_originsExact origins (e.g. https://app.example.com) or a single *. * can't be combined with credentials.
allow_methods / allow_headersAllowed methods / request headers (blank = echo what was requested).
expose_headersResponse headers exposed to JavaScript.
allow_credentialsAllow cookies / Authorization on cross-origin requests.
max_ageSeconds 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.

Example
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.

FieldDescription
find / replaceText to find and its replacement ($1.. supported when regex).
regexTreat find as a regular expression.
max_bodySkip 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.

FieldDescription
min_sizeDon't compress below this many bytes. Default 1024.
content_typesCompressible 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.

FieldDescription
modedetect (default, log only) or block (deny matches with 403).
rulesetcore (built-in baseline, default), custom (your SecLang only), or core+custom.
custom_rulesCoraza SecLang directives (required when ruleset includes custom). Max 64 KB.
inspect_bodyAlso inspect the request body (bounded by max_body_kb, in-memory). Off = URI + headers only.
max_body_kbKB 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")).

FieldDescription
modedetect (default, log only) or block (deny with 403).
ja3_denylistJA3 hashes (32 hex chars) to block — known bad bots.
ja3_allowlistWhen non-empty, blocks any client whose JA3 is NOT listed (allow-known-clients-only).
block_missingBlock when no JA3 could be computed (non-TLS / parse failure). Off by default.
messageOptional 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.

FlagRulePhase
--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-headersrequest
--request-header-remove <k>remove-headersrequest
--response-header-add <k:v>add-headersresponse
--response-header-remove <k>remove-headersresponse
--compressioncompress-response (gzip/br)response
--force-httpsforce-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).

KeyDescription
typeRequired. Any rule type from the catalog above — restrict-ips, rate-limit, add-headers, remove-headers, force-https, compress-response, and the rest.
configRule-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).
phaseOptional. on_tcp_connect, on_udp_connect, on_http_request, or on_http_response. See phase resolution below.
expressionOptional CEL condition — the rule only runs when it's true, e.g. req.path.startsWith("/api"). Blank = always.
nameOptional 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.

File + flags combine. If you pass both a --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.
Applied once, at connect. The policy is attached right after the agent authenticates. Editing the file or flags takes effect on the next 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.
Plan-gated. Attaching a policy requires the traffic-policy entitlement. If your plan lacks it, the agent logs the rejection and keeps forwarding traffic unfiltered rather than failing the tunnel.
Shorthand flags
# 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
policy.yaml
# 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: {}
policy.yaml (phase-keyed)
on_tcp_connect:
  - type: rate-limit
    config: { limit: 100, window: 60, scope: ip }
on_http_response:
  - type: compress-response
    config: {}
Iris