Skip to content

Traffic policies

Attach a policy file to any share and the relay enforces it on every public request — before traffic reaches your machine. Works for tunnels, file shares and upload pages alike, anonymously and self-hosted.

Terminal window
xport 3000 --policy-file xport.yml
xport.yml
on_http_request:
- name: admin-only
expressions:
- req.URL.Path.startsWith('/admin')
actions:
- type: ip_restrictions
config:
allow: ["203.0.113.0/24", "10.8.0.7"]
- type: basic_auth
config:
username: root
password: s3cret
- name: brake-the-scrapers
expressions:
- req.Method == 'GET' && req.URL.Path == '/api/search'
actions:
- type: rate_limit
config:
capacity: 30
rate: 1m
- type: deny
config:
status_code: 429
body: slow down
on_http_response:
- expressions:
- res.StatusCode >= 200 && res.StatusCode < 300
actions:
- type: add_headers
config:
headers:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
- type: remove_headers
config:
headers: [Server]

The CLI validates the file locally (every CEL expression compiles before the share starts); the relay re-validates and compiles it at register. Invalid policies fail the share with a clear error — never silently.

Rules carry CEL expressions, ANDed together; all must hold for the actions to run. Variables:

Variable Fields
req Method, URL.Path, URL.RawPath, URL.RawQuery, Host, Proto, Headers (canonical map of string → list)
conn ClientIP (resolved through the trusted edge)
res (response phase) StatusCode, Headers

Examples:

req.Method == 'GET' && req.URL.Path.startsWith('/admin')
'x-webhook-token' in req.Headers && req.Headers['X-Webhook-Token'][0] == 'abc'
req.Headers['User-Agent'][0].matches('(?i)curl.*')
res.StatusCode >= 500

Request bodies are never exposed to expressions.

Request phase: deny (status_code, body), ip_restrictions (allow/deny CIDR lists — deny wins), rate_limit (capacity, rate like 30s/1m, optional bucket_key CEL list; excess → 429), basic_auth (username, password, realm), redirect (url, status_code 301/302/303/307/308), custom_response (status_code 100–599, content_type, body, optional headers — the policy answers the request itself: mock responses, maintenance pages), url_rewrite (from/to — replace a path prefix before tunneling, e.g. from: /api, to: "" strips it), add_headers, remove_headers, log (message — logs the match, never the content).

Response phase: deny (replaces status/body), add_headers, remove_headers, log.

Actions run in order; the first one that produces a final response (deny, redirect, rate limit, auth challenge) ends the request — it never reaches your service.

24 KiB on the wire, 16 rules per phase, 8 expressions and 8 actions per rule, CEL evaluation cost-capped. Policies beyond that are rejected at register.

Set tunnel.policy_file in config.yaml to attach the same policy to every share on that machine; --policy-file wins per invocation.