mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-27 15:51:33 +02:00
Encrypt task context snapshots at rest
A background task still needs to know who asked for the work, so FastMCP captures the caller's access token and every inbound HTTP header at submission time and writes that snapshot to the Docket backend. On a distributed backend the credentials sit in Redis as plaintext for the task's TTL, and a rediss:// URL protects only the wire. Set FASTMCP_ENCRYPTION_KEY and the snapshot becomes a Fernet token instead. Restore fails closed: a worker that cannot decrypt a snapshot fails the task rather than run the tool with no caller. Closes #4747 🤖 Generated with Claude Code Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8661193411
commit
db4450e40b
9 changed files with 517 additions and 4 deletions
|
|
@ -85,10 +85,11 @@ Task settings (the `FASTMCP_DOCKET_` variables) moved to the optional `fastmcp-t
|
|||
|
||||
## Security
|
||||
|
||||
These control FastMCP's SSRF protection for the outbound fetches it makes during authentication (OAuth client metadata and JWKS).
|
||||
These control encryption of FastMCP's data at rest, and its SSRF protection for the outbound fetches it makes during authentication (OAuth client metadata and JWKS).
|
||||
|
||||
| Environment Variable | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `FASTMCP_ENCRYPTION_KEY` | `str` | None | Key used to encrypt sensitive FastMCP data at rest. Today this protects the [task context snapshot](/servers/tasks#credentials-at-rest), which carries the submitting caller's access token and HTTP headers and lives in the Docket backend for the task's TTL. Every server and worker sharing a task queue must set the same key. When unset, the snapshot is stored as plaintext JSON. |
|
||||
| `FASTMCP_SSRF_TRUST_PROXY` | `bool` | `false` | Trust an outbound HTTP proxy for SSRF-protected fetches. When `false`, FastMCP resolves the target hostname itself and refuses to connect if it maps to a private, loopback, link-local, or reserved IP. When `true`, FastMCP routes auth metadata and JWKS fetches through the configured `HTTPS_PROXY`/`ALL_PROXY` and does not honor `NO_PROXY`; if no proxy is configured the fetch is refused. |
|
||||
|
||||
By default, FastMCP protects its OAuth and JWKS fetches against [SSRF](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery) by resolving the target hostname, rejecting any address that maps to a private, loopback, link-local, or reserved IP, and then pinning the connection to that validated IP.
|
||||
|
|
|
|||
|
|
@ -193,6 +193,28 @@ mcp.add_extension(TasksExtension(url="redis://localhost:6379/0"))
|
|||
- **Fast**: Single-digit millisecond task pickup latency
|
||||
- **Scalable**: Add workers to distribute load across processes or machines
|
||||
|
||||
### Credentials at Rest
|
||||
|
||||
A background task runs long after the request that submitted it has ended, but it still needs to know who asked for the work. FastMCP captures that identity at submission time in a **task context snapshot**: the caller's access token and every inbound HTTP header, including `Authorization`. The worker restores the snapshot before the tool body runs, so `get_access_token()` and `get_http_headers()` return the submitting caller.
|
||||
|
||||
That snapshot lives in the backend for the task's TTL. With `memory://` it never leaves the process. With Redis or Valkey it is a stored value, and by default it is stored as plaintext JSON. A `rediss://` URL encrypts the connection, not the data the backend holds. Anyone who can read the backend can read the tokens.
|
||||
|
||||
Set `FASTMCP_ENCRYPTION_KEY` to encrypt the snapshot before it is written:
|
||||
|
||||
```bash
|
||||
export FASTMCP_ENCRYPTION_KEY=$(python -c "import secrets; print(secrets.token_urlsafe(32))")
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Every server and worker on the same queue must set the same key. The process that restores a snapshot is rarely the one that captured it, and a worker with the wrong key cannot recover the caller.
|
||||
</Warning>
|
||||
|
||||
With a key configured, restore **fails closed**: a worker that cannot decrypt a snapshot fails the task instead of running the tool with no identity. This matters for a tool whose behavior depends on the caller: running it as an anonymous user is worse than not running it. The failure is reported to the client as a task error, and the server log names the key mismatch.
|
||||
|
||||
Two consequences of failing closed are worth planning for. Tasks submitted before the key was set fail when a worker with the key picks them up, so drain the queue before you roll a key out. Rotating a key does the same to tasks in flight under the old one.
|
||||
|
||||
The key protects the snapshot only. Tool arguments and any answers a task gathers through [mid-task input](#gathering-input-mid-task) are still stored as plaintext, so treat the backend as sensitive regardless.
|
||||
|
||||
## Workers
|
||||
|
||||
Every FastMCP server with task-enabled tools automatically starts an **embedded worker**. You do not need to start a separate worker process for tasks to execute.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue