Files
glm-5.3-flash e196ed43e3 docs: remove em dashes from repo prose and forbid them going forward
- Rewrote em dash prose across the whole tree outside docs/reviews/
  (the historical reviews stay verbatim, matching the alktunnels and
  alktty precedent): src doc comments and inline comments (587),
  docs/architecture (319), docs/research + sdd_process (484), tests
  (361), tasks (899), fuzz (28), README/CHANGELOG/Cargo.toml (77),
  AGENTS.md (85), and the .opencode/agents prompts (109)
- Fenced content follows the precedent: comments, ASCII-diagram label
  separators, and template placeholders rewritten; wire data, JSON/BAST
  documents, byte diagrams, and transcripts survive verbatim; assert,
  expect, panic, error-display, and format message literals rewritten
  as prose; no code, fixture, or test data change
- src/rfc1928/LICENSE: the two dashes in alksocks' own provenance
  header rewritten; the MIT legal text untouched
- AGENTS.md adds Project Convention 18: no em dashes in prose; varied
  restructure, no spaced-hyphen substitutes, fenced prose rule, en
  dash numeric ranges exempt, docs/reviews/ exempt from the greps
- tasks/*.md frontmatter: the name values rewritten by the sweep are
  now quoted (a raw colon inside an unquoted YAML scalar parsed as
  null); taskgraph validate reports 37 tasks, no issues

Verification: cargo test; cargo clippy --all-targets -D warnings;
cargo fmt --check; cargo doc --no-deps; fuzz corpus replay
(cargo test --manifest-path fuzz/shared/Cargo.toml); wasm check and
clippy for wasm32-unknown-unknown with -D warnings; em dash grep over
the repo returns matches only in docs/reviews/ and the bast.md BAST
JSON data fence
2026-11-03 19:31:53 +00:00

88 lines
4.4 KiB
Markdown

# 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)