Repository navigation
feat(security): origin-side rate limiter — PR1 (shared-store limiter + tiering, flag-off) - #2263
Merged
Merged
Conversation
Adds a fail-open, DB-flag-gated (RATE_LIMIT_ENABLED / ImsConfig flag.ratelimit, default OFF) rate limiter on the anonymous/expensive surface, backed by the shared cds-caching store for cross-instance counters — origin-side defense-in- depth assuming Akamai Bot Manager removal. - shared-limiter.js: fixed-window counter keyed by (routeClass,tier,clientKey), window governed by windowStart (TTL is GC-only); fail-open to a per-instance in-memory Map on any store fault. Non-atomic RMW under-counts (looser, never tighter) — documented. - agent-identity.js: tier resolver (trusted=valid HMAC-SHA256 w/ ±5min replay window, secret via credstore; auth=already-present bearer/api-key; anon=IP). Identity is a tier, not a gate — unknown callers are limited, never blocked. - rate-limit-settings.js: ImsConfig-backed numeric thresholds, 5s cache, defaults on any read failure. - register.js: middleware + metrics counters + debounced RateLimitAbuse alert; mounted in bootstrap on /content,/build,/graph,/mcp*,/a2a,etc. Legacy always- on /search limiter left intact (not superseded while this flag defaults OFF). - registry RATE_LIMIT_ENABLED entry; package.json alerts eventType RateLimitAbuse. Ships flag-OFF. srv-qa cp-list unaffected (not reachable from content-store.js).
jung-thomas
marked this pull request as ready for review
September 12, 2026 17:24
This was referenced Sep 12, 2026
Closed
feat(security): WAF-equivalent input validation + GraphQL depth/complexity limit — PR3 (#2270)
#2273
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
First PR of the origin-side abuse-protection series (plan: assume Akamai Bot Manager removal, since its UA/behavioral checks 403 our agentic MCP/A2A clients). Moves the protection those edge rules provided onto our side, as defense-in-depth. Ships flag-OFF (
RATE_LIMIT_ENABLED/ ImsConfigflag.ratelimit, default false) — no behavior change until enabled on DEV.Scope (PR1 of 4)
Cross-instance rate limiting on the anonymous/expensive surface + a zero-consumer-setup trusted-caller tier system + observability. (PR2 Fast-Purge, PR3 input validation, PR4 load-shedding to follow.)
New
srv/lib/rate-limit/shared-limiter.js— fixed-window counter keyed by(routeClass, tier, clientKey), backed by the sharedcds-cachingstore (cross-instance in hybrid/prod). Window governed bywindowStartinside the value; TTL is GC-only (a mid-window write can't slide the window). Fail-open: any store fault → per-instance in-memory Map; if that throws, request is allowed. Non-atomic RMW can under-count → effective limit is looser, never tighter (documented; not for exactness).srv/lib/rate-limit/agent-identity.js—resolveTier(req):trusted— valid HMAC-SHA256 header (keyId\nmethod\npath\nts, ±5-min replay window), secret via credstore (AGENT_HMAC_SECRET, rotatable in/admin-ui/#secrets).auth— a bearer token / api-key is already present (keyed off a hash — zero new setup).anon— everyone else (IP floor).srv/lib/runtime-config/rate-limit-settings.js— ImsConfig-backed numeric thresholds (window, per-routeClass limits, tier multipliers, abuse threshold), 5s cache, defaults on any failure. Env-free.srv/lib/rate-limit/register.js— middleware +metricscounters (ratelimit.hits,ratelimit.blocked[route=,tier=]) + debouncedRateLimitAbusealert. Mounted incds.on('bootstrap')on/content,/build,/graph,/feedback,/homepage,/api, and the agentic/mcp*,/a2a,/chat/stream,/graphql/public.Modified
srv/server.js— callregisterRateLimit(app)in bootstrap. Legacy always-on/searchlimiter left intact (intentionally not superseded while this flag defaults OFF — avoids dropping an always-on floor).srv/lib/feature-flags/registry.js—RATE_LIMIT_ENABLEDentry (kind:db, categorySecurity, dev-only).package.json—RateLimitAbusealert eventType (Tests
test/unit/rate-limit.test.js— 15 tests: window allow/block/reset + retryAfter, fail-open fallback counting, key isolation, IP derivation, tier resolution (anon/auth/trusted), HMAC replay + tamper + wrong-path rejection, config defaulting. Registry drift test passes. Full unit suite: only pre-existing env failures (missing island deps / srv-qa bundle / a JWT-exp flake), none related.Notes
cplist unaffected — new modules aren't reachable fromcontent-store.js.setFeatureFlag('RATE_LIMIT_ENABLED', true), observeratelimit.*metrics → then PROD.