Chio/Docs
LOGIN · JOIN

Custody model

Find which service holds each secret and signing key, who may ask it to act, and what evidence each action leaves.

Concept chio@5f63e6e

On this page

An agent can use a provider credential it never receives, and obtain signed evidence from a key it never holds. Three custodians make that possible: each keeps one class of secret, performs one bounded action for one authenticated caller, and records what it did.

Choose the custodian from the authority the work needs: access at a provider, a statement by the kernel, or a change to which signing key is current. The secret broker, the kernel's signing authority, and the keyring each own one of those, and each returns evidence of its action in place of the secret.

CustodiansChoose a custodian.
callercustodianevidenceadmitted requestleavesdβρexecutorsecret brokerprovider credentialsbroker receiptkernel operationleavesKσρkernelsigning authorityreceipt signing keyssigned receiptPOST /v1/authorityleavestκctrust control planekeyringsigning keys and historykey-log checkpointcustody boundarydβρexecutorbrokerreceiptKσρkernelsignerreceipttκccontrol planekeyringcheckpoint

The broker uses a provider credential for an admitted operation

The broker encrypts provider credentials at rest and decrypts them inside its own process. Its tenant master key arrives through an inherited sealed descriptor, consumed once. The executor receives provider output and signed execution evidence.

Three custodians, each with the caller it acts for and the evidence it leaves. Key storage backends, remote and hardware signers, and the witnesses that sign key-log checkpoints are left out.

Each custodian holds one class of secret and acts for one caller

A private key can still be misused through its API. A signer that signs whatever statement a caller selects, or a broker that sends whatever request a caller writes, lets clients spend the key without ever reading its bytes. Custody therefore pairs a protected secret with a constrained interface for asking its owner to act.

  1. Secret broker. Holds provider credentials and uses them for the executor of an admitted operation. It leaves a broker execution receipt.
  2. Kernel signing authority. Holds the receipt signing keys and signs for the kernel. It leaves signed receipts.
  3. Keyring. Holds the trust control plane's signing keys and their history. It leaves key-log events and checkpoints.

The roles stay apart because they authorize different things: a provider credential acts at an external service, a receipt key authenticates the kernel's statement about an operation, and a key-history authority decides which signing identity is current. Combined, a compromise is harder to contain and the evidence harder to read. Separate custody also needs defined behavior when a custodian is unavailable, including recovery of work it already accepted.

Read more in Secrets and signing keys, which provisions and rotates each key class, and Cluster authority, which carries these keys across peers.

The secret broker uses a credential and returns only output and evidence

The broker encrypts provider credentials at rest and decrypts them inside its own process. The supervisor delivers its tenant master key through an inherited sealed descriptor, consumed once. The executor receives provider output and signed execution evidence, and a verbatim match of the credential in response headers or body becomes a retained failure receipt, withheld from the caller.

Follow one admitted issue request across the boundary: the agent supplies a credential reference, the host calls the pinned broker, and the broker owns provider authentication. The host's final call takes the supervisor-owned BrokerIpcClientConfig, the original request, and an evidence writer that commits to the host's private journal. It returns bounded output and a signed success or failure record, and the client has no secret-export call.

The host's final use of a captured operation
security-host/broker_client.rsrust
use chio_core_types::SigningBackend;
use chio_secret_broker::ipc_client::{
    BrokerIpcClient, BrokerIpcClientConfig, BrokerIpcExecutionOutcome,
};
use chio_secret_broker::protocol::BrokerExecuteRequest;
use chio_secret_broker::Result;
use std::sync::Arc;

/// Called by the configured host for an already registered, captured original
/// operation. Configuration comes from the supervisor's independent peer/key pins.
pub fn use_original_broker_request(
    config: BrokerIpcClientConfig,
    registration_authority: Arc<dyn SigningBackend>,
    original: &BrokerExecuteRequest,
    persist_private_outcome: impl FnOnce(&BrokerIpcExecutionOutcome) -> Result<()>,
) -> Result<BrokerIpcExecutionOutcome> {
    let client = BrokerIpcClient::new(config, registration_authority)?;
    let outcome = client.execute_evidenced(original)?;
    // A signed failure is evidence too. Persist it before the caller interprets
    // provider output. A transport error remains an unresolved original attempt.
    persist_private_outcome(&outcome)?;
    Ok(outcome)
}

Download broker_client.rs. The expected peer and receipt signer are provisioned independently; deriving either from an untrusted response would remove the custody check. The signed issue comparison shows how body, endpoint, and credential version are bound.

Change the expected broker process, UID, GID, or receipt key, and the client refuses authentication or outcome verification. If the socket fails, keep the original request and its uncertainty: a new connection, credential version, or attempt ledger says nothing about whether the first provider call happened, and recovery follows the custodian that owns the retained operation. A copy of the same provider token in the tool's environment is a route around the boundary, so host composition removes it along with any direct network path to the provider.

Read more in Credential broker, Prepared connections, Credential custody, and Daemon credentials.

Signing keys stay with the kernel and the keyring

The kernel owns its receipt, threshold-proposal, and session-report signing authority. Callers ask through kernel operations and never reach the private signing authority directly; Signing authority selects the backend and names the separately owned artifact roles.

The trust control plane's keyring retains signing history and stamps each signature with its current signing epoch. A rotation makes the previous epoch stale, and the store refuses stale-epoch signatures, so two keys are never valid at once. The keyring serves authority through the activated, generation-fenced selector, and a verifier checks each kernel receipt under the receipt identity it pinned independently. Witnessed rotation changes the authority and Key log witnesses verify its history.

A custodian acts only for the caller it was provisioned to trust

For the broker, the operator provisions the trusted service UID and the authorized client UID independently. Key files belong to the service UID, the Linux IPC endpoint checks the connecting client's UID, and the client checks the broker's expected peer identity. Keyring rotation enters through POST /v1/authority, and the edge's administrative rotation forwards there when a control plane is configured.

RefusalCustodianCondition
"chio-secret-brokerd failed closed: custody_unavailable" on stderr, exit status 1 (BrokerError::Custody)
Secret broker
A custody check on a key descriptor or on the daemon's own sockets failed
Connection closed with no response; the client reports AuthorityUnavailable("broker IPC read failed: ...") or AuthorityUnavailable("broker IPC write failed: ...") (BrokerError::AuthorizationDenied "broker IPC client UID is not authorized")
Secret broker
The peer UID differs from authorized_client_uid, checked by the daemon on Linux
AuthorizationDenied("broker IPC peer identity does not match production configuration") from connect_authenticated
Secret broker
The broker's observed process, user, and group IDs differ from the client's configured peer
POST /v1/authority returns 503 with {"error": "witnessed capability authority rotation failed"} (KeyringError::PredecessorMismatch, KeyringError::InvalidWitnessActivation, or another keyring error)
Keyring
An event fails to extend its log, an activation lacks valid witness signatures, or another rotation stage failed

The daemon stops on a custody failure, and the client sees a closed connection when the daemon rejects its UID. Repair a refusal with these checks enabled: restore the provisioned identity, key file, or witness set, and leave the check in place.

Custodian source reference

Each entry names the types, files, and lines that implement one custodian's contract.

The secret broker holds provider credentials

The tenant master key travels neither as a path nor as an environment value. SealedKeyFd::from_inherited_file (encrypted_blob_backend.rs:34) wraps an inherited file descriptor and the owner UID the daemon expects, and SealedKeyFd (:26) consumes that descriptor exactly once before its bytes are read. The read succeeds on Linux and Android; every other platform refuses it with BrokerError::Custody. The executor reaches the broker over BrokerIpcClient (ipc_client.rs:70).

crates/security/chio-secret-broker/src/encrypted_blob_backend.rs26–30rust
pub struct SealedKeyFd {
    file: File,
    expected_owner: u32,
    consumed: bool,
}

BrokerExecuteResponse (protocol.rs:557) carries the upstream status, headers, and body the provider returned, plus execution evidence and a signed receipt. Before either body or headers leave the broker, they are scanned for the credential's own bytes. A verbatim match is withheld: the broker signs and persists a failure receipt with the diagnostic code chio.broker.response_rejected (BrokerError::ResponseRejected), BrokerIpcClient::execute reports AuthorizationDenied("chio.broker.response_rejected"), and on the prepared path chio-broker-mcp prints "chio-broker-mcp failed closed: authorization_denied" and returns no tool result.

The kernel signing authority signs receipts

KernelSigningAuthority (crates/kernel/chio-kernel/src/kernel/signing_authority.rs:11) holds the keys that sign receipts, threshold proposals, and session reports. It is declared pub(super), so only the kernel module reaches it. Post-quantum signatures covers the boot-selected cryptographic floor and the hybrid signer an embedder can install, and Remote signing covers signers that hold their key outside the host.

The keyring proves key history

The control plane composes KeyringSigningRouter (router.rs:135) like any trust-control signing authority: its open_enterprise constructor (:173) is called from the enterprise keyring runtime (chio-control-plane/src/keyring_runtime.rs:942) to build the router the trust-control service signs through. Every signature carries the router's current signing_epoch, and the store refuses a signature stamped with a stale epoch. Key transparency covers how that history is built.

A Rust embedder that calls chio-keyring directly receives the typed KeyringError variant, such as PredecessorMismatch ("event predecessor hash mismatch") or InvalidWitnessActivation ("witness threshold or checkpoint binding is invalid").

Who may ask a custodian to act

The broker pins trusted_service_uid and authorized_client_uid (daemon_runtime.rs:213-214) before it runs. validate_key_file_identity (:949, called for both keys at :484-485) requires every inherited key file to be owned by the trusted service UID, on any Unix host. validate_broker_peer_uid (service_parts/part_03.rs:1069) checks the connecting client's UID over the IPC socket and requires Linux. The daemon runs only on Linux: the sealed key read refuses other platforms with "chio-secret-brokerd failed closed: custody_unavailable", and on Android the IPC endpoint refuses to bind with "chio-secret-brokerd failed closed: authority_unavailable".

AUTHORITY_PATH (chio-control-plane/src/trust_control/service_types/paths.rs:10) is /v1/authority, and its POST handler, handle_rotate_authority (authority_handlers.rs:62), is the one caller of rotate_authority_for_state (report_validation.rs:783) in the control plane. It is the only route that rotates a keyring key; the MCP edge's administrative rotation forwards to it when a control plane is configured.

Next: let an agent use a credential it never holds