Chio/Docs
LOGIN · JOIN

Execution security

Carry a permitted request to the component that performs it, and keep the state that recovers its result.

Concept chio@5f63e6e

On this page

An operator can hand an agent's request to the component that performs it and still know, after any interruption, who authorized it, which operation it belongs to, and what may come back.

The kernel admits the request and retains its commitments. The host connects that decision to the tool. The resource or provider performs the effect, and output guards decide what returns. One program may play several of these roles; the boundaries between them stay distinct. The reviewer's read of src/pricing.py in the pricing project follows one call across all of them.

Four boundaries each settle one fact about a call

A call crosses admission, capture, launch, and release in that order. Each boundary establishes its own fact and leaves the next one to a separate observation.

Execution boundariesChoose a boundary.
kernel hostselected executorexact requestsignscapturedispatchFullyEnforcedeffectraw resultguarded outputaKρdfgcallerkernelreceiptexecutorresourceoutput guardkernel hostselected executorexact requestsignscapturedispatchFullyEnforcedeffectraw resultguarded outputaKρdfgcallerkernelreceiptexecutorresourceoutput guard

Admission settles the exact request

The host authenticates the connection and selects the caller context. The kernel evaluates the exact request against current authority, policy, information-flow restrictions, and accounting, and signs the decision.

Still observed separately: that the intended tool launched and its resource path was constrained.

One request from caller to resource and back, with the four boundaries that each establish one fact. The executor stands for an in-process connector or a separate native tool; budgets, approvals, and the recovery paths are left out.
  1. Admission. The host authenticates the connection and selects the caller context. The kernel evaluates the exact request against current authority, policy, information-flow restrictions, and accounting. Whether the intended tool launched, with its resource path constrained, is observed at launch.
  2. Durable capture. Before dispatch, the operation commits its participating authority: the budget, invocation, approval, nonce, and security participants, bound to the original request and its executor. Completion of the external effect is the executor's fact to report.
  3. Enforced native launch. For a separate native tool, the parent observes the required restrictions and the target's execution transition before reporting FullyEnforced. That record covers the executable and its resources; the application result of one call is observed at release.
  4. Guarded release. The raw result returns to the host, passes the output guards and, when required, the original operation's release authority, and only then becomes visible to the caller. Where a flow authority is installed, response classification joins retained restrictions before release, so a completed external effect can coexist with a withheld response. Receipt by the caller is a delivery question, recorded separately.

The retained request binds the arguments and the selected execution path. Rebuilding arguments from mutable application state after admission breaks that binding. A replacement host keeps the original identity and reconciles its retained state under the new serving fence; an old receipt or a successful state lookup grants no permission to dispatch again.

The worker supplies a path and a key; the host supplies the identity

The execution lab contains a trusted Rust host, an authenticated worker client, and a workspace connector that runs inside the host over a copy of the pricing project. The reviewer asks to read src/pricing.py. It holds a connection credential; the host keeps the signing keys and the grant, and reconstructs the tool request itself.

security-execution/src/main.rsrust
let runtime = ProcessRuntime::open(state.join("process.db"), Arc::new(kernel))?
    .with_security_profile(ProcessSecurityProfile {
        tenant_id: "larkfield".into(),
        isolation_epoch_id: "review-epoch-1".into(),
        generation: 1,
    })?;

An agent process supplies the durable caller identity. The host fixes the tenant and isolation generation, and the runtime derives the principal and lineage from the retained process and capability after authenticating the worker. A principal field added to the tool arguments changes nothing.

The process client carries the logical operation key separately from the request's correlation ID, and the host freezes the request associated with that key. A repeated key with different arguments conflicts; a repeated original request resolves the same retained call. The worker cannot replace the issuer, select another process, or reopen the task under a fresh grant, so after a restart the same key resolves the original read even if the file has changed.

Read more in Native secure invocation, which walks through the host constructor and the worker's invocation.

A restart resumes at the last boundary the operation crossed

On restart the host reopens its issuer and subject keys, the original capability, the receipt store, revocations, accounting, and the admission store. It registers the connector and reconciles durable admissions before serving another worker, and a second host cannot take ownership of the same state directory.

