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:

  1. If expires_at is set and the current time is past that value → skip the entry
  2. Extract the descriptor for scope_key from the request context
  3. If the descriptor value equals scope_value:
    • If route is set: also require the request path to equal route
    • If both conditions are satisfied → reject with Retry-After: 3600

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_version increments 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