Chio/Docs
LOGIN · JOIN

Prepared connections

See how one broker stream is bound to one admitted request and a fixed deadline, so a confined tool can use it once.

Concept chio@5f63e6e

On this page

A confined tool with no network access can still reach the credential broker, for one admitted request and for a bounded time. The host prepares that one connection before confinement and hands the tool the stream.

Preparation and dispatch are separate acts. The host binds the stream to the exact request, the broker's single prepared slot holds it, and when the tool finally uses it the broker checks current authority and the original capture again.

Prepared connectionChoose a moment.
one prepared slotr₂conflict0 sframe-read budget, cappeddeadline30 s capnonce expirycapability expiry5 mincprepareKcapturedfirst byteβexecutedauthority_unavailable0 sslotr₂conflictread budgetdeadline30 s capnonce expirycapability expiry5 mincprepareKcapturedfirst byteβexecutedauthority_unavailable

The connection expires at the earliest of three limits

Preparation fixes one absolute monotonic deadline: the earliest of thirty seconds, the capability's expiry, and the caller proof's nonce expiry. The daemon computes it before it returns any descriptor.

One prepared broker connection on a time axis, from preparation to its deadline. Times are illustrative; the frame-read budget's value, the IPC timeouts, and the provider exchange are left out.

A prepared stream gives the tool one path in place of a network

A tool that submits a provider request through the credential broker needs a route to the broker and nothing wider. Permission to open connections would reach every service the host can reach. Preparing the connection lets the trusted host establish the intended peer and the request binding first, then pass the confined process a stream that already carries both.

The stream is part of the operation's authority. A fresh connection after a failure could reach another peer, carry another request, or outlive the reviewed lifetime, so the tool uses the retained stream and recovery keeps the original operation. The wait for that stream also occupies bounded broker capacity, which belongs in the plan for concurrency and deadlines.

Preparation binds the stream to one request before any descriptor exists

The host calls BrokerIpcClient::prepare_dispatch_connection, which authenticates the broker through the client's peer check and carries the exact original execute request. It does so before the native cage is applied. On the daemon side, prepared_connection_binding reduces the tenant scope, the proof, and the complete request bytes to one frame binding and computes the connection's deadline before it returns a descriptor.

The client accepts the stream only with a chio.broker-prepare-dispatch-acknowledgement.v1 acknowledgement bound to the same registration and request. An unaccepted or mismatched acknowledgement fails preparation outright, so a confined tool only ever receives a descriptor for its own request.

For native brokered execution the prepared socket is pinned to the intended broker peer, and the single-use handoff keeps that identity. A failed connection gives no permission to reconnect to another endpoint or to fall back to a direct provider client.

One slot holds the wait and the execution together

The broker has exactly one prepared-connection slot. Preparation reserves it for the whole wait and execution, and a concurrent preparation is declined while it is held.

crates/security/chio-secret-broker/src/service_parts/ipc_prepared.inc7–10rust
struct PreparedIpcSlot {
    busy: Arc<std::sync::atomic::AtomicBool>,
    pending: Mutex<Option<PreparedIpcConnection>>,
}

Preparation claims busy by compare-and-swap, so two prepared connections never coexist. Releasing the returned permit clears the flag and lets the next preparation proceed, with its own deadline and authority; the release extends nothing that came before it.

The deadline is the earliest of three limits and never moves

The prepared lifetime ends at the earliest of thirty seconds, the capability's expiry, and the proof nonce's expiry. The daemon holds it as an absolute monotonic deadline.

Once the first execution byte arrives, the ordinary frame-read budget also applies, capped by whatever prepared lifetime remains. A tool that trickles occasional bytes gains no time: the deadline it was prepared with is the deadline it keeps.

Current authority is checked again when the stream is consumed

Before the daemon touches credential material, it checks current parent authority, revocation, the original native capture, and duplicate state. A grant withdrawn or a proof expired after preparation leaves a correctly prepared connection unusable.

On Linux, chio-broker-mcp adopts the cage's inherited broker socket on descriptor 8 and opens no connection of its own.

The host prepares before confinement, and the executor consumes once

A confined issue tool holds neither the registration signer nor network access. The host prepares the exact stream before confinement; after the kernel commits the original capture, the executor consumes it once. The gap between the two calls belongs to the host's dispatch transaction. The integration takes a configured BrokerIpcClient, the retained AttemptRegistration, and the original BrokerExecuteRequest; the supervisor transfers the stream and the independently provisioned tenant and receipt pin to the executor.

