fix: enforce IP bucket before identity rate limits#705
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
agent-news | 819795e | May 01 2026, 05:00 AM |
|
Preview deployed: https://agent-news-staging.hosting-962.workers.dev This preview uses sample data — beats, signals, and streaks are seeded automatically. |
There was a problem hiding this comment.
Pull request overview
This PR tightens the rate-limit middleware’s safety properties by ensuring every counted request hits an IP-based bucket before any identity-based bucket, preventing spoofed identity headers from bypassing an exhausted IP quota. It also makes identity bucketing explicitly opt-in per route and hardens behavior when Cloudflare rate-limit bindings are misconfigured.
Changes:
- Enforce IP bucket checks before optional identity bucket checks in
checkRateLimit, and only check identity whenidentityHeaderis explicitly set. - Fail closed (503) in staging/production when a configured rate-limit binding is missing.
- Add a regression test ensuring spoofed
X-BTC-Addresscannot bypass an exhausted IP bucket; update publisher/editorial routes to opt into identity bucketing.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/middleware/rate-limit.ts | Reworks bucket-check order (IP first, optional identity second) and changes missing-binding behavior to fail closed in staging/prod. |
| src/routes/signal-review.ts | Opts publisher signal-review rate limiting into identity bucketing via X-BTC-Address. |
| src/routes/classified-review.ts | Opts publisher classified-review rate limiting into identity bucketing via X-BTC-Address. |
| src/routes/brief-compile.ts | Opts brief compile rate limiting into identity bucketing via X-BTC-Address. |
| src/lib/constants.ts | Updates rate-limit comments to reflect Cloudflare bindings as the source of enforcement. |
| src/tests/rate-limit-skip.test.ts | Adds regression coverage for spoofed identity headers not bypassing exhausted IP bucket. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return c.json( | ||
| { error: "Rate limit service unavailable", retry_after: config.periodSeconds }, | ||
| 503 | ||
| ); |
There was a problem hiding this comment.
When a rate-limit binding is missing in staging/production, the 503 response includes retry_after in the JSON body but does not set a Retry-After header. This is inconsistent with the 429 responses from this middleware and with other retryable 503 responses in the codebase, and makes it harder for clients/proxies to implement standard backoff behavior. Consider also setting Retry-After on this 503 path (using config.periodSeconds).
| return c.json( | |
| { error: "Rate limit service unavailable", retry_after: config.periodSeconds }, | |
| 503 | |
| ); | |
| const response = c.json( | |
| { error: "Rate limit service unavailable", retry_after: config.periodSeconds }, | |
| 503 | |
| ); | |
| response.headers.set("Retry-After", String(config.periodSeconds)); | |
| return response; |
* fix(agent-resolver): scope NEWS_KV writes to requested addresses resolveAgentNames previously wrote every agent in the bulk fetch page (~1000 entries) to NEWS_KV on every cache miss, even when the caller asked about 1-2 addresses. With staggered 24h TTLs across ~1000 agents, this single call site explains the bulk of the ~12.3K/h remaining NEWS_KV writes after PR #704/#705 removed rate-limit counters (B1 in cloudflare-bill-reduction-tracker-2026-05.md). Keep the bulk fetch as the latency optimization (one HTTP page beats N calls) but scope the KV puts to the originally-requested addresses only; the pre-warm fan-out is dropped. Validation: 24h post-deploy NEWS_KV writes/h via Cloudflare GraphQL, plus smoke on /api/signals, /api/correspondents, /api/init for name resolution still returning expected display names. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(agent-resolver): tighten cache-scoping comment Drop the planning-doc reference per arc0btc review nit on PR #725 — inline comments shouldn't depend on transient planning paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
identityHeadermaxRequests/windowSecondsfields fromRateLimitOptionsX-BTC-Addressvalues cannot bypass an exhausted IP bucketContext
Follow-up for Codex/Copilot review comments posted on merged PR #704.
Validation
npm run typechecknpm test -- src/__tests__/rate-limit-skip.test.ts src/__tests__/signal-read-rate-limit.test.ts src/__tests__/signals.test.tsnpx biome lint src/middleware/rate-limit.ts src/routes/signal-review.ts src/routes/classified-review.ts src/routes/brief-compile.ts src/__tests__/rate-limit-skip.test.ts src/lib/constants.ts && git diff --checknpm run wrangler -- deploy --dry-run --env production