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

4.1 KiB

ADR-012: Target Policy and the Virtual-Subnet Translation Layer

Status

Accepted (2026-09-20, Phase 1; the dialer-refuses shape is OQ-SK-05's evidence-backed resolution (see ADR-006), and the 2026-09-14 virtual-subnet proposal is resolved as assembly-layer policy)

Context

OQ-SK-05's residual Phase 1 questions: (a) does the base crate ship a per-target allowlist hook, or is target policy entirely the dialer's concern; (b) domain-form targets (RFC 1928 ATYP 0x03): resolve producer-side, refuse, or pass through unresolved; (c) the 2026-09-14 virtual-subnet proposal (identity-scoped subnets so proxied resources become IP-addressable ("BND.ADDR ↔ channel id" translation, a hub exposing proxied resources per identity under ACL).

The proposal's key property: the mechanism needs no wire change; DST.ADDR and BND.ADDR are arbitrary RFC fields, so virtual addresses ride ordinary CONNECT/BIND requests; the translation layer is the dial callback (dial(target) matches a virtual subnet against a per-identity resource table).

Decision

  • No allowlist hook in v1 (ADR-006's resolution, restated as the target-policy half): the dial callback is the policy point; per-target refusal is an RFC reply code. The declarative-allowlist variant remains an additive future extension if a deployment needs declarative policy; the evidence-backed default is dialer-refuses.
  • Domain-form targets: pass through unresolved to the dialer. Resolution is the egress's/dialer's concern (POC #2 resolved the local-egress half: "the egress owns resolution" (the ssh -D semantic). The protocol layer never resolves: a wasm producer may have no resolver at all, and the composed variant's targets ride the alktunnels direct op (producer-side resolution there, per alktunnels ADR-007's own posture). fast-socks5's dns_resolve config (and its resolve_dns step) is not part of the extracted vendor region at all; the crate's producer never resolves (ADR-006/ADR-012), so the extraction drops both.
  • The virtual-subnet translation layer is assembly-layer policy, not a crate feature. The crate ships no translation-table helper in v1: the dial callback is the seam, and a deployment wanting identity-scoped virtual subnets implements the subnet→resource table there (per-identity scoping = resources + ACL + target policy; hub exposure = the terminate-and-re-produce story; no new wire vocabulary). The full-tunnel composition (the host routes the subnet into the proxy via tun2proxy) is where a virtual subnet becomes routable from vanilla apps: connect(10.x.y.z:p) is an ordinary CONNECT the dialer translates. tun2proxy's virtual-network/virtual-DNS support is the prior art to check if and when a deployment builds this; the crate's obligation is only that the seams support it, which ADR-004's shape already does.

Consequences

  • The security boundary stays exactly two mechanisms: the open gate (socks5:open/socks5:bind) and the dial/accept callbacks. No third policy layer.
  • The virtual-subnet endgame is unblocked without crate work: the translation layer rides the dial callback; if a deployment materializes with concrete needs, a translation-table helper can be added additively (assembly-layer code, possibly later graduated into the crate behind a feature); the seams don't change.
  • DNS posture is uniform: producer-side resolution everywhere (local egress resolves; composed egress hands the name to the direct op which resolves at the tunnel producer).

References

  • OQ-SK-05 (the full question set + the proposal's record), OQ-SK2-07 (the deferred helper question this ADR's resolution feeds), OQ-SK-01 (the dial callback), OQ-SK-09 (the multiplexing rejection; the virtual-subnet idea that survives is recorded in the tracker)
  • POC #2 (poc-udp-associate-findings.md: the egress-owns-DNS resolution), ADR-004 (the seams), ADR-006 (the ACL posture), ADR-008 (the provider defaults)
  • alktunnels phase-0-findings OQ-TN-08 (the target-policy dissolution precedent); tun2proxy (/workspace/tun2proxy, the virtual-network prior art + the endgame composition partner)