security-host/prepared_original.rsrust
use chio_core_types::PublicKey;
use chio_secret_broker::ipc_client::{BrokerIpcClient, BrokerIpcExecutionTranscript};
use chio_secret_broker::protocol::BrokerExecuteRequest;
use chio_secret_broker::store::AttemptRegistration;
use chio_secret_broker::Result;
use std::os::unix::net::UnixStream;

/// Host side: the original registration is already retained by the authority.
/// Preparation validates its acknowledgment; it does not commit kernel capture.
pub fn prepare_original(
    host_client: &BrokerIpcClient,
    registration: &AttemptRegistration,
    original: &BrokerExecuteRequest,
) -> Result<UnixStream> {
    host_client.prepare_dispatch_connection(registration, original)
}

/// Confined executor side, AFTER the host commits the original operation's
/// native capture. The supervisor transfers this exact stream, never its signer.
pub fn consume_prepared_original(
    stream: UnixStream,
    tenant_scope: &str,
    original: &BrokerExecuteRequest,
    independently_pinned_broker: &PublicKey,
    persist_private_transcript: impl FnOnce(&BrokerIpcExecutionTranscript) -> Result<()>,
) -> Result<BrokerIpcExecutionTranscript> {
    let transcript = BrokerIpcClient::execute_evidenced_on_authenticated_stream(
        stream,
        tenant_scope,
        original,
        independently_pinned_broker,
    )?;
    // Exact frames can contain sensitive request/response material. Retain them
    // in the host's private journal, not its public logs.
    persist_private_transcript(&transcript)?;
    Ok(transcript)
}

Download prepared_original.rs. An accepted preparation returns a peer-authenticated stream only after the acknowledgement validates, and preparation alone causes no provider effect. A later execution returns the original request and response frames with a signed outcome; the private transcript writer commits those frames durably, to the host's private journal and never its ordinary logs, because they can carry sensitive request and response data.

  • Exact request, live capture. The broker executes the admitted request through the transferred stream; retain the transcript and the original attempt.
  • Request, tenant, or proof changed after preparation. The frame binding refuses the substitution; keep the closed stream as evidence and compare the original commitment.
  • Parent authority withdrawn or deadline passed. The stream carries no usable authority; inspect current authority and the original capture state.
  • Reply lost after possible provider acceptance. The original outcome stays unresolved and is reconciled; reconnecting or calling the provider directly is no recovery.

The consuming function takes ownership of the UnixStream, so a local caller cannot use that value twice. Durable duplicate checks and the current-authority decision stay with the daemon, because Rust ownership says nothing about how many times a remote effect occurred.

A refused preparation or binding ends the connection before any credential is used

Each refusal matches one check in the daemon's prepared-connection path, at preparation or at service. chio security selftest broker runs the four prepared-connection cases on Linux, where the descriptor handoff applies, and asserts that capacity, expiry, and substitution each end as a declined preparation, a refused binding, or a client fault.

RefusalConditionSource
prepare_dispatch_connection returns AuthorizationDenied("broker declined the prepared execution connection")
The single slot already holds a prepared execution
BrokerError::Conflict "prepared connection capacity is occupied", sent as accepted: false with code conflict
Connection closed with no response; prepare_dispatch_connection returns AuthorityUnavailable("broker IPC read failed: ...")
Capability or proof-nonce expiry had passed at preparation
BrokerError::AuthorizationDenied "prepared connection authorization expired"
chio-broker-mcp prints "chio-broker-mcp failed closed: authority_unavailable" and returns no tool result
The deadline passed before the daemon began serving
The daemon's deadline check before the first byte, or BrokerError::AuthorizationDenied "prepared connection expired"
chio-broker-mcp prints "chio-broker-mcp failed closed: authority_unavailable" and returns no tool result
The request no longer matches its binding or deadline
BrokerError::AuthorizationDenied "prepared execution binding or lifetime changed"

Only a declined preparation reaches the client as a response: whatever its diagnostic code, accepted: false becomes the AuthorizationDenied above in place of a stream. The other three checks close the socket without writing. The host's broker connection reports a failed preparation to the kernel as KernelError::ToolServerError carrying authorization_denied or authority_unavailable.

For a held slot, find the owner and deadline of the existing preparation and wait for its valid release. For an expired preparation, check the original grant and proof, and find out whether capture occurred before seeking new authority for new work. For an expired or changed binding, inspect the original capture and attempt state and compare the submitted frame with the retained binding: an expired transport is no evidence that nothing happened.

Next: run the broker that serves these connections