Replace sync redis.Redis with redis.asyncio to avoid blocking the
event loop during rate-limit checks. Make RateLimiter.is_allowed async
across both backends and update all call sites to await.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add get_client_ip() helper that inspects Forwarded and X-Forwarded-For
headers only when the direct peer is in TRUSTED_PROXIES (comma-separated
IPs/CIDRs). Replaces raw request.client.host in rate-limit and webhook
source_ip to prevent all traffic collapsing behind a reverse proxy IP.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents normal user requests with Authorization: Bearer from being
throttled by the agent auth limiter in the shared require_user_or_agent
dependency path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
get_agent_auth_context_optional was not rate-limited, allowing
brute-force token guessing via routes that use require_user_or_agent.
Now applies agent_auth_limiter when a token is actually presented.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A 6-character prefix of the token is standard practice for debugging
failed auth attempts and is not a security risk. Restored in both
required and optional auth paths, and removed the now-incorrect test
that asserted its absence.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Agent token auth performed O(n) PBKDF2 operations per request with no
rate limiting, enabling CPU exhaustion attacks. Webhook ingest had no
rate limits either. Add an in-memory token-bucket rate limiter:
- Agent auth: 20 requests/minute per IP
- Webhook ingest: 60 requests/minute per IP
Includes unit tests for the rate limiter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The first 6 characters of invalid agent tokens were logged, leaking
partial credential information. Remove token_prefix from log messages
while preserving the request path for debugging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The optional variant of get_agent_auth_context had accept_authorization=False,
which prevented agents using Authorization: Bearer from passing through the
ACTOR_DEP / BOARD_READ_DEP / TASK_DEP dependency chain.
This caused 401 on any agent route that resolves a board or task via the shared
ACTOR_DEP (e.g. PATCH /agent/boards/{id}/tasks/{id} and
POST /agent/boards/{id}/tasks/{id}/comments), even though the same token worked
fine on routes that use AGENT_CTX_DEP directly (accept_authorization=True).
Fix: set accept_authorization=True in get_agent_auth_context_optional so both
X-Agent-Token and Authorization: Bearer are accepted consistently.
Verified: PATCH and POST /comments now resolve board/task correctly when
Authorization: Bearer is used. No security regression — agent_token_hash
comparison rejects any non-agent bearer tokens.