Skip to content

Shield360 Edge configuration reference

This page is the full reference for Shield360 Edge. Start with the quick start if you have not run Edge yet.

The configuration is a YAML file, passed with --config or SHIELD360_EDGE_CONFIG. The container image has a sample at /etc/shield360-edge/edge.yaml; mount your own over it. SHIELD360_* environment variables override the file. Unknown keys are refused, so a typo fails at start instead of being ignored.

Check a file without starting the proxy:

Terminal window
shield360-edge --config edge.yaml --check

Secrets never go in the file: the API key comes from SHIELD360_API_KEY (or shield360.api_key_file), and the enrollment token from SHIELD360_EDGE_ENROLLMENT_TOKEN (or shield360.enrollment_token_file).

Sample edge.yaml
listen: ":8080" # SHIELD360_EDGE_LISTEN
admin_listen: ":9901" # /healthz and /readyz; SHIELD360_EDGE_ADMIN_LISTEN
shield360:
url: https://app.shield360.ai # SHIELD360_URL; events and signed configs
# config_public_key: '{"kty":"OKP","crv":"Ed25519","x":"...","kid":"..."}'
# # SHIELD360_CONFIG_PUBLIC_KEY; required to
# # use the signed config (block mode)
environment: production # SHIELD360_ENVIRONMENT
cache_dir: /var/lib/shield360-edge # last verified signed configs; once
# # enrolled, the credential and AI policy
# enrollment_token_file: /run/secrets/shield360-edge-enrollment-token
# instance_name: edge-prod-1 # SHIELD360_EDGE_INSTANCE_NAME; default host name
# policy_refresh: 5m
# heartbeat_interval: 1m
telemetry:
# otlp_endpoint: http://otel-collector:4318 # SHIELD360_OTLP_ENDPOINT
service_name: shield360-edge
server:
read_header_timeout: 10s
idle_timeout: 120s
shutdown_timeout: 25s
max_body: 10MiB
trusted_proxies: [] # e.g. ["10.0.0.0/8"] behind a load balancer
forwarded_for: append # or preserve: forward a trusted peer's X-Forwarded-For unchanged
rate_limit: # default per client IP and route; per instance
requests_per_second: 50
burst: 100
deception:
paths: ["/.env", "/wp-admin", "/.git/config", "/phpmyadmin"]
action: monitor # or block: 403 for the client until ttl passes
ttl: 15m
routes:
- name: web
hosts: [] # any host
path_prefix: /
upstream: http://app:3000
app: my-web-app # Shield360 application name (signed config, events)
# api_key_env: MY_APP_SHIELD360_API_KEY # this app's own key; default the shield360 key
rasp:
mode: monitor # block needs a pinned config key
# attacks: { virtual_patch: block, file_write: block }
# runtime: node # the app's runtime, for runtime-scoped virtual patches
# runtime_version: 22.12.0
fail_closed: false # true: 503 when inspection fails
waf: # OWASP Coraza + the OWASP Core Rule Set (embedded)
enabled: true
mode: monitor # block: answer 403 to attacks
# paranoia_level: 1
# inbound_threshold: 5
# exclude_rules: [942430]
- name: openai
path_prefix: /ai/openai
strip_prefix: true
upstream: https://api.openai.com
ai: true
vendor: openai
# Enrolled: the environment's AI policy applies. A file overrides it
# (or, with ai_policy_mode: fallback, applies until it is fetched).
# ai_policy_file: /etc/shield360-edge/ai-policy.json
# ai_policy_mode: override
budget: # overrides the platform's per-key budget here
limit_usd: 25
period: day
key: header:authorization # hashed in memory, never stored
KeyEnvDefaultMeaning
listenSHIELD360_EDGE_LISTEN:8080Proxy address.
admin_listenSHIELD360_EDGE_ADMIN_LISTEN:9901/healthz (process up) and /readyz (listening, not draining).
shield360.urlSHIELD360_URL—Shield360 address, for events and signed configs.
shield360.api_key_fileSHIELD360_API_KEY—API key. It is never read from the YAML text itself.
shield360.config_public_keySHIELD360_CONFIG_PUBLIC_KEY—Pinned key for the signed config: a JWK, a JWKS or the base64url x. Without it, routes run in monitor mode (see Modes).
shield360.environmentSHIELD360_ENVIRONMENT—deployment.environment. When it is production, trust on first use is refused.
shield360.cache_dirSHIELD360_EDGE_CACHE_DIROS tempWhere the last verified signed configs are kept. Once enrolled, it also keeps the instance credential, the cached AI policy and undelivered AI events, all owner-only (0600). Required to enroll.
shield360.enrollment_token_fileSHIELD360_EDGE_ENROLLMENT_TOKEN—Edge enrollment token (see Enrollment). It is never read from the YAML text itself.
shield360.instance_nameSHIELD360_EDGE_INSTANCE_NAMEhost nameHow this instance is listed in Shield360.
shield360.policy_refresh5mHow often an enrolled instance refreshes the AI policy (at least 10s).
shield360.heartbeat_interval1mHow often an enrolled instance reports its state (at least 10s).
telemetry.otlp_endpointSHIELD360_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_ENDPOINT—OTLP/HTTP base URL. When unset, no spans are exported.
telemetry.service_nameshield360-edgeSpan service.name.
telemetry.client_ip—networkWhat Edge reports of a client’s address with its events and spans: network (the /24 for IPv4, /48 for IPv6), full (the address; turn it on only when your organization has decided to keep client addresses) or off.
telemetry.export_logs—trueSend Edge’s own log lines (start-up, policy, upstream errors, findings) to the OTLP endpoint as logs, with the shield360 key, or the first route’s key when there is none. They never hold request values. Spans and logs carry service.instance.id (the instance name), host.name and, with POD_NAME/POD_NAMESPACE set from the downward API, k8s.pod.name and k8s.namespace.name.
server.read_header_timeout10s
server.read_timeout, server.write_timeout0Off by default, so uploads and streams are not cut off.
server.idle_timeout120s
server.shutdown_timeout25sDrain time on SIGTERM.
server.dial_timeout10sConnecting to an upstream.
server.max_body10MiBDefault body limit.
server.trusted_proxiesSHIELD360_EDGE_TRUSTED_PROXIES (comma-separated)[]Peers, as IPs or CIDRs, whose X-Forwarded-* and X-Request-Id are believed.
server.forwarded_forSHIELD360_EDGE_FORWARDED_FORappendWhat a trusted peer’s X-Forwarded-For becomes upstream. append adds the peer’s address. preserve forwards the header as the peer sent it, so an app that already trusts one hop (your load balancer) still finds the client right-most, with Edge in the path or not. A trusted peer that sends no header is named as the client.
rate_limit.requests_per_second, rate_limit.burstoff; burst 2 × rateDefault token bucket per client IP and route.
deception.paths[]Decoy paths. A request matches when its path equals one or sits under it.
deception.actionmonitormonitor or block for a suspicious client’s later requests.
deception.ttl15mHow long a client stays suspicious.
deception.status404The status a decoy answers with.
access.allow, access.deny[]Client IPs or CIDRs. deny answers 403 and wins over allow. An allow-listed client is never blocked by the WAF, OpenAPI validation, feeds, reputation or rate limits; its findings are still reported.
access.feeds[][]Threat lists fetched over HTTPS: name, url, action (monitor or block), refresh (default 6h, at least 5m). One IP or CIDR per line; text after it is ignored, so Spamhaus DROP and the Tor exit list work as published. A list that cannot be fetched keeps its last copy.
reputation.threshold0 (off)Findings (WAF matches, decoy hits, OpenAPI violations, runtime protection blocks) from one client within reputation.window (default 10m) that flag it for reputation.ttl (default 1h).
reputation.actionmonitormonitor or block for a flagged client’s requests. Counted per instance.
block_page.file—An HTML template ({{.Message}}, {{.RequestID}}, {{.EventID}}) sent to blocked browsers that accept text/html. Others get the text page.

Each entry in routes[]:

KeyDefaultMeaning
nameroute-NLower-case letters, digits, - and _.
hostsanyExact hosts, or *.example.com.
path_prefix/Matched on segment boundaries.
strip_prefixfalseRemove the prefix before forwarding.
upstreamrequiredAn http(s) base URL.
preserve_hosttrue (false for AI routes)Forward the client’s Host header.
appthe route nameShield360 application name, used for the signed config (GET /api/rasp/config?app=) and for events.
rasp.disabledfalseSkip inspection on this route.
rasp.modemonitorDefault mode: monitor or block.
rasp.attacks{}Mode per attack type: off, monitor or block. At the edge, virtual_patch, file_write and xss apply.
rasp.runtime, rasp.runtime_versionunknownThe app’s runtime (node, python, go, dotnet) and version, for virtual patches limited to one runtime. When unset, every rule applies, and Edge logs a warning at start for a route that blocks.
api_key_env—The name of an environment variable holding this route’s own API key, for an app that already has one. The route’s runtime protection events, signed config and spans then go to that key’s project. Unset at start is an error. Empty: the shield360 key.
fail_closedfalseAnswer 503 when inspection (or the WAF) fails.
waf.enabledfalseRun OWASP Coraza with the embedded OWASP Core Rule Set on this app route (not on AI routes).
waf.modemonitormonitor reports attacks and forwards; block answers 403.
waf.paranoia_level1The rule set’s paranoia level, 1 to 4.
waf.inbound_threshold5The anomaly score that counts as an attack.
waf.exclude_rules[]Rule set rule ids to remove (false positives).
waf.rules[]Extra SecLang directives after the rule set.
waf.exclusions[][]Turn rules off for part of the route only: path_prefix (exact or below it), methods, and targets such as ARGS:comment, REQUEST_COOKIES:session or REQUEST_HEADERS:x-signature. Without targets the rules are off for those requests; without a path, for the whole route. Prefer this to exclude_rules.
waf.response.enabledfalseRun the rule set’s response rules (SQL errors, stack traces, directory listings). Responses stream to the client, so matches are reported (waf.response_matched), never blocked.
waf.response.max_body, waf.response.content_types64KiB; HTML, text, JSON, XMLHow much of a response is checked, and which types. Compressed responses are not checked.
access.allow, access.deny[]Added to the global lists for this route.
rate_limits[][]Limits on part of the route, on top of its own: name, path_prefix, methods, requests_per_second, burst, and key (ip, or header:<name> to count per API key or tenant; the value is hashed in memory).
openapi.spec—The app’s OpenAPI 3 document (YAML or JSON). Requests are checked for unknown paths and methods, parameters and JSON bodies. Events name the reason and the document’s parameter or operation, never a request value.
openapi.mode, openapi.allow_unknownmonitor, falseblock answers 400. allow_unknown lets paths and methods the document does not describe through.
security_headers.enabledfalseAdd X-Content-Type-Options, Referrer-Policy, X-Frame-Options and, over HTTPS, Strict-Transport-Security, where the app sets none. security_headers.hsts overrides the HSTS value (off leaves it out); security_headers.set adds or replaces any header.
max_bodyserver.max_body
timeout60s (10m for AI routes)How long the upstream may take to start answering.
rate_limitthe global defaultOverride the rate limit for this route.
aifalseMake this an AI provider route.
vendorfrom the hostAI catalog id: openai, anthropic, google, and so on.
ai_policy_file—The platform’s device policy JSON (proxyGuard, proxyGuardKinds, aiTools, approvedModels, injectionMode, …). On an enrolled instance it overrides the platform policy, or backs it up (ai_policy_mode). With no policy at all, AI requests are forwarded unchecked (fail open).
ai_policy_modeoverrideoverride: the file wins over the platform policy. fallback: the file applies only until a platform policy has been fetched or cached.
budget.limit_usd, budget.period, budget.key—, day, header:authorizationSpend limit per key per hour, day or month. The key is header:<name> (hashed in memory and never stored), user (X-Shield360-User) or ip. Overrides the platform’s per-key budget on this route.

Each request goes through these steps, in this order:

  1. Client address. The peer address is used, unless the peer is in server.trusted_proxies. In that case the right-most X-Forwarded-For entry that is not a trusted proxy is used.

  2. Route. A route is chosen by host first (an exact host beats *.suffix, and either beats a route with no hosts). Then the longest path_prefix wins, matched on segment boundaries, so /api does not match /apiary. Host comes before prefix, as in nginx: a route on shop.example.com with prefix / wins over a *.example.com route with prefix /api for shop.example.com/api/.... Prefixes are matched against the request’s escaped path one segment at a time, so an encoded slash (%2F) never stands in for a separator of the prefix, and with strip_prefix the rest is forwarded exactly as the client encoded it. A request that matches no route gets a 404.

  3. Access. A client on a deny list gets a 403. A client on a threat feed with action: block, or flagged by reputation with action: block, gets a 403 too; with monitor both are reported and forwarded. Allow-listed clients are marked here (see access.allow).

  4. Deception. A request to a decoy path is answered by Edge and never reaches an upstream. The client IP is then marked suspicious for deception.ttl on every site the instance serves. An enrolled instance shares the mark through Shield360, so its other instances apply it within a few seconds and a restart does not lose it. With action: block, the client’s later requests get a 403. With action: monitor, they are forwarded with X-Shield360-Suspicious: deception and reported. Both are reported as the deception attack type (deception.hit, deception.suspicious), so they show in the runtime protection views. The app’s signed config can lower the action with attacks.deception (monitor caps it, off stops reporting), never raise monitor to block. Decoy paths set in the console’s Edge settings also apply to the SDK of every app in the environment, on top of each app’s own list, so a path is defined once. An app keeps its own action and TTL.

  5. Rate limit. A token bucket per client IP and route, then each matching rate_limits rule. A request over a limit gets a 429 with Retry-After.

  6. Body limit. A request over max_body gets a 413, whether it declared its length or was sent chunked.

  7. OpenAPI (routes with openapi). The request is checked against the document; in block mode a request it does not allow gets a 400.

  8. Web application firewall (app routes with waf.enabled). See Web application firewall. Scoped exclusions run before the rule set.

  9. Inspection (app routes). The route runs the runtime protection request checks for its app:

    • It records the request’s input: query values, cookies, the input headers (user-agent, referer, x-forwarded-for), JSON string leaves, form fields, raw text bodies and upload file names. At most 256 inputs are kept, 4096 characters each, and 8 KiB of raw text.
    • It runs file_write.upload on every upload file name.
    • It runs the virtual patches: header rules first, then body rules.

    Edge buffers the first 1 MiB of a POST, PUT, PATCH or DELETE body to judge it. The upstream still receives the whole body, streamed. A blocked request gets a plain-text 403 that shows the request id and the event id.

  10. Forward. The request is forwarded with these headers:

  • X-Forwarded-For, -Proto and -Host. Values a client sent are kept only when the client is a trusted proxy.
  • X-Real-Ip.
  • X-Request-Id. A value from a trusted proxy is reused; otherwise Edge makes a new one.

