# Kill Switch

- Canonical URL: https://docs.fairvisor.com/docs/algorithms/kill-switch/
- Section: docs
- Last updated: n/a
> Descriptor-based emergency blocker evaluated before any rate-limit rule.


The kill switch is an emergency blocking mechanism that runs **before** any rate-limit rule evaluation. When a request matches a kill switch entry, it is rejected immediately with no further processing.

See [Kill Switches configuration](/docs/policy/kill-switches/) for the policy schema. This page covers the algorithm internals.

## How it works

Kill switches are the first check in the evaluation pipeline:

```
request
  └─ bundle loaded?  (503 if not)
  └─ kill switch scan  ← evaluated here, BEFORE route matching and rules
  └─ route matching
  └─ rule evaluation (token bucket, cost budget, …)
  └─ allow
```

This guarantees that a kill switch fires regardless of which policy or rule would have matched.

For each kill switch entry, the engine:

1. Extracts the descriptor value from the request using the entry's `scope_key`
2. Compares the extracted value to `scope_value` (string equality, case-sensitive)
3. If a `route` is present, also checks whether the request path is an exact match
4. Checks the `expires_at` timestamp, if present — expired entries are skipped
5. If all conditions match: **reject immediately**

A request must match **all** conditions in a single entry. Entries are evaluated in declaration order; the first match wins. The scan is O(n) over all entries — expired entries are skipped after a single timestamp comparison.

## Configuration

Kill switch entries are defined in the policy bundle. See [Kill Switches configuration](/docs/policy/kill-switches/) for the full schema.

## Supported scope keys

| Scope key | Source | Example value |
|---|---|---|
| `jwt:<claim>` | JWT claim extracted from Bearer token | `jwt:org_id` → `"org-abc"` |
| `header:<name>` | HTTP request header | `header:x-tenant-id` → `"tenant-42"` |
| `query:<param>` | URL query parameter | `query:api_key` → `"k_abc123"` |
| `ip:address` | Client IP address | `"203.0.113.42"` |
| `ip:country` | GeoIP country code | `"TR"`, `"RU"` |
| `ip:asn` | GeoIP ASN number | `"AS12345"` |
| `ua:bot` | Bot detection from User-Agent | `"true"` |

## TTL / expiration

The optional `expires_at` field accepts an ISO 8601 UTC timestamp:

```json
{
  "scope_key": "jwt:org_id",
  "scope_value": "org-abc",
  "expires_at": "2026-03-01T00:00:00Z"
}
```

The timestamp is parsed into a Unix epoch at bundle load time for zero-cost checking on each request. An entry with an `expires_at` in the past is silently skipped.

## Route scoping

Omitting `route` makes the entry match any path. Providing `route` restricts the block to an exact URI path:

```json
{
  "scope_key": "ip:country",
  "scope_value": "TR",
  "route": "/api/v1/completions"
}
```

This entry only blocks Turkish IPs on `/api/v1/completions`; other paths are unaffected.

## Response headers

When a kill switch fires:

```
HTTP 429 Too Many Requests
Retry-After: 3600
X-Fairvisor-Reason: kill_switch
```

The `Retry-After` value is fixed at **3600 seconds** (1 hour). The optional `reason` field in the kill switch entry is logged but not exposed to the client.

## Failure behavior

Kill switches have no per-request mutable state, so there is no shared dict operation on the hot path and no dict-failure code path. If the policy bundle fails to load at startup, Fairvisor returns `503 Service Unavailable` for all requests until a valid bundle is loaded.

## Shadow mode

In shadow mode, the kill switch check is still performed but the result is recorded as `would_reject = true` rather than actually rejecting the request. This lets you test a new kill switch entry in production without blocking traffic.

## Tuning

The kill switch scan is O(n) where n is the number of entries. For deployments with large entry sets (>100), scope entries with `route` to reduce the effective scan depth per request path.

## Example

```json
{
  "kill_switches": [
    {
      "scope_key": "jwt:org_id",
      "scope_value": "org-abc"
    }
  ]
}
```

This blocks all requests from org `org-abc`. Add `"expires_at": "2026-04-01T00:00:00Z"` for a time-limited block.

