Demo (not for merge): is_public enforcement on the git read path — grounds the #18 design decisions#24
Open
beardthelion wants to merge 2 commits into
Open
Conversation
…e-read) `repos.is_public` is stored on every repo but never checked when serving clone/fetch, so private repos are world-readable over git smart-HTTP. This wires read enforcement for the whole-repo (scope=`/`) case — the literal "Implement private-read enforcement" short-term roadmap item. node: - api/repos.rs: add `authorize_read()`. Public repos stay open; private repos require the caller's authenticated DID to match the owner. Returns 404 (not 403) on denial so a private repo's existence does not leak. Mirrors the owner-match idiom in api/protect.rs. Gates `git_upload_pack` and the `git-upload-pack` branch of `git_info_refs`; the receive-pack (push) handshake is left untouched (authorized separately on the POST). - server.rs: move `info/refs` into `git_read_routes` and layer `optional_signature`, so an `AuthenticatedDid` is available on reads when a signature is present, without breaking anonymous clone of public repos. client (git-remote-gitlawb): - main.rs: sign the GET ref-advertisement, and broaden POST signing from push-only to fetch too, so a `git clone` of a private repo can authenticate. Out of scope (deliberately): path/package-scoped visibility, the gossip/sync and GraphQL/IPFS read surfaces, and the never-replicate-vs-fetch-denied question. See proposal discussion. Verification: `cargo check --workspace` clean (0 errors, 0 warnings); git-remote-gitlawb unit tests 6/6 pass.
Six unit tests over the full visibility matrix for `authorize_read`:
public → allow (anonymous and any DID); private → allow owner; private → deny
anonymous; private → deny non-owner. Plus a no-leak contract test asserting the
denial payload is byte-identical to a missing repo (`RepoNotFound("owner/secret")`),
so a private repo's existence cannot be inferred from the response.
Red-green verified: disabling the enforcement check fails exactly the three
denial tests and leaves the three allow tests green.
Contributor
|
copy |
Author
|
No rush on the build-out. Just the one fork when you have a sec: for gated content, should a peer lacking authorization (a) never receive the objects in gossip/IPFS at all, or (b) receive hashes/metadata and get capability-required on content fetch? Even a one-letter answer unblocks the path-scoping work. Also fine to hear if it is already claimed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Not a merge candidate — a working demonstration to make the open design decisions in #18 concrete instead of theoretical. It wires the
is_publicflag the node already stores but never checks on the git read path, and runs end to end against a local node. Please read the "What this does NOT do" section before reviewing as a feature.What works
authorize_readgates the git smart-HTTP read path (info/refs?service=git-upload-packandgit-upload-pack) on the DID that signed the request (verified upstream byoptional_signature). Unauthorized reads return 404, not 403, byte-identical to a missing repo.404(identical to missing repo)200404200(unchanged)A 6-case truth-table unit test pins this, including an assertion that the private-denial payload is indistinguishable from a missing repo. (
cargo fmt/clippyclean, rebased onmain, no conflicts.)What this does NOT do (why it is a demo, not a feature)
These are deliberate, and several are genuinely maintainer decisions, not things I should pick unilaterally:
authorize_readis wired into the two HTTP handlers only. The IPFS read path (api/ipfs.rs,ipfs_pin.rs) and gossip replication still serve the objects ungated. So a repo marked private here is still readable through those paths. This is the single biggest reason it must not be mistaken for real privacy, and closing those surfaces is the same decision as the replication question below.caller_did == owner_did. There is no way to grant another DID read access, so this expresses "private to me alone," not the capability-based reader sets Path/package-scoped visibility — make "private" a property of a subtree, not a whole repo #18 is actually about.is_publicboolean; it does not add a path-scoped data model. Path/package-scoped visibility — make "private" a property of a subtree, not a whole repo #18 argues the data model should carry path scope from day one to avoid a later migration — this demo deliberately does not, pending your call on that.gl. The create API acceptsis_public:false, but everyglpath hard-codestrueand there is no endpoint to change visibility after creation. The demo was exercised by hand-crafting the create call.The decision that unblocks the real feature
@kevincodex1 the fork that gates everything (path-scoping, and surfaces #1 above) is replication of gated content: should a peer lacking authorization (a) never receive the objects in gossip/IPFS at all, or (b) receive hashes/metadata and get capability-required on content fetch? (a) is stricter; (b) is friendlier to the durability goal but leaks existence. The byte-identical-404 in this demo is one expression of (a) on the HTTP path; if you want (b), even this behavior changes. Pick a direction and I will build the path-scoping layer on top.
Also: is private-read / authorization enforcement already claimed or in progress? I would rather build on it than collide.