# Limits

Sizes, protocols, rates and timeouts, in one place.

Source: https://seal.zone/docs/limits

The enclave, the SDKs and the CLI enforce every number here. Going over one usually returns `BadRequest` when you seal or `PolicyDenied` in a session; see [Errors](https://seal.zone/docs/errors). Plan allowances aren't enforced yet, so they aren't listed.

## Secrets

| Where it goes | Length | Characters |
|---|---|---|
| HTTPS API, in a header | 16 to 8,192 bytes | Printable ASCII and spaces only |
| Postgres password | 16 to 1,024 bytes | Printable ASCII and spaces only |
| Redis password | 16 to 1,024 bytes | Anything except a NUL byte |

Why 16 bytes: someone who can make the upstream echo chosen text could test guesses against the response masking, and a short secret falls fast. The minimum can refuse an existing short database password, so set a longer one first. Header values can't carry control characters, and printable ASCII keeps Postgres's password normalization independent of the secret's bytes.

### What you seal for

| Thing | Limit |
|---|---|
| Destination schemes | `https://`, `postgres://` (or `postgresql://`), `rediss://`. `http://`, `redis://` and plaintext Postgres are refused. |
| Destination URL | No password, no query string, no fragment. Postgres needs a user and exactly one database. |
| Default ports | 443 for HTTPS, 5432 for Postgres, 6379 for Redis. |
| Path prefix | An absolute ASCII path: no `%`, `?`, `#`, `\`, `.` or `..` segments, or `//`. Requests outside it are refused. |
| Database user and name | 1 to 63 visible ASCII characters, no percent-encoding. |
| Redis database | A number in the path, default 0. Commands for any other database are refused. |
| Apps per blob | At least one. A blob never works for an app it doesn't name. |
| Header | A valid HTTP header name. Not `host`, `content-length`, `transfer-encoding`, `connection`, `cookie`, `te` or `upgrade`. |
| Header format | Contains `{key}` exactly once. Printable ASCII and spaces only. |
| Methods | Uppercase HTTP method names. |
| Pins | Up to 4 per blob. |
| App and tenant names | 1 to 32 characters of `a-z`, `0-9` and `-`, not starting or ending with `-`. |
| Upstream certificates | Must chain to the Mozilla roots or the AWS RDS bundle. Azure's managed database and cache certificates are in the Mozilla set. See [APIs and databases](https://seal.zone/docs/upstreams). |

## Protocols

### HTTP

| Rule | Why |
|---|---|
| The key goes in a header, never the body or query string. | The enclave can only place it in a header today. |
| No WebSockets, `Upgrade` or `CONNECT`. | A tunnel could carry the key past the enclave's checks. |
| Requests use a plain path (`/v1/…`), not a full URL. | The enclave always sends to the sealed host. |
| Redirects come back to your app unfollowed. | Following one could send the key to another host. |
| Method-override headers and `_method` parameters are refused. | Some frameworks read them as the real method, around `--methods`. |
| Compressed responses are refused. The enclave asks for uncompressed ones. | It can't scan a compressed body for the key. |
| `Range` and `If-Range` headers are dropped. | A partial response could slip a piece of the key past the masking. |
| With `--methods` set, `multipart/form-data` is refused, and form or JSON bodies are read whole, up to 1 MiB. | The enclave looks for a `_method` field. |
| One stand-in key per connection. | A connection belongs to one session. |
| Responses stream, server-sent events included. A few bytes are held back until 100 ms of quiet. | That catches a key split across chunks. |

### Postgres

| Rule | Why |
|---|---|
| The server must offer TLS and SCRAM-SHA-256. No md5, no plaintext passwords. | The others would expose the password to the network or the host. |
| The enclave logs in as the blob's user and database, whatever your client sends. | The blob decides where the password goes. |
| Replication and `FunctionCall` are refused. Startup `options` are dropped. | They reach around the checked connection. |
| One query or prepared statement can be up to 16 MiB. | The enclave reads it whole before passing it on. |
| Cancel requests reach only the replica holding the connection. | Each replica keeps its own connections. |

### Redis

| Rule | Why |
|---|---|
| TLS only (`rediss://`). Standalone servers, not Redis Cluster. | Cluster redirects go back to your client and aren't followed. |
| `AUTH` again, `HELLO … AUTH`, admin commands (`ACL`, `CONFIG`, `MONITOR`, `FLUSHALL` and others) and other databases are refused. | They change who you are or reach past the sealed database. |
| One argument up to 64 MiB, one command up to 96 MiB. | Commands are read whole to check them. |

## Requests, sessions and timing

Per replica. Going over a rate limit returns `BudgetExceeded`.

| Limit | Value |
|---|---|
| Open connections | 1,024 in all, API calls and app traffic together |
| Connections from one source | 512 per IPv4 address or IPv6 /64. Apps behind one NAT address share it. |
| Seals | Bursts of 20, then 2 per second, per source |
| Identity proof tickets (the SDK reuses one for about 2 minutes) | Bursts of 20, then 5 per second, per source |
| Seal and session-start request body | 1 MiB |
| Extend, end and signal request body | 64 KiB |
| Idle API connection | Closed after 120 seconds |
| TLS handshake | 10 seconds |
| Idle HTTP keep-alive from your app | 30 seconds |

| Thing | Value | What it means for you |
|---|---|---|
| Session token | 10 minutes | Lives inside the SDK. Your code never handles it. |
| Extend | Every 5 minutes | The SDK proves the app's identity again, automatically. |
| Grant and settings changes reaching replicas | About 30 seconds | Replicas recheck live sessions on each change. |
| Enclave attestation age | At most 120 seconds | The SDK refuses an enclave with an older hardware report. |
| Waiting for an unreachable replica | Up to 120 seconds | The SDK tries the other replicas, then raises `Unavailable`. |
| Disabling an AWS, GitHub or Kubernetes identity at its provider | About 5 minutes | A copied credential stops at the next extend. |
| Disabling an Azure or GCP identity at its provider | Until its cached token expires | Up to 24 hours for an Azure managed identity. Remove the grant in Seal to cut it within about 30 seconds. |

## Replicas and telemetry

| Thing | Value |
|---|---|
| Seal-hosted replicas per tenant | One, in one Azure region |
| Seal-hosted idle stop | After 15 minutes with no authenticated traffic. Replicas you run never stop when idle. |
| Cold start | About a minute. The SDK waits up to 120 seconds, so the first call after a quiet spell is slow. |
| Exit timer on your own replica | `SEAL_EXIT_AFTER_SECS`, 60 to 604,800 seconds (one week) |
| Telemetry batch interval | At most every 5 seconds |
| Telemetry buffer | 10,000 unsent entries per replica. Past that, request entries are dropped first, and each drop is recorded. |

See [Where the enclave runs](https://seal.zone/docs/hosting) and [Telemetry](https://seal.zone/docs/telemetry).

## Tenants and admins

| Thing | Limit |
|---|---|
| Tenant id | 1 to 32 characters of `a-z`, `0-9` and `-`. It can't be changed later. |
| Threshold | 1 up to the number of admin keys |
| Pending window | 0 to 24 hours. Shortening it waits out the current window. |
| Freshness window | 5 minutes to 24 hours. Default 24 hours. |
| Signal codes | 1 to 64 characters of `a-z`, `0-9`, `-` and `.` |
| Release signatures | 2 of 3 release keys. See [Releases and the log](https://seal.zone/docs/releases). |

Keep at least two admin keys. If you lose your only one, nobody can ever change the tenant again.

## Freshness and failing closed

A replica acts only on a tenant state with a current freshness statement, so nobody can replay an old state for longer than the window.

| Situation | What happens |
|---|---|
| No fresh state for longer than the freshness window | Every seal and session fails with `Unavailable` until the replica gets one. |
| Key guardian unreachable when a replica boots | The replica gets no keys and serves nothing. |
| Key guardian unreachable while a replica runs | It keeps its tenant keys for 15 minutes, then starts no new sessions until it reaches the guardian. |

An outage stops your keys from working. It never makes them readable. A shorter freshness window limits replays, and also how long a replica rides out an outage.
