Information Flow Across Calls
Let agents read sensitive data and keep working, while the kernel refuses to send what they learned to destinations without clearance.
On this page
An agent can read deployment settings, support tickets, and internal reports, pass what it learns through any number of tools, and keep working. Chio lets you grant that access and decide separately where the results may go. The kernel refuses the call that would send what the agent learned to a destination without clearance; reads, searches, and internal writes continue around the refusal.
The mechanism is information-flow control across calls. The host labels data where it enters the session. The kernel joins each label into state it retains for the agent, its delegation lineage, and its session. At a call that sends data out, the kernel compares that retained label with the destination’s clearance and refuses the call when the label exceeds it. The label follows what the agent learned, not the text of the latest request. Paraphrasing a secret, splitting it across tools, or reading public material afterward leaves the retained label where it was.
Follow one read to a public comment
Return to the reviewer from Capabilities. Pull request #421 carries hostile text addressed to AI reviewers. The reviewer’s grant covers read_file and search on /workspace, and a tool that posts review comments on the pull request. A model that follows the text can then make three calls, each permitted: read deploy/settings.yaml, write a summary, and post the database URL from that file in a public review comment.
Each call passes the checks made on it alone. The grant covers the tool, and no guard pattern matches the path or the comment. A sequence rule compares a call with the one before it by tool name, so it cannot tell this read from a harmless one. The deciding fact is what the agent read two calls earlier. Beyond Blocklists places this attack on the ladder of rewrites that a pattern list cannot stop.
After the read of deploy/settings.yaml the retained label is internal. Reading and writing continue; both public posts are refused because internal does not flow to the public clearance.
lattice.rs:65pub fn authorize_egress(
source: &InformationLabel,
clearance: Option<&InformationLabel>,
) -> Result<(), EgressDenial> {
// … Top source, Top or missing clearance: Err
if !source.flows_to(clearance) {
return Err(EgressDenial::FlowViolation);
}
Ok(())
}
Notice where the label changes and where it is compared. It rises at the read and never falls. The kernel compares it with a clearance only at the calls that egress, so writing the summary proceeds while the kernel refuses both posts. A declassification grant for the summary lets that one post through and leaves the label in place, so the kernel refuses the next post.
A label names who may receive the data
An information label states the restrictions that must survive a transfer. Each owner of the data names the readers it permits, and compartments add separation that a destination must keep. In the example, the settings file has one owner, your operations team, which permits only itself as a reader. The figure draws that label as internal.
Labels are ordered by how much they restrict. Data may flow to a destination that keeps every owner, allows only readers each owner already permits, and keeps every compartment. Adding a reader, dropping an owner, or dropping a compartment would broaden disclosure, so the kernel refuses that flow. Public data carries the Bottom label, written ⊥, which has no obligations. The Top label marks restrictive uncertainty, such as history that cannot be reconstructed, and data under it cannot egress. Labels and clearances works through the ordering case by case.
The host classifies data where it enters
A result has its label before the model sees it. Three inputs set that label. A tool flow declaration, which the tool’s publisher signs into its manifest, gives the output_label that every result carries. The operator’s output floor sets the least restriction any result carries. A classifier reports categories it finds in the returned bytes, and the operator’s category map turns each category into a label. The kernel joins these with the state the operation already carries, and it persists the join before releasing the result.
The same declaration states whether the tool sends data outside the session and the clearance its input needs. Neither the data nor the model chooses a label. A document that calls itself public, or an argument that calls a destination internal, has no effect on the declaration, the floor, or the category map. When a classifier fails, the host refuses the call, or withholds the result if the tool already ran, so a broken classifier cannot turn restricted data into public data.
Each read joins the retained label
The kernel combines labels with a join, written ⊔. For an owner present in both labels, the join keeps only the readers both permit. It keeps every owner and every compartment from either side. Joining public data changes nothing, so a public read cannot reset a restricted session.
The kernel retains the joined label at three scopes: the principal that made the call, the delegation lineage it belongs to, and the session. When the next call arrives, its source label is the join of all three with the request’s own label and any operator input floor. Every update is a join, so retained restrictions rise or hold. No ordinary call lowers them.
Egress meets the destination’s clearance
A destination clearance describes the restrictions a destination accepts, in the vocabulary of labels. The operator’s policy supplies it, bound to the account or resource that decides who will see the data, and a tool’s declared input clearance adds another. A request cannot name its own clearance.
A call egresses when its tool declares egress or when the host knows the executor sends data out. Leaving the declaration off does not hide outbound I/O the host knows about. At an egressing call, the kernel checks the source label against every applicable clearance before the external effect.
The check refuses in four cases: a label that does not flow to the clearance, a missing clearance, an unknown source, and a Top clearance. On a kernel built with build_kernel_with_active_defense, the signed receipt records the refusal as evidence from the chio-flow-pre-invocation guard, such as “flow denied: source exceeds policy clearance”. The Linux process host with a native broker returns a signed Deny and names the flow refusal in its warning log. Control egress lists each result.
Restrictions survive delegation and restart
Retained restrictions are persistent taint. The flow store keeps them under a key of tenant, principal, lineage, session, and isolation epoch, outside any worker’s memory. A replacement worker resumes the task under the same obligations.
| What changes | What still carries the internal label |
|---|---|
| The host process restarts | Principal, lineage, and session state; the store reopens with the original read. |
| The agent starts a new session | The new session, through the principal and lineage it shares with the old one. |
| The reviewer delegates to the tester | The tester’s lineage. It can return results to internal destinations and cannot publish them without a declassification grant. |
| A verified isolation epoch opens | The lineage and the fresh session. Only the principal’s label starts again at ⊥. |
An isolation epoch is the flow store’s mechanism for a clean start, and it requires evidence that the store’s configured verifier accepts. A new session identifier or epoch name establishes nothing by itself; the store refuses an unverified switch. Persistent taint runs the restart, session, and delegate cases against the native store.
Declassify one summary, once
Some work exists to publish. The triage agent reads a private support ticket from Harbor Goods and drafts a public issue for the pricing project, and your pricing lead approves the draft. A declassification grant records that approval for one disclosure. It binds the exact request bytes, destination, tool, and purpose; the source label it relaxes and the target label; the authenticated tenant, subject, agent, session, and capability; and a validity window. The kernel consumes it once, at the point of use, and refuses a changed draft, another destination or purpose, and a second send.
The grant leaves the retained label alone, so the agent’s next public post needs ordinary clearance or another grant. Declassification is an embedder API. The host that builds the flow resolver chooses the signing authorities it trusts, and the operator’s policy and the tool’s declaration list the purposes they accept. A host that trusts no authority keeps every such disclosure refused, and a model’s statement that a draft is safe carries no authority.
The research workflow runs this shape on an internal battery study. A delegate’s review reaches the internal destination; the same publish to the public destination exits with status 2, and the public receiver records nothing. After an editor approves the exact summary, the publish carries that grant through native dispatch, which consumes it:
research approve summary.txt \
--as delegate --operation public-reviewed-v1
research publish summary.txt \
--as delegate --to public \
--operation public-reviewed-v1 \
--grant .research/operations/public-reviewed-v1/disclosure.jsonAllow: public-reviewed-v1
Caller: reviewer
Destination: publicThe public receiver records one summary, and the delegate’s retained label still carries the study’s owner and compartment. A retry of the same operation sends nothing new, and reusing the grant under a new operation is refused; Declassification runs both.
What the application supplies
Information flow covers data that passes through mediated calls. The application supplies what the kernel cannot infer:
- labels for each source, through tool flow declarations, output floors, and category maps;
- a clearance for each destination, bound to the account or resource that decides its audience;
- the identities that group work, since calls under one principal or lineage share their restrictions;
- the authorities and purposes allowed to declassify;
- confinement for any process that could open its own network route around the mediated call.
The label records what the workflow read. The kernel does not inspect the model’s reasoning, so once an agent has read restricted data, even a summary that reveals none of it needs a declassification grant to reach a public destination. Prompt injection places information flow beside the other controls a hostile document meets.