What a worker may do next, from where a call’s response was lostwhere the response was lostnot yet dispatchedrefused at admissionno outcome retainedoutcome retainedthe tool and its grantread-only, statelessknown_outcome_onlydecides the unknown casesavedworkeradmissionkerneldispatchkerneleffect?resourceoutcomekernelcheckpointworkerdispatch againstruck: the effect may have happened
unknown effectful outcome
Keep the original identity; reconcile with the effect owner or operator.
The kernel refuses to dispatch it again. A fresh key would be a new intent; it cannot resolve this one.
s=unknown, ¬e  ⇒  a(s)=reconcile(k)s = \text{unknown},\ \lnot e \;\Rightarrow\; a(s) = \htmlClass{cor-parent}{\text{reconcile}(k)}
What a worker may do next, from where a call’s response was lostwhere the response was lostnot yet dispatchedrefused at admissionno outcome retainedoutcome retainedthe tool and its grant (unknown case)read-only, statelessknown_outcome_onlysavedworkeradmissionkerneldispatchkerneleffect?resourceoutcomekernelcheckpointworker
✕ dispatch again: the effect may have happened
unknown effectful outcome
Keep the original identity; reconcile with the effect owner or operator.
The kernel refuses to dispatch it again. A fresh key would be a new intent; it cannot resolve this one.
s=unknown, ¬e  ⇒  a(s)=reconcile(k)s = \text{unknown},\ \lnot e \;\Rightarrow\; a(s) = \htmlClass{cor-parent}{\text{reconcile}(k)}

no outcome retained, not an eligible read: unknown effectful outcome. Keep the original identity; reconcile with the effect owner or operator. The kernel refuses to dispatch it again. A fresh key would be a new intent; it cannot resolve this one.