Streams (server-sent events and chunked responses) are flushed as they arrive. Upgrades such as websockets are passed through. 11. Response. Security headers are added as the response starts. For HTML responses on inspected routes, Edge keeps a copy of the body (up to 1 MiB) while the client receives it. After the response is complete, it runs xss.reflected on the copy in the background, and, with waf.response.enabled, the rule set’s response rules. These checks only report. They never change, delay or block a response.

AI routes (ai: true) skip steps 7 to 9 and 11. They go through the same enforcement as shield360 proxy and the hosted AI proxy:

  • vendor allow, warn and block (aiTools);
  • approved models;
  • sensitive data in prompts: warn, mask (restored in the answer, streams included) or block;
  • prompt injection;
  • refusals in the provider’s own error shape.

Edge adds a per-key budget, and counts each answer’s tokens and list-price cost, from JSON responses and from streams.

Fail open. Inspection that panics is recovered, and the request is forwarded. An inspect.failed event records it. Set fail_closed: true on a route to answer 503 instead. Telemetry and reporting failures never affect a request.

Modes follow the runtime protection rules:

  • Monitor is the default. Blocking is opt-in per route and per attack type. xss never blocks.
  • The signed config wins. When a key is pinned, the platform’s per-app settings override the route’s local modes.
  • No pinned key means monitor only. When a Shield360 URL and API key are set but no config key is pinned, every route stays in monitor mode. Edge logs a warning for each app.
  • Kill switches. SHIELD360_RASP=off and the signed config’s killSwitch turn inspection off.
