Describe the bug
In HTTP mode the lockdown repository access cache is shared across callers. Entries are keyed on owner/repo alone inside a process-wide cache2go table, so a trust decision computed under one caller's credentials — repository visibility, author push access — can be served to a different caller whose own credentials were never checked.
Stdio mode is not affected: it runs a single cache for one process-wide identity.
Affected version
Current main as of 2026-08-19.
Steps to reproduce the behavior
- Run the server in HTTP mode.
- Issue a lockdown-gated request for owner/repo as identity A.
- Issue the same request for the same owner/repo as identity B.
- Observe that B is served A's cached trust decision, without B's own credentials being used.
Expected vs actual behavior
Expected: a cached trust decision is scoped to the identity whose credentials produced it.
Actual: decisions are shared process-wide under a bare owner/repo key.
Note on cache freshness
The cache's sliding/idle expiry is not a defect. cache2go refreshes an entry's TTL on access by design, and this cache has always relied on that — the hand-rolled cache it replaced kept the same semantics. A hot repository stays cached; idle entries are reclaimed.
Whether repo-access decisions should additionally carry a fixed maximum age is a separate freshness question rather than an isolation one, and is deliberately not part of this fix. A fixed age makes every hot repository refetch on a timer, so it would want singleflight (or a shared store such as Redis) and possibly stale-while-revalidate to avoid a refetch stampede. Worth revisiting on its own terms.
Logs
N/A
Describe the bug
In HTTP mode the lockdown repository access cache is shared across callers. Entries are keyed on owner/repo alone inside a process-wide cache2go table, so a trust decision computed under one caller's credentials — repository visibility, author push access — can be served to a different caller whose own credentials were never checked.
Stdio mode is not affected: it runs a single cache for one process-wide identity.
Affected version
Current main as of 2026-08-19.
Steps to reproduce the behavior
Expected vs actual behavior
Expected: a cached trust decision is scoped to the identity whose credentials produced it.
Actual: decisions are shared process-wide under a bare owner/repo key.
Note on cache freshness
The cache's sliding/idle expiry is not a defect. cache2go refreshes an entry's TTL on access by design, and this cache has always relied on that — the hand-rolled cache it replaced kept the same semantics. A hot repository stays cached; idle entries are reclaimed.
Whether repo-access decisions should additionally carry a fixed maximum age is a separate freshness question rather than an isolation one, and is deliberately not part of this fix. A fixed age makes every hot repository refetch on a timer, so it would want singleflight (or a shared store such as Redis) and possibly stale-while-revalidate to avoid a refetch stampede. Worth revisiting on its own terms.
Logs
N/A