# ADR-007: ACL runs before any advertisement or ref line ## Status Accepted ## Context Ref names leak repository existence (and structure). The advertisement is the first thing a client sees; if ACL runs after advertisement begins, a denied caller has already learned that a repo (or a ref within it) exists. The vision's visible-surface = authorized-surface invariant requires the check to run before the first byte of git protocol content. POC-1 observed the natural enforcement point on the git:// path: the repo name arrives in-band in the first request line, *before* any ref data — so the resolve-then-authorize step sits structurally ahead of the first emitted line. POC-3 noted the http extra-routes are registered permissive by default (the gateway bearer layer does not cover them) — making the http-side check an explicit door responsibility (alkhttp `git` feature), not an inherited one. ## Decision Every session/request performs, in order, before any protocol output: 1. Extract the wire repo name (git:// first-request line; ssh exec command string; http path segment). 2. Resolve it against the registry to (repo id, storage root) — reject unknown repos with the same error as unauthorized ones (no existence oracle) (ADR-008). 3. Run alkcall `AccessControl::check(peer_identity)` against the repo's required access. Read access covers the *whole fetch surface*: advertisement, ref listing, and pack transfer alike — anonymous access on explicitly-public repos grants the same full read path (vision §"Primary deployment target" makes anonymous clone first-class; the "advertisement is the only anonymous surface" line in the same doc's principle 1 is read as the *minimum* boundary, and this decision sets the operative rule). *(Mechanism amended by ADR-011 §3 (review 001 D-2): the static ACL engine fails closed on `identity: None`, so it cannot express anonymous-public fetch. The per-repo check is alkgit-core's `authorize` policy function evaluated on the registry record; the alkcall registry gate still applies where the op has scopes. The step *order* — resolve, then authorize, before any protocol output — is unchanged.)* 4. Only then hand the session to transport. Adapters embed this sequence; transport asserts it (the session entry points take an authorized-repo marker — a type the adapter constructs only after step 3 passes — so skipping the check is a type error, not a runtime log) but does not re-check — authorization evaluation lives in one place, alkcall, and invocation/wiring lives in one place, the adapter. Push is always authenticated on every repo, no exceptions (vision § "Primary deployment target"). ## Consequences - No ref/capability line is ever emitted for a repo a caller cannot see; the gitea-class "enforcement elsewhere" bug is structurally excluded. - Unknown-repo and unauthorized errors are indistinguishable to callers. - The check is cheap (registry lookup + ACL check) and runs before any expensive protocol work. - http adapters must wire ACL explicitly for their routes (POC-3 showed alkhttp extra routes default permissive) — doors.md encodes this. ## References - `docs/research/vision.md` §"Guiding principles" 1–2, §"Immediate threat-model notes" - `docs/research/poc-1-findings.md` follow-up 4; `docs/research/poc3-findings.md` §"does NOT settle" (auth) - alkcall ADR-017 (privilege model) - ADR-008 (repo identity), doors.md (door auth mechanics)