EventWhereAttributes
Request span (one per request)OTLPhttp.request.method, server.address, http.response.status_code, shield360.edge.route, shield360.rasp.app, shield360.edge.request_id, the report context above (path, client network and kind), shield360.edge.answered (true when Edge answered itself: a block, a decoy, a rate limit) and, when the app answered, shield360.edge.upstream_ms (the app’s time, so Edge’s own share is the rest). Shown as Detections → Runtime events → Shield360 Edge → Traffic.
shield360.rasp.eventGuard events endpoint (each app’s reporter: batched, folded, at most 100 a minute) and OTLP span eventThe runtime protection shield360.rasp.* attributes, with framework shield360-edge, plus shield360.edge.route and shield360.edge.request_id on the span. Includes the Edge-only deception attack type. The platform’s runtime protection views show these.
shield360.edge.eventOTLP span event, logshield360.edge.rule is one of deception.hit, deception.suspicious, ratelimit.exceeded, inspect.failed, budget.exceeded, waf.matched, waf.blocked, waf.failed, waf.response_matched, access.denied, access.feed, reputation.escalated, reputation.flagged or openapi.violation. Every event also carries the report context: shield360.edge.path (the path’s first segment only, /:other when it is not a plain word), shield360.edge.client.network (or .client.ip with client_ip: full) and shield360.edge.client.kind (browser, bot, scanner, tool or unknown, derived from the User-Agent, which is never sent). WAF events add shield360.edge.waf.score (the inbound anomaly score), .threshold, .paranoia and .targets (the inputs that matched, by name: ARGS:id, REQUEST_HEADERS:user-agent; never their values). Rate limit events of a path rule add shield360.edge.ratelimit.rule; feed events shield360.edge.feed; OpenAPI events shield360.edge.openapi.reason, .parameter and .operation; an allow-listed client’s WAF match shield360.edge.allowlisted. Also shield360.edge.decision, shield360.edge.route, shield360.rasp.app and shield360.edge.decoy; WAF events add shield360.edge.waf.rules (matched rule ids), shield360.edge.waf.attacks (sqli, xss, …), shield360.edge.waf.mode and, when blocked, shield360.edge.waf.rule.
shield360.guard.decision, shield360.injection.detectionOTLP span eventThe AI proxy’s attributes.
shield360.edge.ai.usageOTLP span eventgen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens and shield360.edge.cost_usd (list price).

Each proxied request is one server span (<METHOD> <route>) with http.request.method, http.response.status_code, server.address, shield360.edge.route, shield360.rasp.app and shield360.edge.request_id.

Events name locations, rules and decisions. They never hold request values, API keys or client IP addresses. A snippet is sent only when the signed config allows snippets, as in the SDKs.

An enrolled Edge instance is a managed member of an environment. It reads the environment’s AI policy, reports its AI decisions and spend, and shows in WAF & RASP → Edge instances with its last heartbeat. The steps are in Connect Edge to Shield360.

Edge exchanges the token at POST /api/edge/enroll for its own credential: a collector token for a device of kind edge, which the platform scopes to the token’s environment. Shield360 keeps only the token’s SHA-256 hash. Edge saves the credential in <cache_dir>/edge-enrollment.json with mode 0600, and later starts use it without the token. When Shield360 revokes the credential (Revoke on the instance), Edge enrolls again with the token if it still has one, at most once a minute. Revoking a token stops new enrollments only. The enroll route takes bodies of up to 4 KiB and 30 attempts a minute per client address. If Shield360 cannot be reached at start, Edge serves traffic anyway and tries to enroll again at each policy refresh. Neither secret is ever logged.

AI routes read the environment’s AI policy from the endpoint the AI proxy uses, GET /api/collector/device/policy, with the instance credential. Edge asks again every policy_refresh with If-None-Match, so an unchanged policy costs a 304. The last good answer is cached in <cache_dir>/edge-policy-cache.json (0600), with the Shield360 address and instance it belongs to and when Shield360 last confirmed it. A cache (or undelivered events) from another Shield360 address or another enrollment is never used.

