Skip to content

fork_repo clones a full mirror after only a root (/) visibility check, bypassing path-scoped withholding #98

Description

@beardthelion

fork_repo authorizes the caller only at the whole-repo / path, then git clone --mirrors the entire source. A caller allowed at / but denied a path-scoped subtree can fork the full mirror and obtain blobs the filtered git_upload_pack path would have withheld. This bypasses path-scoped visibility (INV-2).

Where

crates/gitlawb-node/src/api/repos.rs

  • fork_repo authorizes at / only:
    let (source, _rules) =
        crate::api::authorize_repo_read(&state, &owner, &name, Some(auth.0.as_str()), "/").await?;
  • then clones the whole repo as a mirror:
    let output = std::process::Command::new("git")
        .args(["clone", "--mirror", source_path..., disk_path...])

The _rules returned by authorize_repo_read are discarded, so any path-scoped withholding is ignored for the fork path.

Impact

A non-owner caller with root (/) read access but without access to a path-scoped subtree (e.g. /secret/**) can fork the repo and read every blob in that subtree from their own copy. The read path (git_upload_pack) withholds those blobs via withheld_blob_oids; the fork path does not, so fork is a clean bypass of path-scoped visibility.

This is pre-existing (not introduced by #60). Surfaced while reviewing #60, which optimizes the same withheld-walk on the read path; the fork path defeats that invariant wholesale.

Suggested remediation

Either:

  1. Fail closed for non-owners when the source has any path-scoped rule (has_path_scoped_rule), using did_matches(...) for the owner-only bypass, and preserve the existing not-found-style response for protected-data failures; or
  2. Build the fork from the same filtered projection used by git_upload_pack rather than from a full --mirror, so withheld blobs are never copied.

Option 1 is the smaller, safer fix; option 2 preserves fork-for-readers semantics but is more involved. Owner forks (and forks of repos with no path-scoped rules) must continue to work unchanged.

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