Files
alksocks/docs/architecture/decisions/006-access-control.md
T
glm-5.3-flash 1ad8d9d489 core: the cross-cutting integration suite + docs sync
The batch-1 closing task: nine cross-cutting integration tests
(tests/core-integration.rs) pinning the whole protocol core end to
end over the channels harness, plus the architecture-sync docs pass.

Tests:
- One registration, all three commands: CONNECT + ASSOCIATE + BIND
  interleaved live on the one channels/socks5/sub op; a teardown
  ends only its own session (mux/demux isolation) — the POC #2/#5
  regression guard, complete.
- Concurrency: nine concurrent mixed-command sessions (raw topology)
  and six through the typed consumer sessions (consumer-side
  topology) — the mux/demux pinned in both channel-id allocation
  directions; fresh round-trips after the burst; no channel leaks.
- Teardown matrix (wire.md §Sentinels and lifecycle): CONNECT
  half-close, ASSOCIATE EOF, BIND client-gone (a parked accept so
  the quiet-wait exit is deterministic) — each followed by fresh
  sessions of every command (no connection poisoning).
- Identity/scope matrix (ADR-006 end to end): open-only (CONNECT +
  ASSOCIATE serve, BIND refused 0x02 at the command gate), bind-only
  fail-closed at the open gate for all commands (the spec drift the
  task's phrasing carried, reconciled — ADR-006's open gate requires
  socks5:open unconditionally), both-scopes (everything, granted
  explicitly); the CF-006 witness asserted per row.
- The readiness contract re-pinned under concurrent load: a
  hot-Ok(None) egress across concurrent associations does not starve
  the datagram stage's channel branch.

Docs sync: README Current State ("the protocol core is implemented"
+ the landed inventory; the decomposition residue gone); consumer.md
§Wiring reconciled to the landed session signatures; ADR-006
§Consequences states the grant asymmetry explicitly; lib.rs crate
docs describe the landed surface.

Verification: cargo test 302 green; --all-features green; clippy
--all-targets -D warnings clean; fmt --check clean; both wasm checks
clean; cargo doc --no-deps clean. Batch boundary holds: no
local/composed code, the default crate is protocol-only.
2026-09-24 07:44:54 +00:00

4.4 KiB

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: <alpn-family>:<capability> (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)