Set rules on taint and labels
For: tenant admins and governance reviewers.
The taint-default rule
Every tenant's tool policy has a built-in rule:
{"id": "rule-taint-default", "preset": "taint-default", "match": "*", "when": "job.tainted && tool.outbound", "action": "approve"}
After a job is tainted, a call of a function that can send data out waits for a person. tool.outbound is true for:
- an open-world function, even a read-only one (a URL or a query carries data out);
- a function of class
egressor anyhost.*class (so host writes and shells of a tainted job ask on every call); - a function bound to a connection's token or a credential binding;
- the routine output tool
oz0.output.
The rule gates calls the job's workflow decides. It does not gate a harness session's own tools (its shell, its web fetch, a remote session's provider tools): they run inside the session and ask only when a pre_tool_call hook or the agent's harness.requires_approval names them, or, for a shell command, when a command rule asks, which reads the command and not the job's taint.
A tainted session's WebFetch or curl runs without a yes unless one of those does; its MCP tools (the servers an imported plugin's harness extras give it included) and the job's later calls are gated as any call is.
OZ's own tools (oz.delegate, oz.ask_colleague, the board, oz.hand_over, oz.quarantine) are not outbound: their labels travel with them. A chat channel's post functions are refused to agents anyway.
The rule is a floor: it is not part of the first-match order of the rules, and after the rules and the classes decide, it makes the decision at least approve. It never loosens a decision: a refusal stays a refusal, and an allow rule before it does not let a tainted call through. The approval card says that the taint asked, and why (the reasons, without content); the trace gives the step the same reason.
The rule is shown in the tool policy like any rule. You may narrow its pattern or condition (oz0 tenant tool-policy set --taint-default-when CONDITION, such as a service you trust left out, see the tool policy), or set it to shadow mode to see what it would ask before it acts; each loosens oversight and needs a reason. To turn it off:
oz0 tenant tool-policy set --taint-default off --reason "agents run in a closed lab"
Rules on labels
Conditions read the job's labels at the call, in the runtime and again at the edge:
| Name | What it is |
|---|---|
job.tainted | whether the job read untrusted input |
job.taint | why: a list of {source, tool, call} |
job.labels.level | the level's index in the tenant's list, so >= works |
job.labels.level_name | the level's name, "" while the job read nothing labelled |
job.labels.compartments | the compartments, sorted |
tool.outbound | whether the function can send data out (above) |
# nothing confidential leaves through outbound tools, whatever the job's trust
oz0 policy refuse '*' --when "job.labels.level >= 2 && tool.outbound" --reason "policy 4.2"
# mail from a job that read a web page waits for a person
oz0 policy ask mail.send --when "job.taint.exists(r, r.source == 'open_world')" --reason "phishing"
The edge can only take the runtime's word for what a job read, so every tool call's audit entry records the labels its decision read and the labels of its result, and oz0 evidence verify checks them offline: a call whose recorded labels are lower than an earlier call's, or than an earlier result's, is reported.
A call whose input names no labels claims none: its entry records labels: null, and it is checked as a call that read nothing, so leaving the labels out never takes a call out of the check.
It also bounds each call by the job's start (its run.start entry's labels, and an outside start's untrust: a webhook's or an event's job, and an agent's job a chat message started; a flow a chat message started is not untrusted from its start), by every join the runtime recorded that is not a tool call's own result (run.labels.joined: a child's, a colleague's, the board's, a file's, memory's, an event's, a harness session's start; the first 50 of each run of the job), and by a finished child's labels at its end (its run.output entry).
A quarantine child bounds its parent only through the join the parent recorded, since the parent joins nothing of an answer its contract refused.
A join bounds only the calls scheduled more than two seconds after it, since a join and a call of the same workflow step carry nearly the same time.
A runtime that drops a join before it records it still passes; one that records it and forgets it later does not.
Taint and labels
How a job's labels track what it read: outside text taints the job tree, a tainted job asks before tools that can send data out, a quarantine child reads what its parent should not, and label policies withhold, narrow egress, pick models and wall off compartments by label.
Set clearance, egress and content walls
Set clearance, egress and content walls for tenant admins and governance reviewers.