The receiver rate limit is per-URL-path, so an attacker gets unlimited aggregate rate against /webhook/* #139
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by the integration review of #111. Blocks the 1.0.0 tag: this is the milestone's headline internet-facing control and it does not deliver its stated guarantee.
internal/middleware/ratelimit.go:258composes the limiter key as (client IP,httprate.KeyByEndpoint). In httprate v0.15.0KeyByEndpointis exactlyreturn r.URL.Path, nil.The chi pattern
/webhook/{uuid}matches any single segment, so a client mints a fresh 120/min bucket per invented path string. Aggregate rate against the route is therefore unlimited. Every one of those requests is admitted intoHandleWebhook, which logs an INFO line containing the attacker-controlled path (internal/handlers/webhook.go:42) and performs a database lookup (:48) before returning 404.Net effect: a single IP drives unbounded DB queries and unbounded log volume against the only unauthenticated, internet-exposed endpoint — the exact property
internal/config/config.go:38-41claims to provide and that #64 existed to obtain.Neither unit review could have caught this. #64 landed the per-endpoint key before the trusted-proxy work existed, and #88 rewrote only the IP half of the key.
Definition of done
/webhook/*by varying the path. The usual shape is an IP-only aggregate limiter over the whole/webhook/*group, kept ALONGSIDE the existing per-entrypoint limiter rather than replacing it — the per-entrypoint limit is still wanted, it is just not sufficient alone.internal/handlers/webhook.go:42should drop to DEBUG or be rate-limited; state what you chose./webhook/is throttled in aggregate. Mutation-verify it: it must fail against the current per-path key.Implementation requirements
next, PR based onnext, single commit, title ending(closes #N).TODO.md.make checkplus the Docker lint path with the cache defeated (#119).PR: #143
Added an IP-only aggregate limiter in front of the existing per-entrypoint one over
/webhook/*, at10 * RECEIVER_RATE_LIMITper minute (1200/min default). Per-entrypoint limit and its tests unchanged. Derived from the existing setting, sointernal/config/config.gois untouched.Logging: moved the INFO line in
HandleWebhookbelow the entrypoint lookup rather than dropping it to DEBUG. Unknown entrypoints now produce only the pre-existing DEBUG line; the request is still in the access log.Verified:
TestReceiverRateLimit_LimitsAggregateAcrossInventedPathsfails against the pre-fix per-path key (mutation run: only that test fails, all six existing receiver/key tests still pass).make checkexit 0,0 issues.;docker build --no-cache-filter=lint,builderexit 0 withmake lintandmake testboth executing in-container.