By default Edge fails open. While Shield360 cannot be reached, the policy in force (or the cached one after a restart) stays. With no policy at all, AI requests are forwarded unchecked.

Fail closed. When an administrator turns on guardFailClosed with an enforcing AI guard (proxyGuard: block), AI routes honor it exactly as the AI proxy does:

  • Edge keeps a separate marker, <cache_dir>/edge-policy.failclosed (0600), beside the cache while the last policy asked to fail closed, so a cache that is lost or corrupt cannot also erase the instruction. It is removed when a policy without it arrives.
  • A fail-closed policy that Shield360 has not confirmed (a 200 or 304) within its freshness window, guardMaxAgeHours (72 hours), is stale, as for the coding-agent hooks.
  • While no trustworthy policy is in force (none could be loaded and the marker is present, or the policy is stale), AI requests are refused with 403 policy_unavailable in the provider’s error shape, and the refusal is reported. Health shows degraded.

App routes (runtime protection, rate limits, deception) stay fail-open; fail_closed on a route only concerns its inspection.

A route’s ai_policy_file stays as an override for air-gapped installs, or, with ai_policy_mode: fallback, as the policy until the platform’s is in force.

Every AI decision goes to the collector ingest route (/api/collector/device/events) with the instance credential, as the AI proxy reports: guard decisions (shield360.guard.decision, source proxy), prompt-injection detections (shield360.injection.detection) and budget refusals (guard name budget). Each answered request’s token counts go to the usage table (shield360.collector.usage, tool shield360-edge, auth type api_key), so the cost views price Edge traffic as billed API usage, attributed to X-Shield360-User when a request names one. Events are batched every two seconds. What cannot be delivered is written at once to <cache_dir>/edge-events-pending.jsonl (0600, up to 5,000 events) and retried; the file shrinks only once Shield360 has accepted its events, so a crash or kill loses nothing undelivered.

The environment’s per-key budget (WAF & RASP → Edge instances → Per-key AI budget) comes with the policy as edgeBudget. It applies to every AI route without a budget of its own, keyed as a route budget is (header:<name>, user or ip).

Enforcement is in memory, per instance, as for route budgets. With N replicas, a key can spend up to N times the limit, and a restart starts the count again. A raised or lowered limit keeps what was spent; a new period or key starts counting again.

Every heartbeat_interval, Edge posts its version, its routes (name, whether AI, vendor), its health and its counters (requests, blocked, AI requests, AI blocked, budget refusals since start) to POST /api/edge/heartbeat. Health is degraded while an AI route has no policy in force or the server is draining. Only names and counts are sent.

App routes still fetch the signed runtime protection config with the app’s API key (SHIELD360_API_KEY), as the SDKs do. The instance credential is not used for it.

  • Rate limits and budgets live in memory, per instance. With N replicas, a client can make up to N times the rate, and a key can spend up to N times its budget. A restart forgets both. Deception marks are shared only between enrolled instances, and private client addresses stay on the instance that saw them.
  • Budget memory is bounded. Only charged requests create an entry (at most 100,000 keys); at the cap an entry from an earlier period, else a small spender, is dropped.
  • Budgets are checked before a request and charged after it. Requests running at the same time can go past a limit by what they cost together. Costs are list prices.
  • Edge sees requests and responses, not the app’s sinks. It cannot see SQL, shell, file or SSRF calls. Those checks need the SDK’s runtime protection. At the edge, Edge runs virtual patches, upload file names and reflected XSS.
  • Some bodies are not fully inspected. Only the first 1 MiB of a body is judged. A body with a Content-Encoding is not judged (header rules still run). Compressed HTML responses are not checked for XSS.
  • Shutdown does not wait for upgraded connections. Websockets close when the process exits.
  • No TLS. Edge serves plain HTTP. Terminate TLS in front of it.
  • AI routes fail open unless told otherwise. With no platform policy, no cached one and no file, AI requests are forwarded unchecked, unless the last policy set guardFailClosed (see AI policy).
  • Rate limits and budgets shared between replicas.
  • A Kubernetes sidecar and an ingress controller mode, with Helm charts.
  • TLS termination with ACME certificates, and HTTP/2 and HTTP/3 to clients.
  • Several upstreams per route, with health checks.