fix(visibility): route is_owner through did_matches to fix bare-key mirror lockout (#153)#156
fix(visibility): route is_owner through did_matches to fix bare-key mirror lockout (#153)#156Gravirei wants to merge 2 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe ChangesOwner DID Matching Fix
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant visibility_check
participant is_owner
participant did_matches
Caller->>visibility_check: request repo read
visibility_check->>is_owner: check ownership(owner_did, caller)
is_owner->>did_matches: compare(owner_did, caller)
did_matches-->>is_owner: match result (representation-agnostic)
is_owner-->>visibility_check: allow/deny
visibility_check-->>Caller: Allow or Deny
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
jatmn
left a comment
There was a problem hiding this comment.
Thanks for the contribution. I do not see any actionable issues from my review.
@kevincodex1 LGTM
beardthelion
left a comment
There was a problem hiding this comment.
Verified the security-critical property by execution, not inspection: routing is_owner through did_matches widens who counts as owner by exactly one case, the bare-key mirror owner matched by the same key's full did:key: caller (the #153 fix). Reverting is_owner makes bare_owner_full_caller_allows_owner fail, so it's load-bearing. Cross-method stays denied because only did:key: is stripped, so did:gitlawb:zX keeps its method and can't equal a bare id. Ran the full owner/DID truth table and the node suite (424 green). The short-circuit isn't exploitable by the widening: the newly-matched caller is the Ed25519-verified owner, and this converges the read gate onto the same did:key-aware normalization the mutation gate and SQL owner filter already use, so it reduces gate/query skew.
Findings
- [P3] Pin the empty-DID invariant on the owner gate
crates/gitlawb-node/src/api/mod.rs:70
did_matches("", "did:key:")anddid_matches("", "")return true. Unreachable today (the read-gate caller is always a verifieddid:keyor None, and an empty-owner mirror slug is quarantined before the gate) and it predates this PR sincerequire_repo_owneralready usesdid_matches. But this change makesdid_matchesload-bearing on the read path too, so a one-line test pinning "the gate caller is a validateddid:keyor None" is cheap insurance. Optional.
Concur with the approval; the note is defense-in-depth, not a blocker.
beardthelion
left a comment
There was a problem hiding this comment.
Re-approving on 73bd7a4. My earlier approval was on dce4330; the only change since is the added empty_did_matches test, so I re-ran the owner-gate matrix by execution against the current head.
Routing is_owner through did_matches fixes the #153 mirror-owner lockout: a bare owner key with a full did:key caller now resolves to Allow, and reverting is_owner reproduces the lockout. The security-critical negatives hold under mutation. Cross-method (did:gitlawb: / did:web: against a bare key) denies, and a trailing-segment over-widen makes cross_method_did_denied_with_bare_owner go red, so that guard is load-bearing. A distinct full key denies. The empty-DID match is unreachable: an empty or bare did:key: id never resolves through to_verifying_key, so the caller reaching the owner arm is always a verified did:key or None. Quarantine still drops before the owner short-circuit (authorize_repo_read and the git-serve paths), so a newly bare-key-matched mirror cannot surface a hidden repo.
Findings
- [P3] Pin the fail-safe negative in
empty_did_matches.crates/gitlawb-node/src/api/mod.rs:118. The test pins the degenerate positives (did_matches("",""),did_matches("","did:key:")) but not the security-relevant negative: an emptyowner_didmust never match a real key. It is unreachable today, so this is not a live issue, but a future over-widening refactor would keep the test green. I confirmed astrip + starts_withmatcher leaves this test passing, while addingassert!(!did_matches("", "did:key:z6MkReal"))turns it red. Consider adding that negative, and optionally an empty-id guard insidedid_matchesso the invariant lives at the matcher (the #153 tests still pass with the guard).
Optional and non-blocking. @kevincodex1 good to merge.
|
please fix conflicts |
73bd7a4 to
0be2f18
Compare
beardthelion
left a comment
There was a problem hiding this comment.
Verified by execution that this is behaviorally a no-op on the current base, not a live lockout fix. Routing is_owner through did_matches is identical to the two-side normalize_owner_key already on main after #157: I ran the owner gate across the clone/fetch path, list_repos, and subtree withholding, as the owner, as a cross-method did:gitlawb: caller, and anonymous, against both the did_matches body and the base body, with identical Allow/Deny outcomes. The code is safe and consolidating onto the shared matcher is a genuine improvement. The changes I'm asking for are to the framing and one test comment so the record stays accurate.
Findings
-
[P2] Fix the false "previously denied (lockout)" comment; the case already passes on the current base.
crates/gitlawb-node/src/visibility.rs:434
The lockout lived on the pre-#157 single-side is_owner. On this PR's base (26bc3f6, which already normalizes both sides) is_owner("z6MkOwner", "did:key:z6MkOwner") returns Allow, so reverting is_owner to the base body leaves both new tests green. Keep them as forward guards against a revert to single-side, but correct the comment (and the "fixes #153 lockout" assertion message on line 438) to say that, rather than claiming this diff lifts a lockout. -
[P3] Retitle from fix(...lockout) to refactor(...) so the changelog is accurate.
crates/gitlawb-node/src/visibility.rs:32
Since #157 already resolved the read lockout, a fix title records a changelog Bug Fix for a behavioral no-op. Suggest: refactor(visibility): route is_owner through the shared did_matches matcher (#153). It still legitimately closes #153, which also asked for the stale did_matches doc comment to be corrected, and this does that.
Stale approval on an earlier head; superseded by my review on 0be2f18.
Summary
Switch
is_ownerfromnormalize_owner_key(single-side normalization) todid_matches(two-side normalization with cross-method rejection) so a mirror-only repo owner authenticated with their fulldid:key:is not denied read to their own repo.Motivation & context
Closes #153
visibility::is_ownerusednormalize_owner_key(owner_did)which only stripsdid:key:off the owner side. When a mirror row storesowner_didas bare short key and the caller authenticates with fulldid:key:, the comparison fails and the owner is locked out.The old doc comment warned that
did_matches"would let a non-key canonical row bypass the #124 visibility gate" — butdid_matchesalready rejects cross-method collisions (did:key:X!=did:gitlawb:X), so that reasoning is stale.Kind of change
What changed
crates/gitlawb-node/src/visibility.rs— Replacedis_ownerbody withcrate::api::did_matches(owner_did, caller)and updated the doc comment to reflect current semantics.bare_owner_full_caller_allows_owner(regression for the lockout) andcross_method_did_denied_with_bare_owner(cross-method bypass guard).How a reviewer can verify
cargo test -p gitlawb-node -- visibility::testsProtocol & signing impact
did:key, Ed25519 / RFC 9421 signatures, UCAN, ref certs, or P2P wire formatsSummary by CodeRabbit
did:keyrepresentations are recognized consistently.