Skip to content

feat(net): capsule inbound TCP bind (bind_tcp) (#1230) - #1457

Open
jvsteiner wants to merge 1 commit into
astrid-runtime:mainfrom
jvsteiner:feat/1230-capsule-bind-tcp
Open

feat(net): capsule inbound TCP bind (bind_tcp) (#1230)#1457
jvsteiner wants to merge 1 commit into
astrid-runtime:mainfrom
jvsteiner:feat/1230-capsule-bind-tcp

Conversation

@jvsteiner

Copy link
Copy Markdown

Linked Issue

Closes #1230.

Summary

A capsule can now bind an inbound TCP port and accept connections from its run loop — the runtime primitive the srouter capsule needs (loopback ingress on 127.0.0.1:8788). This is the bind_tcp concern extracted from the retired #1321 as a focused, independently-reviewable PR (its other two concerns are #1380 and #1231).

Changes

  • Host fn bind_tcp(host, port) -> tcp-listener (net/mod.rs) — validates the host, checks the net_bind capability, applies a loopback airlock (non-loopback binds are refused, not downgraded), binds a tokio::net::TcpListener, and returns a tcp-listener resource.
  • tcp-listener resource (tcp_listener.rs) — accept / poll-accept(timeout_ms) / local-addr; accepted connections wrap as NetStream::Tcp and reuse every existing read/write/peek/close host fn. accept is cancellable on capsule unload and sets recv_yielded so a bound accept isn't epoch-trapped as a spinner. Capped by the shared MAX_ACTIVE_STREAMS.
  • Gate check_net_tcp_bind (security/mod.rs trait default, fail-closed + manifest_gate.rs impl) — matches host:port against the manifest's existing net_bind allowlist with the same matcher check_net_connect uses. A unix:* entry never authorizes a TCP bind.
  • The net_bind capability field, its discovery-time merge, and the WIT contract already existed — unchanged.

Verification

  • cargo test -p astrid-capsule green.
  • 6 ported tests: 4 gate tests (check_net_tcp_bind_* — host:port match, wildcard port, unix-entry-doesn't-authorize-tcp, empty-denies) + 2 loopback-airlock unit tests.
  • clippy clean, rustfmt applied.

Notes

Checklist

  • Linked to an issue
  • CHANGELOG.md updated

Fills in the daemon's stubbed astrid:net bind_tcp host fn so a capsule can
bind a loopback TCP listener and accept inbound connections — the missing
substrate for capsule-hosted HTTP servers (e.g. an Anthropic-Messages shim
Claude Code points ANTHROPIC_BASE_URL at, routed by srouter).

Design:
- Authorization reuses the existing `net_bind` manifest field, whose own
  doc already reads "Unix/TCP socket bind addresses". TCP entries are
  `host:port` / `host:*` patterns matched with the SAME semantics as
  net_connect; a `unix:*` entry (the CLI proxy) never matches a TCP
  host:port, so the two socket families share the field without
  cross-authorizing. New gate method `check_net_tcp_bind(capsule, host,
  port)`, fail-closed default in the trait, allowlist match in
  ManifestSecurityGate.
- Host fn `bind_tcp`: capability-gate → loopback-confinement rail →
  tokio bind → resource-table slot. Loopback-only is enforced host-side
  (is_loopback_bind_host) regardless of the allowlist, mirroring how
  connect_tcp runs its is_safe_ip airlock AFTER the capability gate.
  Non-loopback bind is refused (AirlockRejected), not downgraded.
- TcpListenerSlot now holds the live Arc<tokio::net::TcpListener>.
  accept / poll_accept register the accepted stream as a NetStream::Tcp —
  the SAME representation outbound connect_tcp uses — so every existing
  read/write/peek/timeout host fn works on accepted connections with no
  extra wiring. Per-capsule MAX_ACTIVE_STREAMS cap applies; accept sets
  recv_yielded so a bound accept-loop is not epoch-trapped as a spinner;
  cancellable so capsule unload wins over a blocked accept.

Proven: a probe capsule bound 127.0.0.1:8799, accepted a curl connection,
and served an HTTP 200 through the patched daemon (LISTENING → ACCEPTED →
wrote 110 bytes). 6 new unit tests (gate host:port matching incl. the
unix-entry-doesn't-authorize-TCP case; loopback host classification).

POC for the Astrid router work — informs the upstream feature request
(gotchas + security posture documented separately).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(net): implement capsule inbound TCP bind (bind_tcp) — currently a CapabilityDenied stub

1 participant