Skip to content

Pin full-scan fallback can replicate an unreachable private blob in cleartext #99

Description

@beardthelion

Summary

The pin path fails open relative to its candidate set, so an unreachable private blob can be replicated to public IPFS/Pinata in cleartext.

replicable_objects (crates/gitlawb-node/src/git/visibility_pack.rs) replicates any OID not explicitly in the withheld set:

all.into_iter().filter(|oid| !withheld.contains(oid)).collect()

The withheld set comes from blob_paths, which walks reachable objects only (git rev-list --all plus HEAD). That is correct for the serve path: upload-pack never exposes unreachable objects. But since #90, the pin full-scan fallback push_delta::list_all_objects (crates/gitlawb-node/src/git/push_delta.rs) runs git cat-file --batch-all-objects, which by its own docstring returns every object including unreachable/dangling ones. A private blob that is in the object DB but unreachable from any ref or HEAD is therefore never classified, never withheld, and replicates in cleartext.

Trigger

  • An unreachable private blob: a force-push, amend, rebase, or reset that orphans a commit containing a secret, in the pre-GC window.
  • A full-scan pin: the FullScanRequired path in resolve_candidates_for_push (non-commit push tip, a rev-list error, or force-full-scan), or any reconciliation sweep that calls list_all_objects.

Scope

Present on main today via #90; independent of the withheld-classifier work in #84, which only walks the reachable graph (correct for serve). The asymmetry is between the reachable-only classifier and the all-objects pin candidate set.

Fix direction

Fail closed on the pin path: a candidate OID the classifier never assigned a path to should be treated as withheld when a path-scoped rule is active, rather than replicated. Equivalently, intersect the pin candidate set with the reachable set when a withholding policy applies. Keeping --batch-all-objects for the reconciliation sweep is fine as long as unclassified objects are withheld, not pinned.

Severity

Data exposure (a cleartext private blob to public IPFS). Low likelihood: requires an unreachable private blob, a full-scan pin trigger, and the pre-GC window.

Evidence

  • replicable_objects fail-open: crates/gitlawb-node/src/git/visibility_pack.rs
  • candidate set list_all_objects (cat-file --batch-all-objects): crates/gitlawb-node/src/git/push_delta.rs
  • consumed by pin_new_objects: crates/gitlawb-node/src/ipfs_pin.rs, crates/gitlawb-node/src/pinata.rs

Metadata

Metadata

Assignees

No one assigned

    Labels

    crate:nodegitlawb-node — the serving node and REST APIkind:securityVulnerability fix or hardeningsev:mediumDegraded but workaround existssubsystem:replicationMirror, replica, and cross-node syncsubsystem:visibilityPath-scoped visibility and content withholding

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions