fix(security): Close review follow-up gaps
Rate-limit the optional agent bearer path after user auth resolution so mixed user/agent routes no longer leave an unthrottled PBKDF2 path. Stop logging token prefixes on agent auth failures and require a locally supplied token for backend/.env.test instead of committing one. Update tests and docs to cover agent bearer fallback, configurable webhook signature headers, and the operator-facing security settings added by the hardening work. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -107,13 +107,13 @@ When using the in-memory backend in multi-process deployments, also apply rate l
|
||||
|
||||
### Webhook signature errors (403)
|
||||
|
||||
If a webhook has a `secret` configured, inbound payloads must include a valid HMAC-SHA256 signature in one of these headers:
|
||||
If a webhook has a `secret` configured, inbound payloads must include a valid HMAC-SHA256 signature. If the webhook also sets `signature_header`, that exact header name must be used. Otherwise the backend checks these defaults:
|
||||
|
||||
- `X-Hub-Signature-256: sha256=<hex-digest>` (GitHub-style)
|
||||
- `X-Webhook-Signature: sha256=<hex-digest>`
|
||||
|
||||
Missing or invalid signatures return `403 Forbidden`. If you see unexpected 403s on webhook ingest, verify that the sending service is computing the HMAC correctly using the webhook's secret.
|
||||
Missing or invalid signatures return `403 Forbidden`. If you see unexpected 403s on webhook ingest, verify that the sending service is computing the HMAC correctly using the webhook's secret and sending it in the configured header.
|
||||
|
||||
### Webhook payload too large (413)
|
||||
|
||||
Webhook ingest enforces a **1 MB** payload size limit. Payloads exceeding this return `413 Content Too Large`. If you need to send larger payloads, consider sending a URL reference instead of inline content.
|
||||
Webhook ingest enforces a **1 MB** payload size limit by default. Payloads exceeding this return `413 Content Too Large`. If you need to raise the limit, set `WEBHOOK_MAX_PAYLOAD_BYTES`; otherwise consider sending a URL reference instead of inline content.
|
||||
|
||||
@@ -45,7 +45,7 @@ Some endpoints are designed for autonomous agents and use an agent token header:
|
||||
X-Agent-Token: <agent-token>
|
||||
```
|
||||
|
||||
In the backend, these are enforced via the “agent auth” context. When in doubt, consult the route’s dependencies (e.g., `require_user_or_agent`).
|
||||
On shared user/agent routes, the backend also accepts `Authorization: Bearer <agent-token>` after user auth does not resolve. When in doubt, consult the route’s dependencies (e.g., `require_user_or_agent`).
|
||||
|
||||
Agent authentication is rate-limited to **20 requests per 60 seconds per IP**. Exceeding this limit returns `429 Too Many Requests`.
|
||||
|
||||
@@ -83,7 +83,7 @@ The following per-IP rate limits are enforced on sensitive endpoints:
|
||||
|
||||
| Endpoint | Limit | Window |
|
||||
| --- | --- | --- |
|
||||
| Agent authentication (`X-Agent-Token`) | 20 requests | 60 seconds |
|
||||
| Agent authentication (`X-Agent-Token` or agent bearer fallback on shared routes) | 20 requests | 60 seconds |
|
||||
| Webhook ingest (`POST .../webhooks/{id}`) | 60 requests | 60 seconds |
|
||||
|
||||
When a rate limit is exceeded, the API returns `429 Too Many Requests`.
|
||||
|
||||
@@ -31,9 +31,9 @@ Frontend:
|
||||
|
||||
## Agent authentication
|
||||
|
||||
Autonomous agents authenticate via an `X-Agent-Token` header (not the bearer token used by human users). See [API reference](api.md) for details.
|
||||
Autonomous agents primarily authenticate via an `X-Agent-Token` header. On shared user/agent routes, the backend also accepts `Authorization: Bearer <agent-token>` after user auth does not resolve. See [API reference](api.md) for details.
|
||||
|
||||
Security notes:
|
||||
|
||||
- Agent auth is rate-limited to **20 requests per 60 seconds per IP**. Exceeding this returns `429 Too Many Requests`.
|
||||
- On authentication failure, only a short prefix of the presented token is logged to aid debugging. Full tokens are never written to logs.
|
||||
- Authentication failure logs never include token material.
|
||||
|
||||
@@ -18,6 +18,30 @@ See `.env.example` for defaults and required values.
|
||||
- **When required:** `AUTH_MODE=local`
|
||||
- **Policy:** Must be non-placeholder and at least 50 characters.
|
||||
|
||||
### `WEBHOOK_MAX_PAYLOAD_BYTES`
|
||||
|
||||
- **Default:** `1048576` (1 MiB)
|
||||
- **Purpose:** Maximum accepted inbound webhook payload size before the API returns `413 Content Too Large`.
|
||||
|
||||
### `RATE_LIMIT_BACKEND`
|
||||
|
||||
- **Default:** `memory`
|
||||
- **Allowed values:** `memory`, `redis`
|
||||
- **Purpose:** Selects whether rate limits are tracked per-process in memory or shared through Redis.
|
||||
|
||||
### `RATE_LIMIT_REDIS_URL`
|
||||
|
||||
- **Default:** _(blank)_
|
||||
- **When required:** `RATE_LIMIT_BACKEND=redis` and `RQ_REDIS_URL` is not set
|
||||
- **Purpose:** Redis connection string used for shared rate limits.
|
||||
- **Fallback:** If blank and Redis rate limiting is enabled, the backend falls back to `RQ_REDIS_URL`.
|
||||
|
||||
### `TRUSTED_PROXIES`
|
||||
|
||||
- **Default:** _(blank)_
|
||||
- **Purpose:** Comma-separated list of trusted reverse-proxy IPs or CIDRs used to honor `Forwarded` / `X-Forwarded-For` client IP headers.
|
||||
- **Gotcha:** Leave this blank unless the direct peer is a proxy you control.
|
||||
|
||||
## Security response headers
|
||||
|
||||
These environment variables control security headers added to every API response. Set any variable to blank (`""`) to disable the corresponding header.
|
||||
|
||||
@@ -21,7 +21,7 @@ Per-IP rate limits are enforced on sensitive endpoints:
|
||||
|
||||
| Endpoint | Limit | Window | Status on exceed |
|
||||
| --- | --- | --- | --- |
|
||||
| Agent authentication (`X-Agent-Token`) | 20 requests | 60 seconds | `429` |
|
||||
| Agent authentication (`X-Agent-Token` or agent bearer fallback on shared routes) | 20 requests | 60 seconds | `429` |
|
||||
| Webhook ingest (`POST .../webhooks/{id}`) | 60 requests | 60 seconds | `429` |
|
||||
|
||||
Two backends are supported, selected via `RATE_LIMIT_BACKEND`:
|
||||
@@ -35,7 +35,7 @@ The Redis backend fails open — if Redis becomes unreachable during a request,
|
||||
|
||||
## Webhook HMAC verification
|
||||
|
||||
Webhooks may optionally have a `secret` configured. When a secret is set, inbound payloads must include a valid HMAC-SHA256 signature in one of these headers:
|
||||
Webhooks may optionally have a `secret` configured. When a secret is set, inbound payloads must include a valid HMAC-SHA256 signature. If `signature_header` is configured on the webhook, that exact header is required. Otherwise the backend falls back to these default headers:
|
||||
|
||||
- `X-Hub-Signature-256: sha256=<hex-digest>` (GitHub-style)
|
||||
- `X-Webhook-Signature: sha256=<hex-digest>`
|
||||
@@ -72,7 +72,7 @@ This boundary helps LLM-based agents distinguish trusted instructions from untru
|
||||
|
||||
## Agent token logging
|
||||
|
||||
On authentication failure, only a short prefix of the presented token is logged to aid debugging. Full tokens are never written to logs.
|
||||
On authentication failure, logs include request context only. Token values and token prefixes are not written to logs.
|
||||
|
||||
## Cross-tenant isolation
|
||||
|
||||
|
||||
Reference in New Issue
Block a user