# ADR-006: Access-Control Posture — the Open Gate Plus the Command Gate ## Status Accepted (2026-09-20, Phase 1; scope gates POC-validated in shape — POC #1 (`socks5:open`), POC #5 (`socks5:bind` at command dispatch); consequence asymmetry pinned by the integration suite, 2026-09-24) ## Context SOCKS5 is arbitrary-egress by nature — the open gate plus the target policy are the security boundary (AGENTS.md convention 12). alktunnels resolved the sibling posture (its ADR-006): whatever ACL governs the resource governs everything reachable through it, plus whatever policy the SOCKS5 implementation itself applies downstream. For SOCKS5, BIND adds an *inbound* capability — the accept-side twin of arbitrary egress — so the open gate must distinguish it (OQ-SK-08's security note): an identity with proxy egress must not implicitly get ingress listeners. POC #5 validated exactly this distinction at command dispatch from the per-call opener identity. ## Decision - **The open gate is the boundary.** The open op's `AccessControl.required_scopes` is `["socks5:open"]` (`SOCKS5_OPEN_SCOPE`) — scope-gated via `ChannelCore::register_openable` (the registry runs the ACL before the open handler). Scope-string convention: `:` (`socks5:open`, `socks5:bind`) — following alktty's `TTY_OPEN_SCOPE` / alktunnels' `tunnel:open`/`tunnel:direct` shape. Stable once published. - **The command gate: BIND requires `socks5:bind` at command dispatch.** The open op carries the session-level gate; the command-class distinction happens in-stream, at command read, from the per-call opener identity (CF-005/CF-006): CONNECT and UDP ASSOCIATE require only `socks5:open`; TCP BIND requires `socks5:bind`. An identity without it gets the RFC-faithful `ConnectionNotAllowed` (0x02) refusal while CONNECT still serves the same identity (POC #5 test 4). - **Target policy is the dialer's concern.** No target allowlists in the base crate — the dial callback refuses (per-target refusal is an RFC reply code; ADR-004's seams). The dialer-refuses shape avoids a second policy layer (alktunnels OQ-TN-08's resolution pattern, evidence-backed). Whatever ACL governs the socks5 resource governs everything reachable through it, plus whatever the dial callback enforces. - **Per-target ACL for the composed egress comes free**: each destination rides its own alktunnels direct-op channel governed by the `tunnel:direct` grant's ACL (POC #2 validated); the dial callback governs per-destination checks. - **Ownership**: optionally consult `OwnershipProvider` for resource ownership (alknet ADR-050's seam) — a registration-time choice, not a protocol concern. - **Hub relaying**: the hub's terminate-and-re-produce applies per hop (alkcall ADR-051's `ChannelRelay`/`HubLegTemplate`); the hub's ACL governs the final dial — the hub is the network boundary. No socks5-specific relay work. ## Consequences - Two grants, clean separation: `socks5:open` (egress) and `socks5:bind` (ingress listeners) — never implied by each other (the alktunnels ADR-006 Amendment 1 posture: a capability on its own scope). The implication asymmetry is pinned by the integration suite (`tests/core-integration.rs`, the identity/scope matrix): `socks5:open` alone serves CONNECT + ASSOCIATE and is refused BIND (0x02) at the command gate; `socks5:bind` alone grants *nothing* on the op — the open gate's `required_scopes = ["socks5:open"]` fails closed for every command (the establisher never runs, no phantom channel); both scopes, granted explicitly, serve everything. A BIND-capable deployment grants both. - BIND's security boundary is enforced even though BIND rides the same op (ADR-001): the command gate is the mechanism, and it is POC-validated (POC #5). - The allowlist-hook variant (a declarative per-target policy at registration) is *not* shipped in v1 — the dial callback is the policy point; see OQ-SK-05's residue (OQ-SK2-07 in the tracker) for the virtual-subnet translation-layer proposal, which is the same dial-callback shape. ## References - OQ-SK-05 (target policy), OQ-SK-08 (the BIND security note) - POC #1 (the `socks5:open` gate; the identity seam), POC #5 (the `socks5:bind` command gate), POC #2 (composed per-target ACL) - alktunnels ADR-006 + Amendment 1 (the sibling posture), alknet ADR-024 (registry layering), alknet ADR-050 (ownership), alkcall ADR-042 / ADR-051 (hub relay)