From a8339bc147b8882c6efc0a11070083348d8adc26 Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Tue, 15 Sep 2026 03:22:48 +0400 Subject: [PATCH] feat(net): expose authenticated connection ownership Signed-off-by: Joshua J. Bouw --- astrid-sdk/src/lib.rs | 1 + astrid-sdk/src/net.rs | 31 +++++++++++++++++++ astrid-sys/src/lib.rs | 1 + .../deps/astrid-net@1.0.0/net@1.0.0.wit | 11 +++---- .../request-context@1.0.0.wit | 19 ++++++++++++ contracts | 2 +- 6 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 astrid-sys/wit-staging/deps/astrid-request-context@1.0.0/request-context@1.0.0.wit diff --git a/astrid-sdk/src/lib.rs b/astrid-sdk/src/lib.rs index 0e3a07d..247fe0a 100644 --- a/astrid-sdk/src/lib.rs +++ b/astrid-sdk/src/lib.rs @@ -57,6 +57,7 @@ use astrid_sys::astrid::ipc::host as wit_ipc; use astrid_sys::astrid::kv::host as wit_kv; use astrid_sys::astrid::net::host as wit_net; use astrid_sys::astrid::process1_1_0::host as wit_process; +use astrid_sys::astrid::request_context::host as wit_request_context; use astrid_sys::astrid::sys::host as wit_sys; use astrid_sys::astrid::uplink::host as wit_uplink; use borsh::{BorshDeserialize, BorshSerialize}; diff --git a/astrid-sdk/src/net.rs b/astrid-sdk/src/net.rs index 2bf3013..53a40b2 100644 --- a/astrid-sdk/src/net.rs +++ b/astrid-sdk/src/net.rs @@ -11,6 +11,21 @@ use super::*; +/// Opaque host-minted owner of an authenticated local connection. +/// +/// This is a comparison handle, not a credential. It carries no authority on +/// its own and cannot be constructed from client payloads through the SDK. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ConnectionOwner(String); + +impl ConnectionOwner { + /// Borrow the opaque owner value for equality checks and routing. + #[must_use] + pub fn as_str(&self) -> &str { + &self.0 + } +} + /// Direction argument for [`TcpStream::shutdown`] — mirror of /// [`std::net::Shutdown`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -169,6 +184,16 @@ pub struct TcpStream { } impl TcpStream { + /// Return the host-minted owner of this authenticated local connection. + /// + /// Outbound, remotely accepted, and unauthenticated streams return + /// `None`. Interactive request routing must fail closed in that case. + pub fn connection_owner(&self) -> Result, SysError> { + wit_request_context::connection_owner(&self.inner) + .map(|owner| owner.map(ConnectionOwner)) + .map_err(host_err) + } + /// Open an outbound TCP connection to `host:port`. /// /// DNS resolution and the SSRF airlock run host-side; the WASM @@ -589,6 +614,12 @@ fn parse_port(port_str: &str) -> std::io::Result { mod tests { use super::*; + #[test] + fn connection_owner_exposes_only_an_opaque_comparison_value() { + let owner = ConnectionOwner("request-owner-1".to_owned()); + assert_eq!(owner.as_str(), "request-owner-1"); + } + #[test] fn parse_host_port_basic() { let (h, p) = parse_host_port("example.com:443").unwrap(); diff --git a/astrid-sys/src/lib.rs b/astrid-sys/src/lib.rs index e285928..2666889 100644 --- a/astrid-sys/src/lib.rs +++ b/astrid-sys/src/lib.rs @@ -75,6 +75,7 @@ mod generated { import astrid:ipc/host@1.0.0; import astrid:kv/host@1.0.0; import astrid:net/host@1.0.0; + import astrid:request-context/host@1.0.0; import astrid:http/host@1.1.0; import astrid:sys/host@1.0.0; import astrid:process/host@1.1.0; diff --git a/astrid-sys/wit-staging/deps/astrid-net@1.0.0/net@1.0.0.wit b/astrid-sys/wit-staging/deps/astrid-net@1.0.0/net@1.0.0.wit index b4b3b40..0811b59 100644 --- a/astrid-sys/wit-staging/deps/astrid-net@1.0.0/net@1.0.0.wit +++ b/astrid-sys/wit-staging/deps/astrid-net@1.0.0/net@1.0.0.wit @@ -337,12 +337,11 @@ interface host { /// Bind a TCP listener for inbound connections. /// - /// `"0.0.0.0"` / `"::"` exposes the listener to every network - /// interface (server posture) — restrict in `Capsule.toml - /// [capabilities] net_tcp_bind` to loopback-only patterns unless - /// the capsule genuinely needs to serve. Port `0` selects an - /// ephemeral port. Gated by a `net_tcp_bind` capability allowlist - /// distinct from `net_connect`. + /// Astrid confines TCP listeners to loopback hosts: `localhost`, + /// `127.0.0.0/8`, or `::1`. Wildcard and non-loopback interfaces are + /// rejected. Port `0` selects an ephemeral port. The requested + /// `host:port` must match the capsule's `[capabilities].net_bind` + /// allowlist; this authority is distinct from `net_connect`. bind-tcp: func(host: string, port: u16) -> result; /// Open an outbound TCP connection to `host:port`. Goes through diff --git a/astrid-sys/wit-staging/deps/astrid-request-context@1.0.0/request-context@1.0.0.wit b/astrid-sys/wit-staging/deps/astrid-request-context@1.0.0/request-context@1.0.0.wit new file mode 100644 index 0000000..360806a --- /dev/null +++ b/astrid-sys/wit-staging/deps/astrid-request-context@1.0.0/request-context@1.0.0.wit @@ -0,0 +1,19 @@ +/// Authenticated request context for host-local client connections. +/// +/// This package exposes only an opaque comparison handle. The value is minted +/// by the host when an authenticated local connection is accepted; it is not a +/// credential, carries no authority by itself, and must never be accepted from +/// guest payloads as proof of ownership. + +package astrid:request-context@1.0.0; + +interface host { + use astrid:net/host@1.0.0.{error-code, tcp-stream}; + + /// Return the opaque owner of an authenticated accepted local connection. + /// + /// `none` means the stream is outbound, remotely accepted, or was not + /// authenticated strongly enough to own interactive requests. Consumers + /// must fail closed for owner-scoped prompts when this is `none`. + connection-owner: func(connection: borrow) -> result, error-code>; +} diff --git a/contracts b/contracts index a268eb3..d928298 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit a268eb375198ac07c587687f8765a1339e46cf0c +Subproject commit d928298a8ebee62b6f0d8154f7491b3ce64dc305