Skip to content

Forward the client's refspec on cached lock walks and probes - #61

Merged
matt-edmondson merged 3 commits into
mainfrom
fix/lock-list-forwards-refspec
Sep 28, 2026
Merged

matt-edmondson merged 3 commits into
mainfrom
fix/lock-list-forwards-refspec

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #47

What was wrong

Lock snapshots are cached per ?refspec= because the locking API treats the ref as an authentication input. The upstream requests behind a snapshot never sent that ref, though. UpstreamRequests.BuildLockListRequest had no refspec parameter, so two requests asked upstream the unscoped question:

  • LockListRefresher.RefreshAsync, the page walk that fills the snapshot.
  • LockListRefresher.ProbeAsync, the one-page check that admits a caller to the snapshot.

With the cache warm, a ref-scoped listing got the no-ref answer. A credential that upstream would refuse for that ref was admitted anyway.

Change

  • BuildLockListRequest takes a refspec and sends it as an escaped refspec= query parameter when it is not null.
  • RefreshAsync and ProbeAsync both pass key.Ref.
  • Credential admission: the issue asked whether CredentialAdmission should key on the ref. I made it do so: ICredentialAdmission.IsAdmitted and Admit take the ref, and LockListService passes key.Ref. Without this, a probe that succeeded under one ref would let the same credential read another ref's snapshot without a probe of its own. That would undo the point of forwarding the ref on the probe.
  • Admission key format: the ref goes last in the admission key, with a length prefix, because it comes straight from a query string and could contain the separator. A missing ref and an empty ref produce different keys.

Tests

  • Integration: LockListCachingTests.CachedListing_ForwardsTheClientsRefspecOnTheWalkAndTheProbe records upstream query strings through a new RecordedRequest.Query on StubUpstream.
    • A cached GET /locks?refspec=refs/heads/main produces a walk and a probe that both carry refspec=refs%2Fheads%2Fmain.
    • A request with no refspec produces upstream requests without one.
  • Admission: CredentialAdmissionTests.IsAdmitted_ADifferentRef_IsNotAdmitted checks that an admission under refs/heads/main does not carry over to another ref, to no ref, or to an empty ref. The existing admission tests now pass a null ref.

Checked both directions:

  • With key.Ref swapped for null in the refresher and the service, the refspec case of the integration test fails. The no-refspec case is a control and passes either way.
  • With the admission key ignoring the ref, all three admission cases fail.
  • With the fix, the full suite passes: 331/331.

🤖 Generated with Claude Code

https://claude.ai/code/session_016wsoxnwaqMuAzvkm2xnzYh


Generated by Claude Code

Lock snapshots are keyed by the ?refspec= a client listed under, because the
locking API treats the ref as an authentication input and upstream may
answer differently per ref. But BuildLockListRequest had no refspec, so both
the walk that fills a snapshot and the one-page probe that admits a caller
to it asked upstream the unscoped question. With the cache warm, a
ref-scoped listing got the no-ref answer, and a credential upstream would
refuse for that ref was admitted anyway.

BuildLockListRequest now takes the refspec and LockListRefresher passes
key.Ref from both RefreshAsync and ProbeAsync. CredentialAdmission keys on
the ref as well, so an admission earned under one ref does not admit the
same credential to another ref's snapshot without its own probe.

Fixes #47

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wsoxnwaqMuAzvkm2xnzYh
…s-refspec

# Conflicts:
#	GitLfsCache.Tests/Integration/LockListCachingTests.cs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015u9u95HXjY2mDagnzGzxK2
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cached lock listings never send the client's refspec upstream, so a snapshot keyed by ref holds (and authorizes against) the no-ref answer

2 participants