a(s)={submit(k)s=savednew intent k′s=refusedattemptn+1(k)s=unknown, ereconcile(k)s=unknown, ¬ereplay(k)s=retainede=read-only∧¬ known_outcome_only\begin{gathered} a(s) = \begin{cases} \text{submit}(k) & s = \text{saved} \\ \text{new intent } k' & s = \text{refused} \\ \text{attempt}_{n+1}(k) & s = \text{unknown},\ e \\ \htmlClass{cor-parent}{\text{reconcile}(k)} & s = \text{unknown},\ \lnot e \\ \text{replay}(k) & s = \text{retained} \end{cases} \\[8pt] e = \text{read-only} \land \lnot\,\texttt{known\_outcome\_only} \end{gathered}
In Rust · admission_operation.rs:167
pub enum AdmissionOperationState {
    // …
    DispatchCommitted,
    Finalizing,
    Completed,
    CompensatedBeforeDispatch,
    NotAcceptedAfterDispatchCommit,
    OutcomeUnknownAfterDispatch,
    // …
}
A model of the process host’s recovery table. The kernel tracks admission in finer states than these four windows; the excerpt shows the ones around dispatch. Eligibility for a fresh read comes from the actual tool, grant, and request, so a worker flag cannot widen it.Calls and recovery

Before capture, recovery reconciles the reservations the original operation owns, and a request saved but never dispatched is submitted again under its original key. A refused request is fixed and sent as a new intent under a new key. An operation with dispatch committed and no retained outcome keeps its accounting and is resolved through the selected executor's attempt by its original key: invoke_known_only keeps an unknown outcome from becoming a fresh attempt, and only a read-only tool without request-bound, constrained, or budget state may take a bounded next attempt. A retained result is repaired through its guarded return and replayed; a valid release checkpoint permits that exact guarded response and records nothing about delivery.

Captured interruptiontext
First client: process response was truncated; the operation may have committed
Original-key replay: OutcomeUnknownAfterDispatch
Resource journal: 2 reads total; no third read during recovery

In this capture one read had completed. A second reached the connector's read journal, then the host was killed before the response returned. Startup reconciled that operation, and replay returned a signed refusal naming its retained unknown state, which stops further dispatch under that request while the interrupted read stays in the retained history. Both operations ended with dispatch state terminal and different operation states, completed and outcome_unknown_after_dispatch, so read the operation state and retained outcome together. Report progress from that state: an unknown provider outcome is neither a policy denial nor evidence that nothing happened.

Read more in Dispatch commitments, which maps each state to what an application tells its user.

A native tool reports confinement only after its launch is observed

When the tool runs as a separate native process, admission connects to a verified launch. The live manifest registry binds the publisher's signed manifest, the server and tool identity, the runtime topology, and the operator's launch policy, which sets the resource ceiling.

Confinement bindingChoose an outcome.
signed registryresource ceilingLinux cageLandlock · seccompdispatchfails closedexec observedλlaunch recordFullyEnforcednative_launchsigns at dispatchσπrKpρpublisheroperatorrequestkerneltool processconfined by ChioLinux cageregistryceilingLinux cageLandlock · seccompdispatchfails closedλlaunch recordFullyEnforcednative_launchsignsσπrKpρpublisheroperatorrequesttoolconfined by ChioLinux cage

The receipt reads confined because the launch record is bound at dispatch

The publisher's signed registry and the operator's resource ceiling meet at admission, and the host retains the exact helper, target, and resource descriptors. The parent observes the target exec under the compiled restrictions and records a FullyEnforced launch record. At dispatch the kernel binds that record into the receipt as native_launch; a verifier checks its digest and signer against pinned kernel keys. Verified tool launch.

One native tool call from request to receipt: the publisher and operator authorities, the Linux cage boundary, and the launch record the kernel binds at dispatch. The descriptor table, the syscall allowlist, and worker-lane confinement are left out.Process confinement

Preparing a launch retains the helper, the exact target, and its descriptors. On the Linux cage, the parent observes the required restrictions and the target's exec before reporting FullyEnforced, and the kernel binds that native_launch record into the receipt at dispatch, verified against pinned kernel keys. A call with no launch reference reads "not confined by Chio" with its tool_origin reason. A record short of FullyEnforced, or a target that exits before dispatch, fails dispatch closed: the host reaps the child and never relaunches the tool unrestricted.

On the installed Linux reference runtime, the edge unit runs enforcement preflight before it starts. Its findings name four probes:

ProbeChecksOn failure
security.platformThe host can enforce the cage mechanismsMove to a supported host
security.bearer_rolesAuthentication roles are present and distinctRepair credential delivery and role assignment
security.native_launchSigned inputs and pinned signers match this server and commandCheck manifest, launch policy, stage, target, and arguments together
security.durable_storesRetained stores meet their requirementsDiagnose the named store; keep authority history

Parsing a policy cannot supply missing enforcement, and a platform-only report says nothing about this launch. A Shadow or Disabled launch that passes without --require-enforcement establishes no containment. The complete preflight invocation uses the edge's credential environment, independent manifest and cage-policy signer pins, session store, and exact wrapped command.

Inspect the service result, or run the complete preflight on its own
Terminalbash
sudo systemctl start chio-mcp-edge.service
systemctl show chio-mcp-edge.service --property=ActiveState,SubState,Result
journalctl -u chio-mcp-edge.service -b --no-pager

To run the preflight independently of service startup, set CHIO_MANIFEST_PUBLIC_KEY and CHIO_CAGE_POLICY_SIGNER from the independently reviewed provisioning record. The systemd credentials supply the same four distinct bearer roles as the edge, and the command preserves the exact reader target and the pricing project root.

Terminalbash
sudo systemd-run --wait --pipe --collect \
  -p LoadCredential=session-token:/etc/chio/credentials/edge-session-token \
  -p LoadCredential=admin-token:/etc/chio/credentials/edge-admin-token \
  -p LoadCredential=control-token:/etc/chio/credentials/edge-control-token \
  -p LoadCredential=workload-token:/etc/chio/credentials/edge-workload-token \
  -p User=chio-edge \
  /usr/local/bin/chio security supervise --exec \
  --credential-env CHIO_AUTH_TOKEN=session-token \
  --credential-env CHIO_ADMIN_TOKEN=admin-token \
  --credential-env CHIO_CONTROL_TOKEN=control-token \
  --credential-env CHIO_REMOTE_AUTHORITY_WORKLOAD_TOKEN=workload-token \
  -- /usr/local/bin/chio --session-db /var/lib/chio-mcp-edge/sessions.sqlite3 \
  security preflight --require-enforcement \
  --signed-manifest /etc/chio/mcp-edge/launch/signed-manifest.json \
  --manifest-public-key "$CHIO_MANIFEST_PUBLIC_KEY" \
  --cage-policy /etc/chio/mcp-edge/launch/cage-launch-policy.json \
  --cage-policy-signer "$CHIO_CAGE_POLICY_SIGNER" \
  --server-id workspace \
  -- /usr/local/libexec/chio/chio-tool-repo-reader --root /srv/larkfield/pricing

The reviewer reads src/pricing.py once, even across a restart

Pull the execution lab. It builds against the published Chio crates with Rust 1.94.1 or newer, and the chio commands come from the installed CLI. The project holds the complete host, the worker client, the lockfile, and a copy of the pricing project under repository/.

Prepare the example commands

Build the lab once, then put its binaries on the command path. Repeat the export in each new terminal opened in security-execution/.

Terminalbash
chio new security-execution --example security-execution
cd security-execution
cargo build --locked --bins
export PATH="$PWD/target/debug:$PATH"

Start the host in the first prepared terminal. It retains its state in .chio/ and prints readiness after publishing the worker connection.

Host terminalbash
security-execution-host .chio repository
Terminal 2bash
review read-pricing-v1 src/pricing.py read-pricing.receipt.json \
  < .chio/connection.json
chio receipt verify --input read-pricing.receipt.json \
  --trusted-kernel-pubkey .chio/kernel.pub
Returned value
{
  "bytes": 97,
  "path": "src/pricing.py",
  "sha256": "effbf40757e3cd23d8e72b86b8b515cbbb3e975344cc985c68308445b67ac421",
  "text": "TIER_PERCENT = 10\n\n\ndef discount(price):\n    return round(price * (100 - TIER_PERCENT) / 100, 2)\n"
}

You should see discount() with its 10 percent tier, the file's byte length, and its SHA-256 digest. The worker saves the original receipt bytes before interpreting that value, and the verifier checks the signature against the host's separately retained public key; displaying or explaining a receipt checks nothing.

Terminal 2bash
chio --receipt-db .chio/receipts.db receipt list \
  --admin-all --tool-server workspace --limit 5
jq '
  .metadata.admission_operation | {
    projected_state, projected_dispatch_state, projected_operation_version,
    retained_dispatch_commit
  }
' read-pricing.receipt.json
Record excerpt
{
  "projected_state": "completed",
  "projected_dispatch_state": "terminal",
  "projected_operation_version": 8,
  "retained_dispatch_commit": {
    "committed_version": 6,
    "provider_attempt": {
      "transport_id": "kernel-tool-server:workspace"
    }
  }
}

The dispatch committed at version 6 and the completed operation was projected at version 8, under the executor binding kernel-tool-server:workspace. Both version numbers are observations from one run. The excerpt selects fields from the signed receipt; verify the complete receipt, which also retains the operation ID, request binding, provider-attempt identity, and serving fence. --admin-all reads this host's whole store; a tenant-scoped deployment selects its authenticated read boundary instead.

Stop the host with Ctrl-C and start it again with the same command; it publishes a new connection credential for the retained process. Then run the original call again.

Terminal 2bash
review read-pricing-v1 src/pricing.py read-pricing.receipt.json \
  < .chio/connection.json
cat .chio/resource-reads.jsonl

You should see the same receipt, byte for byte, and no second read in the journal. A new revision of src/pricing.py leaves the result for read-pricing-v1 unchanged; choose a new key only for new work.

The archive's interruption instructions add an operator-controlled pause after the source read and before the connector returns; preserve the state if it leaves an unknown outcome. A read shows a retained result surviving host replacement; deduplicating a mutating provider operation is the provider's contract. For an embedded integration, observe_dispatch.rs reads dispatch_state() and the commit's committed_version around a call as advisory snapshots: an unavailable observation stays an observation error, and neither read acquires mutation or dispatch authority.

A path outside src/ stops before the connector opens it

The host grants the reviewer only workspace/read_file under src/, so the project's README.md one level up is out of scope. The connector opens path components relative to a directory it already holds, refuses symlinks and parent traversal, and bounds the returned file to 64 KiB.

security-execution/src/repository.rsrust
for (index, component) in components.iter().enumerate() {
    let Component::Normal(name) = component else {
        anyhow::bail!("relative normal path components required")
    };
    let name = CString::new(name.as_encoded_bytes())?;
    let final_component = index + 1 == components.len();
    let flags = libc::O_RDONLY
        | libc::O_CLOEXEC
        | libc::O_NOFOLLOW
        | libc::O_NONBLOCK
        | if final_component {
            0
        } else {
            libc::O_DIRECTORY
        };
    // Each component is opened relative to an already held directory.
    // O_NOFOLLOW excludes symlink substitution at every step.
    let fd = unsafe { libc::openat(current.as_raw_fd(), name.as_ptr(), flags) };
    ensure!(
        fd >= 0,
        "open repository component: {}",
        std::io::Error::last_os_error()
    );
    current = unsafe { File::from_raw_fd(fd) };
}

Each openat starts from the held directory descriptor, and O_NOFOLLOW refuses a substituted symlink at every component, so a permitted-looking src/link.rs cannot redirect the read. The connector also checks the file type and response size. These checks complement the grant; isolating arbitrary worker code from the rest of the Unix account is the host's job, through confinement.

Terminal 2bash
review read-parent-v1 ../README.md refused.receipt.json \
  < .chio/connection.json
cat .chio/resource-reads.jsonl

You should see a signed denial and an unchanged journal. The connector records an entry only after reading a file, which gives an observation independent of the verdict: a denial accompanied by another read would expose a broken integration. The journal is a diagnostic record, separate from signed execution evidence.

The complete scope selected by this host
security-execution/src/kernel.rsrust
fn scope() -> ChioScope {
    ChioScope {
        grants: vec![ToolGrant {
            server_id: "workspace".into(),
            tool_name: "read_file".into(),
            operations: vec![Operation::Invoke],
            constraints: vec![Constraint::PathPrefix("src/".into())],
            max_invocations: None,
            max_cost_per_invocation: None,
            max_total_cost: None,
            dpop_required: None,
        }],
        ..Default::default()
    }
}

Every route to the resource passes through one entry point

A resource is governed only when every path from the agent to it crosses the kernel. A writable mount, a separate native tool, or an inherited credential each opens a route that skips it.

Routes from the agent to the resource, and whether every one crosses the kernelcut at Kwritable mounttnative toolinherited credentialrequestadmitteddispatchaKdRagentkerneladapterrepository/governed
mandatory(R)  ⟺  ∀ π∈Π(a,R): K∈π\text{mandatory}(R) \iff \forall\, \pi \in \Pi(a, R):\ \htmlClass{role-seal}{K} \in \pi
π0=(a,K,d,R)∋K\htmlClass{role-seal}{\pi_0 = (a, K, d, R) \ni K}
π1=(a,R)mount∌K\pi_1 = (a, R)_{\text{mount}} \not\ni K
π2=(a,t,R)∌K\pi_2 = (a, t, R) \not\ni K
π3=(a,R)cred∌K\pi_3 = (a, R)_{\text{cred}} \not\ni K
mandatory(R)=1\text{mandatory}(R) = \htmlClass{role-seal}{1}
every route to R crosses the kernelclick a dashed route to grant it
Routes from the agent to the resource, and whether every one crosses the kernelcut at Kwritable mounttnative toolinherited credentialaKdRagentkerneladapterrepository/governed
mandatory(R)  ⟺  ∀ π∈Π(a,R): K∈π\text{mandatory}(R) \iff \forall\, \pi \in \Pi(a, R):\ \htmlClass{role-seal}{K} \in \pi
π0=(a,K,d,R)∋K\htmlClass{role-seal}{\pi_0 = (a, K, d, R) \ni K}
π1=(a,R)mount∌K\pi_1 = (a, R)_{\text{mount}} \not\ni K
π2=(a,t,R)∌K\pi_2 = (a, t, R) \not\ni K
π3=(a,R)cred∌K\pi_3 = (a, R)_{\text{cred}} \not\ni K
mandatory(R)=1\text{mandatory}(R) = \htmlClass{role-seal}{1}
click a dashed route to grant it

Every route from the agent to the repository crosses the kernel, so admission is mandatory.

In Rust · lib.rs:25913 lines
/// Invoke a bridged tool. A dispatcher is required so the kernel cannot
/// sign successful receipts for simulated side effects.
pub fn invoke_tool(&self, tool_name: &str, arguments: Value) -> Result<Value, BridgeError> {
    // …
    if let Some(dispatcher) = &self.dispatcher {
        // … egress contract checks
        let response = dispatcher(&binding.method, &url, &arguments)?;
        // …
    } else {
        Err(BridgeError::Kernel(
            "OpenAPI bridge requires a dispatcher for live tool invocation".to_string(),
        ))
    }
An illustrative deployment. The host, not Chio, enforces the cut: it decides which processes run and which credentials, handles, and mounts they receive. An embedded library cannot isolate itself from a compromised host.Architecture

A credential broker closes the third route. The tool presents a reference and a constrained signed request; the broker resolves the secret, contacts the provider, and retains the bounded outcome. Local launch evidence identifies the executable and its resource contract, while a remote provider owns its final behavior: a lost provider response may need the provider's authenticated status or idempotency contract, and neither a successful socket write nor a copied response proves remote completion. Choose the entry point that owns your effect:

Read more in Bypass resistance before adding a shell, an alternate SDK, a credential mount, or a delegated executor.

Next: embed the kernel and invoke a connector