| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
…tity The repo-access cache used by lockdown mode relied on cache2go's sliding expiry: every read extends an entry's life, so a frequently-accessed entry could keep a stale trust decision (e.g. revoked push access) alive indefinitely instead of refreshing after its TTL. Separately, cache2go.Cache(name) returns a process-wide singleton table keyed by name. In HTTP mode, RequestDeps.GetRepoAccessCache built a new RepoAccessCache per request but always reused the same default-named table, so trust decisions computed under one caller's credentials could be served to a different caller for the same owner/repo, without ever validating the second caller's own access. Fixes: - Track each cache entry's original creation time and bound its maximum age from that fixed point, not from last access, so entries are refreshed after a fixed TTL regardless of read frequency. - Add lockdown.CacheNameForIdentity, which derives a stable, hashed cache-table name from a request identity (e.g. auth token). Two calls for the same identity return the same name (reusing a warm cache across a session's repeated requests); different identities always get different names (no shared cache state). - RequestDeps.GetRepoAccessCache now scopes each request's cache to the requesting token's identity via CacheNameForIdentity, closing the cross-identity leak in HTTP/multi-tenant deployments. Stdio mode is unaffected: it constructs a single RepoAccessCache for the whole process lifetime, as before. Tests added: - TestRepoAccessCacheBoundedExpiryIgnoresRepeatedAccess and TestRepoAccessCacheNewUserDoesNotResetEntryAge exercise bounded expiry deterministically via an injectable clock (no sleeps). - TestCacheNameForIdentity and TestRepoAccessCacheIdentityScopedNamesPreventCrossIdentityLeakage cover the naming helper and cross-identity isolation at the lockdown package level. - TestGetRepoAccessCacheIsolatesTrustDecisionsPerIdentity in pkg/github mirrors the HTTP server's exact construction pattern end-to-end and fails without the dependencies.go fix. Fixes #3107 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Bounds lockdown cache expiry and isolates trust decisions by request identity.
Changes:
| File | Description |
|---|---|
| pkg/lockdown/lockdown.go | Implements bounded expiry and identity cache naming. |
| pkg/lockdown/lockdown_test.go | Tests expiry and identity isolation. |
| pkg/github/dependencies.go | Scopes request caches by token. |
| pkg/github/dependencies_test.go | Tests request-level isolation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Sorry, something went wrong.
Isolating identities by deriving a cache2go table name per token grew a process-wide registry that is never reclaimed: cache2go creates each named table on first use and never evicts it, so every distinct bearer token — including invalid ones, since the table was built before GitHub validated the token — permanently added a table. Keep a single cache table and scope entries instead. WithIdentity stores a SHA-256 digest of the identity and prefixes each entry key with it, so different identities still cannot observe each other's trust decisions, while per-identity state is reclaimed by the table's ordinary TTL cleanup. WithCacheName stays for tenant/test isolation, with docs warning against deriving names from request data. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The golangci-lint action downloads a JSON schema from golangci-lint.run on every run to verify .golangci.yml. A blip reaching that host fails the job before any linter runs, as it did on this PR. Linting should depend only on the checked-out code, which is also what script/lint does locally.
…ed-expiry-for-lockdown-c56a37
This reverts commit d7d8dd2. The lint job failed on a transient timeout fetching the golangci-lint config schema, which is a CI infrastructure concern rather than a defect in this change. Disabling schema verification to work around it does not belong in a cache-hardening PR: it weakens a check for every future run, and its root cause is out of scope here. Leaving CI configuration untouched keeps this PR to the lockdown cache redesign. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The cache changes carried explanatory comments that restated the code or narrated what each step did. Drop them and keep only what the code cannot express: that cache2go never reclaims a named table, that its own expiry slides on every read, that createdAt survives entry updates, and that RepoAccessOpts is shared across requests. Exported options keep a short doc comment. Comment-only; no behavior change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…-pr-3113-cache-isolation
The cache's idle/sliding TTL is cache2go's documented behaviour and was deliberate in both the original hand-rolled cache and the cache2go migration: a hot repo keeps serving from cache and only idle entries are reclaimed. Replacing it with a fixed max age traded that away for a periodic refetch on every hot repo, which is a freshness change rather than the isolation fix this issue is about. Remove createdAt, the injected clock, entryExpired, the createdAt preservation on entry updates, and the tests that only existed to prove bounded non-sliding expiry. Restore the original sliding semantics. Keep the per-caller isolation, which is the actual defect: entries were keyed on owner/repo alone in a process-wide table, so a trust decision computed under one caller's credentials could be served to another caller whose own credentials were never checked. Entry keys now carry a SHA-256 digest of the request identity, inside a single bounded table. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| Back | FazBrowse Home | New Git URL |
Problem
In HTTP mode, the pkg/lockdown repo-access cache was shared across callers.
RequestDeps.GetRepoAccessCache builds a fresh *lockdown.RepoAccessCache per request, but entries were keyed on owner/repo alone inside a process-wide cache2go table. A trust decision computed under one caller's credentials — is this repo private, does this author have push access — could therefore be served to a completely different caller, without that second caller's own credentials ever being checked.
I verified this concretely with a throwaway reproduction before fixing it: a "bob" request was served "alice"'s cached decision, and bob's own REST client was never invoked.
Stdio mode was never affected. It builds one RepoAccessCache for a single process-wide identity.
Fix
Isolation lives in the entry key, not in a cache table per identity. cache2go.Cache(name) never reclaims a named table, so deriving a table name from request data would build a per-token registry that only ever grows. Key scoping keeps every identity in one bounded table, where ordinary idle-TTL cleanup reclaims entries as it always has.
Expiry semantics are deliberately unchanged. cache2go refreshes an entry's TTL on access, so hot repos stay cached and idle entries fall out — the documented behaviour this cache has relied on, matching the hand-rolled cache it replaced. Whether repo-access decisions should also carry a freshness bound is a separate question: a fixed max age would make every hot repo refetch on a timer, so it wants singleflight or a shared store first. Out of scope here.
Tests
I confirmed each of these fails against the pre-fix code and passes with the fix. script/lint and script/test (go test -race ./...) both pass.
Fixes #3107
Acknowledgments
Thanks @YuvalElbar6 for the report that led to this hardening.