Summary
The replication/pin path passes every non-blob object through unconditionally, so a withheld subtree's tree object is pinned to IPFS on push (Kubo or Pinata). Once pinned, the CID is announced to the public IPFS DHT and served by bitswap to any peer, disclosing the withheld subtree's child filenames and child oids off-node — bypassing the get_by_cid gate #135 adds. This is the write-side counterpart of #135 (which covers only the on-node GET /ipfs/{cid} read).
Where
crates/gitlawb-node/src/git/visibility_pack.rs:353 — replicable_objects_fail_closed filters !all_blob_oids.contains(oid) || allowed_blobs.contains(oid); trees are never in all_blob_oids, so they always pass. The doc comment states the now-wrong assumption: "commits and trees are structural, never content-withheld."
- Candidate set includes trees:
crates/gitlawb-node/src/git/push_delta.rs:178 list_all_objects (git cat-file --batch-all-objects); the delta path uses rev-list --objects.
- Both pin backends consume the filtered set:
crates/gitlawb-node/src/ipfs_pin.rs (Kubo), crates/gitlawb-node/src/pinata.rs (Pinata).
- The pin CID is content-sha256 (
crates/gitlawb-core/src/cid.rs:32), independent of repo object format.
Why it matters
This is live independent of the reachability caveat that makes get_by_cid dormant: repos init --object-format=sha1 (crates/gitlawb-node/src/git/store.rs:15), so get_by_cid cannot resolve a sha256 CID today — but the pin path pins by content-sha256 regardless, so the withheld tree bytes are fetchable by CID on the public IPFS network. Exposure is off-node and persists until GC/unpin; via Pinata it reaches public gateways. The confidentiality boundary is the same one get_tree and #135 protect (subtree child names + oids).
Confirmed: a withheld subtree's tree oid survives replicable_objects_fail_closed into the replicable/pinned set, while blob withholding is unaffected.
Scope
Write-side of the same KTD3 divergence as #135. The fix reuses #135's tree-reachability machinery: gate the replicable set on an anonymous replicable_tree_set (the caller = None allowed-tree-set), dropping withheld-subtree tree oids from replication exactly as blobs are handled via replicable_blob_set. Best done after #135 lands tree_paths.
Live only when a pin backend (GITLAWB_IPFS_API or GITLAWB_PINATA_JWT) is configured; both default empty.
Summary
The replication/pin path passes every non-blob object through unconditionally, so a withheld subtree's tree object is pinned to IPFS on push (Kubo or Pinata). Once pinned, the CID is announced to the public IPFS DHT and served by bitswap to any peer, disclosing the withheld subtree's child filenames and child oids off-node — bypassing the
get_by_cidgate #135 adds. This is the write-side counterpart of #135 (which covers only the on-nodeGET /ipfs/{cid}read).Where
crates/gitlawb-node/src/git/visibility_pack.rs:353—replicable_objects_fail_closedfilters!all_blob_oids.contains(oid) || allowed_blobs.contains(oid); trees are never inall_blob_oids, so they always pass. The doc comment states the now-wrong assumption: "commits and trees are structural, never content-withheld."crates/gitlawb-node/src/git/push_delta.rs:178list_all_objects(git cat-file --batch-all-objects); the delta path usesrev-list --objects.crates/gitlawb-node/src/ipfs_pin.rs(Kubo),crates/gitlawb-node/src/pinata.rs(Pinata).crates/gitlawb-core/src/cid.rs:32), independent of repo object format.Why it matters
This is live independent of the reachability caveat that makes
get_by_ciddormant: repos init--object-format=sha1(crates/gitlawb-node/src/git/store.rs:15), soget_by_cidcannot resolve a sha256 CID today — but the pin path pins by content-sha256 regardless, so the withheld tree bytes are fetchable by CID on the public IPFS network. Exposure is off-node and persists until GC/unpin; via Pinata it reaches public gateways. The confidentiality boundary is the same oneget_treeand #135 protect (subtree child names + oids).Confirmed: a withheld subtree's tree oid survives
replicable_objects_fail_closedinto the replicable/pinned set, while blob withholding is unaffected.Scope
Write-side of the same KTD3 divergence as #135. The fix reuses #135's tree-reachability machinery: gate the replicable set on an anonymous
replicable_tree_set(thecaller = Noneallowed-tree-set), dropping withheld-subtree tree oids from replication exactly as blobs are handled viareplicable_blob_set. Best done after #135 landstree_paths.Live only when a pin backend (
GITLAWB_IPFS_APIorGITLAWB_PINATA_JWT) is configured; both default empty.