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:
Abhimanyu Saharan
2026-03-07 23:40:50 +05:30
parent 355bed1b40
commit fb8a932923
11 changed files with 268 additions and 42 deletions

View File

@@ -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.

View File

@@ -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 routes 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 routes 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`.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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