You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
POST /state accepts an unlimited number of branchPatterns, so one request can pin a CPU for minutes and keeps running after the client disconnects #116
BranchStateHandler.TryParsePatterns (GitBranchStateCache/Endpoints/BranchStateHandler.cs, at 61cc96a) caps each pattern's length at BranchPattern.MaximumLength, which is 512. It never caps how many patterns a request may send.
paths does have a limit: MaxPathsPerRequest, enforced at L149-154 with a 400. branchPatterns has nothing equivalent, and the service doesn't lower Kestrel's 30 MB default MaxRequestBodySize.
The patterns are then evaluated by RefResolver.Match (Refs/RefResolver.cs:100-115). That is a synchronous branches × patterns scan with no cancellation token, so it keeps running after the caller hangs up.
Failure scenario
An admitted caller sends one POST /state with about 58,000 patterns of the form "*" + 510×"a" + "b". This needs only read access to one allow-listed repository, and a buggy client could do it by accident. The request fits in the default 30 MB body limit.
Measured with the repository's own BranchPattern: one such pattern against 1,000 60-character branch names takes about 2 ms. One request therefore costs about 116 CPU-seconds per 1,000 mirror branches. It runs on a request thread and can't be cancelled, so a handful of concurrent requests starve every other client's 30-second heartbeat.
GET /branches?pattern= is bounded in practice by Kestrel's request-line limit, so POST /state is the open path.
This is a different code path from #111, which covers the allow-list regex timing out on a long path.
Suggested fix
Add MaxBranchPatternsPerRequest to GitBranchStateCacheOptions, defaulting to something like 256, and validate it in GitBranchStateCacheOptionsValidator.
Reject a request over the limit with a 400 in TryParsePatterns, so both /state and /branches are covered. Mirror the wording of the paths message.
Optionally pass the request's CancellationToken into RefResolver.Match and check it between branches.
Acceptance criteria
A /state request with more patterns than the configured limit returns 400 before any mirror fetch or ref listing.
The new option is validated as greater than zero and documented next to MaxPathsPerRequest.
Category: Bug (missing request limit, a resource-exhaustion path)
Priority: High. Any admitted caller with read access to one allow-listed repository, or a buggy client, can tie up request threads for minutes with one request. The work can't be cancelled, so a few concurrent requests starve every other client's 30 s heartbeat.
Area / suggested assignment: Endpoints / request validation (BranchStateHandler.TryParsePatterns, GitBranchStateCacheOptions and its validator), plus RefResolver.Match for the optional cancellation work.
Next steps: Add MaxBranchPatternsPerRequest (default around 256, validated > 0) and return a 400 in TryParsePatterns before any mirror fetch, so /state and /branches are both covered. Threading the request CancellationToken through RefResolver.Match is a worthwhile follow-on, but not required to close this.
Activity
matt-edmondson commentedon Nov 3, 2026
Triage
BranchStateHandler.TryParsePatterns,GitBranchStateCacheOptionsand its validator), plusRefResolver.Matchfor the optional cancellation work.*in one segment lets an unauthenticated long path time out the regex and get a 500 #111 (allow-list regex timeout, which PR Match repository patterns in linear time, and refuse a timed-out match [patch] #113 addresses) and from Branch patterns?and[...]disagree withgit branch --liston non-ASCII names:origin/?matchesorigin/éhere but not in git #115 (non-ASCII?/[...]semantics), though it touches the same pattern code.Next steps: Add
MaxBranchPatternsPerRequest(default around 256, validated > 0) and return a 400 inTryParsePatternsbefore any mirror fetch, so/stateand/branchesare both covered. Threading the requestCancellationTokenthroughRefResolver.Matchis a worthwhile follow-on, but not required to close this.Generated by Claude Code