Skip to content

Repo-scoped read surfaces gated by existence only, not visibility (certs, issues, labels, bounties, stars) #120

Description

@beardthelion

Summary

Several repo-scoped read handlers verify the repo exists (get_repo) but never run the visibility gate (authorize_repo_read / visibility_check), so they return a private repo's metadata to an anonymous caller. This is the same class as #94 (hooks) and its PR #113 siblings (events, replicas, protected branches); these are the surfaces that batch did not cover. The mutation handlers on the same files do gate correctly; only the reads were missed.

Where (all verified by reading the handler bodies; none take an AuthenticatedDid caller)

All sit on the anonymous read group (optional_signature).

Impact

For any private repo whose owner-DID slug an attacker can guess or enumerate, an unauthenticated request discloses its ref history and pusher identity (certs), issue content, labels, bounties, and existence. certs is the most severe (ref names + commit SHAs + pusher DID, identical to the #114/#116 leak class); the others are lower but still leak private-repo metadata that the read path otherwise withholds.

Fix

Add authorize_repo_read(&state, &owner, &repo, caller, "/").await? to each read handler (deny -> 404 RepoNotFound, existence-hiding), threading the Option<Extension<AuthenticatedDid>> caller, exactly as #113 did for events/replicas/protected. The matching gl/MCP read callers must then sign (the #115 client-signing class).

Found during a read-only egress audit of every repo-data surface.

Activity

  1. added
    kind:securityVulnerability fix or hardening
    crate:nodegitlawb-node — the serving node and REST API
    subsystem:visibilityPath-scoped visibility and content withholding
    subsystem:apiNode REST API request/response surface
    sev:highMajor break or real security/trust risk, no easy workaround
    on Jun 29, 2026
  2. added a commit that references this issue on Jun 30, 2026
  3. beardthelion commented on Jul 1, 2026

    @beardthelion
    CollaboratorAuthor

    One more surface in this class, not in the list above: list_repo_events (crates/gitlawb-node/src/api/events.rs), route GET /api/v1/repos/{owner}/{repo}/events (server.rs:322). It takes no AuthenticatedDid and runs no authorize_repo_read / visibility_check; it looks the repo up by owner/name and returns every local ref certificate plus received ref-update for it. It has never had a gate (not covered by #113).

    Same private ref metadata as the certs entry above (ref_name, old_sha, new_sha, pusher_did, node_did, timestamp), since it merges list_ref_certificates with list_repo_ref_updates.

    Reproduced by execution, anonymous GET on a private repo with one received ref-update:

    GET /api/v1/repos/z6MkOwner/secret/events   (is_public=false, no auth)
    -> 200 {"count":1,"events":[{"ref_name":"refs/heads/main","old_sha":"0000…","new_sha":"aaaa…",
        "pusher_did":"did:key:z6MkPusher","node_did":"did:key:z6MkNode",…}]}
    

    Same fix: thread the optional caller and gate on listable_at_root (deny -> 404 RepoNotFound). The two cross-repo feeds (events/ref-updates, GraphQL refUpdates) were separately gated in #143; this per-repo endpoint is distinct and still open.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions