Chio/Docs
LOGIN · JOIN

Service roles

Give each reference-runtime service its own account and credential, pin the broker's peers, and start the services in authority order.

Deployment chio@5f63e6e

On this page

Each reference-runtime service runs under its own Unix account. The edge holds four distinct bearer credentials, the broker serves one pinned client UID, and every unit starts only after the authority it depends on can answer.

A role combines a responsibility, a process identity, and access to particular state: the trust control plane issues authority, the edge mediates sessions, the broker owns provider credentials, and the key-log services retain authority history. Accounts, signing keys, and protocol credentials each enforce part of that separation.

Start
Service accountsChoose a view.
separate persistent deviceRequires=After=keyring readiness, when configuredretrieslaunchesno preflightno preflightanchor rootfive instances, one UIDκtrust controlsstoresgKMCP edgeprepo readerA′same devicegAreceipt anchorbsecret brokerw₁w₂witnessesw₃v₁audit monitorsv₂separate persistent deviceRequires=After=keyring readiness, when configuredretrieslaunchesno preflightno preflightanchor rootone UIDκtrust controlsstoresgKMCP edgeprepo readerA′same devicegAreceipt anchorbsecret brokerw₁w₂witnessesw₃v₁audit monitorsv₂

Units order two dependencies; readiness checks the rest

The edge declares Requires= on the trust control plane, and each audit monitor starts after all three witnesses. The broker and the trust control plane carry no unit ordering on other services: a missing authority fails their own startup, and Restart=on-failure retries it. Service roles.

The packaged reference runtime on one Linux x86_64 host: four service accounts, with three witnesses and two audit monitors sharing chio-keylog, the edge's Requires= on the trust control plane, and the separate device that holds the edge's receipt anchor. Positions are illustrative; credentials, the session and attempt stores, and the active-defense host are left out.Reference runtime

1. Create one account per service family

The reference runtime's sysusers.d declarations create four accounts, one per service family, so a process compromised under one account cannot read another's state or credentials. Install them with systemd-sysusers as part of the installed layout.

AccountRunsStarts when
chio-trustTrust control planeIts own runtime is ready; any keyring is operator-configured
chio-edgeRemote MCP edgeTrust control is up (Requires=) and preflight passes
chio-keylogKey-log witnesses and audit monitorsWitnesses: local storage. Monitors: all three witnesses
chio-brokerSecret brokerHolds reconcile and custody verifies

You should see the four accounts in /etc/passwd. The three witness instances and two audit instances share chio-keylog: each has its own state directory and credential, but one UID gives them no isolation from each other. Where witness independence matters, run each witness under its own account or on its own host.

2. Give the edge four distinct bearer roles

The remote MCP edge carries four bearer credentials: the session and admin credentials it accepts, and the control and workload credentials it presents to the trust control plane. chio security preflight and the edge's launch validation both refuse two roles that share a value, and any value that is empty, padded, or carries a control character. Prove it with dummy values first:

The comparison script, compare_bearer_roles.sh
security-host/compare_bearer_roles.shbash
#!/usr/bin/env bash
# A disposable preflight comparison with dummy values, never live credentials.
set -eu
umask 077
: "${CHIO_EVIDENCE:?Set a new private evidence directory}"
if [ -e "$CHIO_EVIDENCE" ]; then
  echo 'Refuse to reuse evidence' >&2
  exit 2
fi
mkdir -m 0700 "$CHIO_EVIDENCE"
run_case() {
  name=$1
  admin=$2
  if CHIO_AUTH_TOKEN=fixture-session CHIO_ADMIN_TOKEN="$admin" \
     CHIO_CONTROL_TOKEN=fixture-control \
     CHIO_REMOTE_AUTHORITY_WORKLOAD_TOKEN=fixture-workload \
     chio security preflight --json > "$CHIO_EVIDENCE/$name.json" \
                                  2> "$CHIO_EVIDENCE/$name.stderr"; then
    status=0
  else
    status=$?
  fi
  printf '%s\n' "$status" > "$CHIO_EVIDENCE/$name.exit"
}
run_case distinct fixture-admin
run_case reused fixture-session
python3 "$(dirname "$0")/compare_bearer_roles.py" "$CHIO_EVIDENCE"

Download compare_bearer_roles.sh and compare_bearer_roles.py into one directory, then run the script with the installed chio, Bash, and Python 3.11. It runs preflight twice with dummy values, changing only the admin value, and keeps both reports and exit statuses in a new private directory:

Terminalbash
export CHIO_EVIDENCE="$HOME/chio-role-evidence"
bash compare_bearer_roles.sh
Output
OK role probe: four distinct dummy roles
REFUSE role probe: reused session/admin value, exit 1
OTHER PROBES retained separately; no launch, store or physical readiness claim

You should see the distinct case pass with "4 bearer roles are usable and distinct" and the reused case exit 1 with "session and admin credentials share one value". The role probe runs on any host; the other probes stay in the retained reports, and an unchecked launch or store makes no claim about them.

3. Pin the broker to its service and client UIDs

The secret broker's daemon config, BrokerDaemonConfig, pins two identities. Set trusted_service_uid to the account that owns the daemon, its config, and its key files, and authorized_client_uid to the one IPC client it serves:

crates/security/chio-secret-broker/src/daemon_runtime.rs196–225rust
pub struct BrokerDaemonConfig {
    pub schema: String,
    pub deployment_id: String,
    pub broker_instance_id: String,
    pub tenant_scope: String,
    pub audit_runner_id: String,
    pub trusted_audit_runner: PublicKey,
    pub ipc_socket_path: PathBuf,
    pub authority_socket_path: PathBuf,
    pub trusted_capability_issuer: PublicKey,
    pub trusted_authority: PublicKey,
    pub broker_identity: PublicKey,
    pub broker_audience: String,
    pub parent_audience: String,
    pub provider_adapter_id: String,
    pub provider_adapter_version: u32,
    pub provider_placement: ProviderPlacementConfig,
    pub trusted_service_uid: u32,
    pub authorized_client_uid: u32,
    pub ipc_read_timeout_ms: u64,
    pub ipc_write_timeout_ms: u64,
    pub authority_timeout_ms: u64,
    pub maximum_clock_skew_seconds: u64,
    pub maximum_liveness_snapshot_age_seconds: u64,
    pub maximum_revocation_snapshot_age_seconds: u64,
    pub databases: BrokerDaemonDatabaseConfig,
    pub enterprise_migration: BrokerDaemonMigrationConfig,
    pub admin: BrokerDaemonAdminConfig,
    pub privileged_audit: BrokerDaemonPrivilegedAuditConfig,
}

You should see the broker start only when its config, like every inherited key file, is owned by trusted_service_uid; a foreign owner stops it with custody_unavailable. Once serving, it compares each peer's UID with authorized_client_uid. Credential custody covers the peer-identity check over the broker's socket and the independently configured authority keys.

4. Start the services in the order their authority requires

Each service starts after what it consumes can answer:

  1. Custody. Prepare sealed key custody before the services that consume it.
  2. Authorities. Establish authoritative holds and the key-history services before the broker or the trust control plane depends on them. Open each store under its service identity, verify its anchors, and reconcile retained operations.
  3. Configuration. Install the policy, manifest registry, signing authority, and runtime bundle the entrypoint needs.
  4. Response authority. Validate the response snapshot and the combined deployment before publishing it.
  5. Serving host. Let the host verify its executor profile before it receives traffic.

The reference runtime starts the units in this order. Units order only two dependencies: witness units wait on local storage and each audit unit wants and starts after all three witnesses, and chio-mcp-edge.service declares Requires=chio-trust-control.service. The trust control plane and the broker carry no unit ordering on other services; an operator-enabled keyring or an unavailable broker authority fails their own startup, and Restart=on-failure retries.

The shipped trust control unit configures no keyring. Adding --authority-keyring-config requires --authority-seed-file and --authority-keyring-receipt-anchor-root in place of --authority-db, and startup then waits for every configured witness and audit monitor. The active-defense host checks the response authority's signed health first; node lifecycle gives the drain sequence.

5. Verify each unit's identity and readiness

Read the installed unit, not a process name. systemctl cat shows the effective manager configuration, local overrides included; the packaged unit is deploy/reference-runtime/systemd/chio-mcp-edge.service.

Terminalbash
systemctl cat chio-mcp-edge.service
systemctl show chio-mcp-edge.service \
  -p User -p Group -p Type -p Requires -p After \
  -p ActiveState -p SubState -p Result -p ExecMainStatus
sudo journalctl -b -u chio-mcp-edge.service --no-pager

You should see User=chio-edge, Type=notify, and Requires=chio-trust-control.service. The supervisor announces readiness only after the configured authenticated health request succeeds, and the ExecStartPre= preflight requires enforcement over the signed launch. An active process that has not notified is still starting; the journal names the boundary that failed. A listening socket or a supervisor's running state shows only that something accepted a connection.

Then stop one dependency: the protected path keeps its failure or unknown state and finds no other route, so the provider sees no new call, the file stays unchanged, or the suspended session stays constrained.

A shared host still needs separate keys and files

Distinct bearer values stop role substitution at a protocol boundary, but two processes that can read each other's private files still share authority: rewriting another service's configuration influences it without its admin token. Map each service to the state it reads and changes and the credentials it presents, then check ownership, peer credentials, and configured roots.

RoleKeep separateVerify before readiness
Agent and toolProvider credentials and authority seedsOnly intended IPC and resource grants are reachable
BrokerTool identity and response authorityPrivate endpoint, key custody, durable attempts, live authority
Response authorityBroker, issuer, and receipt signing keysReviewed store and deployment digests, pinned client
Key-log servicesOperator, witnesses, and auditorsDistinct roots, service identities, storage, fresh challenges
Recovery workersEach other's lease ownershipValid serving fences and original operation state

Keep the receipt, issuer, broker, witness, and response-authority keys distinct even when services share a host: each key's artifacts establish a different claim. The reference accounts are a starting composition; choose separate accounts or hosts wherever the threat model requires independent custody. The ownership topology draws these boundaries.

A misconfigured role stops its service at launch

Preflight, the broker, and the edge each refuse a role that breaks separation, before any request is served:

RefusalConditionOwner
Probe security.bearer_roles, exit 1: "bearer roles are not separated: the session and admin credentials share one value", naming the two roles
Two of session, admin, control, workload share a value (RoleDefect::Reused)
Preflight
The same probe: "bearer roles are not separated: the control credential is padded, empty or carries a control character", naming the role
A role value is padded, empty, or has a control character (RoleDefect::Unusable)
Preflight
"chio-secret-brokerd failed closed: custody_unavailable" on stderr, then exit
A custody fault, such as a config or key file not owned by trusted_service_uid (BrokerError::Custody)
Broker
chio mcp serve-http: "--control-token and --auth-token must be distinct bearer credentials", or naming --admin-token
The control token repeats the session or admin token
Edge
chio mcp serve-http: "--auth-token and --admin-token must be distinct bearer credentials"
The session and admin tokens are equal
Edge
chio mcp serve-http: "--auth-token must be non-empty, unpadded, and control-free", naming the flag
A bearer flag is padded, empty, or has a control character
Edge
chio mcp serve-http: "remote authority workload token must be distinct from service, session, and admin tokens"
The workload token repeats the control (service), session, or admin token
Edge

Repair a reused or malformed bearer value at its LoadCredential= input and rerun the probe. A broker socket with the wrong owner or pins supplies no trusted peer; check the configured UIDs, the socket's custody, and the authority key. Units that start in order without runtime authority are repaired at the authority owner: signed health, stores, pins, and recovery prerequisites.

Where the role checks live

ROLE_VARIABLES (roles.rs:17) names the environment variable each role reads, from CHIO_AUTH_TOKEN for the session role to CHIO_REMOTE_AUTHORITY_WORKLOAD_TOKEN for the workload role; RoleDefect::Reused and RoleDefect::Unusable are at roles.rs:26. At launch, validate_hosted_bearer_roles (authority_mode.rs:14) refuses a control token that repeats the session or admin token, and the remote-authority check refuses a workload token that repeats the control (service), session, or admin token. BrokerDaemonConfig declares trusted_service_uid and authorized_client_uid at daemon_runtime.rs:213-214, and BrokerDaemonConfig::load refuses a config file not owned by trusted_service_uid at :266-269.

Next: protect the state these services hold