Skip to content

GET /ipfs/{cid} serves any git object by raw hash with no visibility check, leaking withheld blobs #110

Description

@beardthelion

The content-addressed retrieval endpoint bypasses path-scoped visibility entirely. This is the same withholding-bypass class as #98 (fork) on a different egress surface.

Where

crates/gitlawb-node/src/api/ipfs.rs, get_by_cid (route GET /ipfs/{cid} registered at server.rs:191).

  • The handler takes no AuthenticatedDid extension and the route sits outside add_auth_layers, so it is unauthenticated.
  • It decodes the CID to a SHA-256, calls list_all_repos(), and for every repo does repo_store.acquire(...) + store::read_object(repo_path, sha), returning the first match as raw bytes.
  • There is no authorize_repo_read, no visibility_check, and no withheld_blob_oids filtering anywhere on the path.

Impact

For a public repo with a path-scoped deny rule (e.g. /secret/**), git_upload_pack serves a filtered pack: the withheld blob content is omitted, but the tree objects that name those blobs and their SHAs are kept intact (mode B). An unauthenticated caller can therefore:

  1. Clone the repo and read the tree under the withheld path to recover the withheld blob SHAs and filenames.
  2. Compute each blob CID and fetch the cleartext content via GET /ipfs/{cid}.

So every blob the read path is careful to withhold is retrievable in cleartext by anyone who can reach the node. The fork gate (#98) and the upload-pack filter do not help, because the leak is on a separate route.

Suggested remediation

get_by_cid must not serve a blob the requesting caller may not read. Options:

  1. Resolve the object to its owning repo(s) and apply the same per-caller withheld check the serve path uses (withheld_blob_oids / visibility_check) before returning content; fail closed (404) when the object is in any repos withheld set for the caller. Requires threading caller identity onto the route.
  2. If the endpoint is meant only for already-public content, restrict it to objects reachable from a repo+path the caller passes authorize_repo_read for, rather than a blind cross-repo SHA scan.

Found while mapping egress paths during the #98 review. Pre-existing, independent of #98.

Metadata

Metadata

Assignees

No one assigned

    Labels

    crate:nodegitlawb-node — the serving node and REST APIkind:securityVulnerability fix or hardeningsev:highMajor break or real security/trust risk, no easy workaroundsubsystem:apiNode REST API request/response surfacesubsystem: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