Traffic Policy

A Traffic Policy decides what happens to a request at the devgate edge, before it reaches your service: ask for a password, cap a noisy client, redirect, rewrite a path, add security headers — or answer the request outright. It is a YAML (or JSON) document, written the way ngrok writes theirs, so an existing ngrok policy usually runs as it is.

policy.yaml
on_http_request:
  - name: Staging password
    expressions:
      - "!req.url.path.startsWith('/webhooks')"
    actions:
      - type: basic-auth
        config:
          credentials:
            - "team:correct-horse-battery"
on_http_response:
  - actions:
      - type: add-headers
        config:
          headers:
            x-frame-options: DENY

Attaching a policy

An agent endpoint takes its policy from the command that starts it:

$ devgate http 8080 --url https://<your dev domain> --traffic-policy-file policy.yaml

The agent sends the file when the session starts; the edge validates it and the endpoint runs it from the first request. Change the file and restart the agent to change the policy. On a reserved endpoint the policy is stored with the endpoint and can also be edited in the dashboard; a cloud endpoint is nothing but its policy, edited in the dashboard with no downtime.

Phases, rules and actions

A policy has up to three phases, each a list of rules run in order:

  • on_tcp_connect — when the client connects, before any HTTP is read.
  • on_http_request — for each request, before it is forwarded.
  • on_http_response — for each response, before it goes back to the client.

A rule has an optional name, optional expressions and a list of actions. When every expression is true (or there are none) the actions run in order. A terminating action — custom-response, deny, redirect, close-connection, forward-internal — answers the request, and nothing after it runs. An expression that fails to evaluate counts as false.

Expressions

Expressions are CEL, the Common Expression Language: comparisons, &&, ||, in, string methods (startsWith, endsWith, contains, matches, lower, upper), plus these helpers:

  • getReqHeader('name') / hasReqHeader('name') — request header values (a list).
  • getResHeader('name') / hasResHeader('name') — response header values.
  • getQueryParam('name') / hasQueryParam('name') — query parameter values.
  • inCidrRange(ip, '10.0.0.0/8') / inCidrRanges(ip, [...]) — IP range checks.
  • conn.client_ip.isPrivateIp() — loopback, private and link-local addresses.

Variables

VariableValue
req.methodGET, POST…
req.urlThe full URL: https://host/path?query
req.url.path, req.url.query, req.url.query_paramsPath, raw query, and query parameters as a map of lists.
req.url.host, req.hostThe endpoint hostname; the Host header as sent.
req.headersRequest headers, lowercase names, values as lists.
req.user_agent, req.content_type, req.content_length, req.versionThe usual request facts.
conn.client_ip, conn.client_portWhere the visitor connects from.
conn.ts.start, time.nowTimestamps.
endpoint.urlThis endpoint’s URL.
res.status_code, res.headers, res.content_typeThe upstream answer, in on_http_response.
vars.*Whatever set-vars stored.
actions.ngrok.*Results of earlier actions (basic-auth, rate-limit, jwt-validation, http-request, log…), under ngrok’s names; each action’s page lists its own.

Any string in an action’s config may interpolate an expression with ${…}, e.g. x-client: ${conn.client_ip}.

Actions

ActionWhat it doesUnits
add-headers Adds headers to the request on its way to your service, or to the response on its way back. free
remove-headers Removes headers from the request before your service sees it, or from the response before the visitor does. 1
compress-response Compresses your service’s answer with gzip, Brotli or deflate — whichever the visitor’s browser prefers — as it streams. 10
basic-auth Asks visitors for a username and password before the request reaches your service. free
oauth Makes visitors sign in with Google or GitHub before they reach your service, and tells later rules who they are. free
openid-connect Makes visitors sign in with any OpenID Connect provider — Okta, Auth0, Microsoft Entra ID, Keycloak and others — before they reach your service. free
jwt-validation Lets a request through only with a valid JWT — signed by your issuer, for your audience, not expired. Everything else gets 401. 10
custom-response Answers the request itself with a fixed status, headers and body — no upstream needed. free
deny Rejects the request with a status code, or drops the connection when it runs in on_tcp_connect. free
redirect Sends the visitor to another URL with an HTTP redirect. 10
url-rewrite Changes the path and query your service receives, without the visitor seeing a redirect. 10
restrict-ips Allows or denies clients by IP address or CIDR range. 10
rate-limit Caps how many requests a client — or any bucket you define — can make in a time window. 1
circuit-breaker Stops sending requests to a failing upstream for a while, answering 503 instead, then lets traffic back once it recovers. 10
verify-webhook Checks a webhook’s signature at the edge, so forged events never reach your service. 1
http-request Calls another URL from the policy — an auth service, a feature flag, an internal endpoint — so later rules can act on the answer. 1
set-vars Stores values under vars.* for later rules’ expressions and interpolations. free
log Records metadata about a request or connection, shown with that request in the Traffic Inspector. free
close-connection Drops the client connection without sending any response. free
forward-internal Hands the request to one of your internal endpoints (*.internal), which runs its own policy and forwards to its agent. free

Traffic Policy units

As on ngrok, actions are priced by the work they do: most are free, and the ones that cost something are counted per request only when they actually run. remove-headers, rate-limit, verify-webhook and http-request cost 1 unit, compress-response, jwt-validation, redirect, url-rewrite, restrict-ips and circuit-breaker 10. Units are billed at $0.10 per 100,000 against your plan’s usage credit, and need a paid plan — on Free, a policy with them is refused with “Upgrade required”.

Questions people actually ask

Is devgate’s Traffic Policy compatible with ngrok’s?

Yes for the parts devgate runs: the same YAML or JSON shape, the same phases, CEL expressions, variable names (req.*, conn.*, res.*, vars, actions.ngrok.*), ${…} interpolation and action names. A policy that only uses the actions listed here runs unchanged.

What happens if my policy has an error?

It is checked before anything starts: the agent refuses to start with a list of errors and their paths (on_http_request[0].actions[1].config…), and the dashboard shows them as you type.

Does a policy slow requests down?

Expressions and interpolations are compiled once, when the endpoint’s policy is loaded; per request the edge only evaluates them, which takes microseconds.

Which actions are free?

add-headers, basic-auth, oauth, openid-connect, custom-response, deny, set-vars, log, close-connection and forward-internal cost nothing. remove-headers, rate-limit, verify-webhook and http-request cost 1 Traffic Policy unit per run; compress-response, jwt-validation, redirect, url-rewrite, restrict-ips and circuit-breaker cost 10. Units are $0.10 per 100,000 and need a paid plan.