Kill Switches
Kill switches provide an emergency brake. They are evaluated before any route matching or policy rules, making them the fastest and most authoritative way to block traffic.
If top-level kill_switch_override is active, this pre-check is skipped for its TTL window.
Structure
Kill switches live in the top-level kill_switches array:
{
"bundle_version": 2,
"kill_switches": [
{
"scope_key": "header:x-tenant-id",
"scope_value": "tenant-compromised",
"reason": "incident-2026-01-15",
"expires_at": "2026-01-16T00:00:00Z"
}
],
"policies": [ ... ]
}
Fields
| Field | Type | Required | Description |
|---|---|---|---|
scope_key |
string | yes | Descriptor key to match against. Format: source:name. See Descriptor Keys. |
scope_value |
string | yes | Exact value to match. Case-sensitive. |
route |
string | no | If set, only applies when the request path equals this string exactly. |
reason |
string | no | Human-readable reason string; included in the X-Fairvisor-Reason response header. |
expires_at |
string | no | ISO 8601 UTC. After this time the entry is ignored. Evaluated on every request — see note below. |
scope_key must match the pattern ^(jwt|header|query|ip|ua):[A-Za-z0-9_-]+$.
expires_at is evaluated on every request, not just at load time. Once the timestamp passes, the kill switch entry stops matching automatically — no bundle reload required. This is different from the top-level bundle expires_at, which is only checked when the bundle is loaded.
Evaluation
The engine iterates all kill switch entries. For each entry:
- If
expires_atis set and the current time is past that value → skip the entry - Extract the descriptor for
scope_keyfrom the request context - If the descriptor value equals
scope_value:- If
routeis set: also require the request path to equalroute - If both conditions are satisfied → reject with
Retry-After: 3600
- If
First match wins. Subsequent entries are not evaluated after a match.
Response on match
HTTP 429 Too Many Requests
X-Fairvisor-Reason: kill_switch
Retry-After: 3600
Common use cases
Block a compromised tenant
{
"scope_key": "header:x-tenant-id",
"scope_value": "tenant-42",
"reason": "account_suspended"
}
Block a specific IP
{
"scope_key": "ip:address",
"scope_value": "203.0.113.5",
"reason": "abuse"
}
Block all bot traffic on one route
{
"scope_key": "ua:bot",
"scope_value": "true",
"route": "/v1/chat/completions",
"reason": "scraper_block"
}
Temporary block with auto-expiry
{
"scope_key": "jwt:org_id",
"scope_value": "org-abc",
"reason": "billing_hold",
"expires_at": "2026-02-01T00:00:00Z"
}
After the expiry date passes, the entry is silently skipped without requiring a bundle update.
Block by ASN
{
"scope_key": "ip:asn",
"scope_value": "AS64496",
"reason": "datacenter_block"
}
Activating a kill switch without a full bundle redeploy
Because kill switches are part of the bundle, the fastest way to activate one is via the SaaS dashboard (which pushes an updated bundle to all connected edges) or by incrementing bundle_version and triggering a file reload with kill -HUP $(pidof nginx).
Tip: Keep
bundle_versionincrements small (e.g., +1 each edit). Avoid large jumps that could skip valid intermediate versions.
Temporarily disabling kill switch checks
In break-glass scenarios you can temporarily disable kill-switch enforcement with top-level kill_switch_override:
{
"kill_switch_override": {
"enabled": true,
"reason": "incident-2026-02-20",
"expires_at": "2026-02-20T19:00:00Z"
}
}
Notes:
- This override is global (not per-tenant/per-route)
- It auto-expires at
expires_at - No client-visible response header is added; verify activation through logs and metrics