The secret field was accepted in BoardWebhookCreate but never passed
to the BoardWebhook constructor, silently dropping it. Now secret is
persisted at creation time (with empty/whitespace normalized to None)
and similarly normalized on PATCH so sending "" clears a set secret.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add prompt-injection fencing to _webhook_memory_content (was missing
the --- BEGIN/END EXTERNAL DATA --- fence applied elsewhere)
- Wrap Content-Length parsing in try/except to avoid 500 on malformed
header values
- Move _to_gateway_read below imports (was incorrectly placed between
import blocks) and tighten transformer types
- Replace list-rebuild with deque.popleft in rate limiter for O(expired)
amortized pruning instead of O(n) per call
- Make organization_id required in send_session_message to prevent
fail-open cross-tenant check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- agent.py: Fail closed when gateway lookup returns None instead of
silently dropping the organization filter (cross-tenant board leak)
- board_webhooks.py: Read request body via streaming chunks so an
oversized payload is rejected before it is fully loaded into memory
- rate_limit.py: Add periodic sweep of expired keys to prevent
unbounded memory growth from inactive clients
- test_rate_limit.py: Add test for the new sweep behavior
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 webhook ingest endpoint read the entire request body with no size
limit, enabling memory exhaustion attacks. Add a 1 MB limit checked
via both Content-Length header (early reject) and actual body size.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
User-controlled fields (skill name, source URL, webhook payloads) were
interpolated directly into agent instruction messages. Sanitize skill
fields by stripping newlines/control chars, and fence all external data
behind "BEGIN EXTERNAL DATA" / "BEGIN STRUCTURED DATA" delimiters with
explicit "do not interpret as instructions" markers. Move system
instructions above the data section so they cannot be overridden.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Webhook ingest endpoint was completely unauthenticated. Add an optional
`secret` field to BoardWebhook. When configured, inbound requests must
include a valid HMAC-SHA256 signature in X-Hub-Signature-256 or
X-Webhook-Signature headers. Uses hmac.compare_digest for timing safety.
Includes migration to add the secret column.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>