Skip to content

fix(stream): cap concurrent SSE streams per tenant (S-5) - #216

Merged
brownjuly2003-code merged 2 commits into
mainfrom
fix/s5-sse-stream-cap
Jul 20, 2026
Merged

fix(stream): cap concurrent SSE streams per tenant (S-5)#216
brownjuly2003-code merged 2 commits into
mainfrom
fix/s5-sse-stream-cap

Conversation

@brownjuly2003-code

Copy link
Copy Markdown
Owner

Closes security pre-audit S-5 (P3, SSE resource exhaustion): each open /v1/stream/events connection runs a journal scan every second, but the rate limiter only charges the one request that opened it — a 60-rpm tenant could sustain ~60 growing scanners.

Semantics (per product decision): the (N+1)-th concurrent stream for a tenant gets an immediate 429 + Retry-After; already-open streams are never dropped. Cap defaults to 5 per tenant, tunable via AGENTFLOW_SSE_MAX_STREAMS_PER_TENANT.

  • Per-process counter on app.state (same accepted per-process trade-off as the S-7 failed-auth throttle: N replicas = N× effective cap, still bounds the growth the finding describes).
  • Slot release is idempotent and wired to both the generator's finally (normal close / client disconnect via aclose()) and the response's background task (covers a generator the server never iterates).
  • Auth disabled (tenant_id=None) → cap skipped, matching the /v1/batch S-4 convention.
  • Check→claim has no await in between — atomic on the event loop.

Tests: 5 new unit tests (reject at cap + no slot consumed by the rejected request; per-tenant isolation; auth-disabled bypass; env override; background-task release path + idempotency). Full unit suite 2028 passed locally; ruff check/format clean.

🤖 Generated with Claude Code

Each open /v1/stream/events connection runs a journal scan every second
but the rate limiter only charges the request that opened it, so a
tenant's scan load grew without bound (security pre-audit S-5). New
per-tenant cap on concurrent streams: the (N+1)-th connection is
rejected with 429 + Retry-After before the stream opens; already-open
streams are never dropped. Cap defaults to 5 and is tunable via
AGENTFLOW_SSE_MAX_STREAMS_PER_TENANT.

The counter is per process, like the failed-auth throttle (S-7 accepted
risk): N replicas give an NxCap effective cap, which still bounds the
growth the finding is about. Slot release is idempotent and wired to
both the generator's finally (normal close/disconnect) and the response
background task (generator never iterated). With auth disabled
(tenant_id=None) the cap is skipped, matching the /v1/batch S-4
convention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

DORA Metrics

  • Window: last 30 days
  • Branch: main
  • Deployment frequency: 113 total / 26.37 per week
  • Lead time for changes: avg 0.6h / median 0.0h
  • Change failure rate: 76.99% (87/113)
  • MTTR: 20.13h across 5 incident(s)

@brownjuly2003-code
brownjuly2003-code merged commit ae73845 into main Jul 20, 2026
27 checks passed
@brownjuly2003-code
brownjuly2003-code deleted the fix/s5-sse-stream-cap branch July 20, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants