---
title: "Set rules on taint and labels"
description: "Set rules on taint and labels for tenant admins and governance reviewers."
canonical_url: "https://docs.oz0.ai/govern/taint-and-labels/rules"
---
# Set rules on taint and labels

> Set rules on taint and labels for tenant admins and governance reviewers.

For: tenant admins and governance reviewers.

## The taint-default rule

Every tenant's tool policy has a built-in rule:

```json
{"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 `egress` or any `host.*` 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](https://docs.oz0.ai/govern/command-rules) 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](https://docs.oz0.ai/build/plugins/claude-code-and-agent-plugins#hooks-and-mcp-servers-in-harness-sessions) 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](https://docs.oz0.ai/govern/tool-policy/origins-and-sequences#the-taint-default-rule)), 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:

```bash [Terminal]
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:

<table>
<thead>
  <tr>
    <th>
      Name
    </th>
    
    <th>
      What it is
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        job.tainted
      </code>
    </td>
    
    <td>
      whether the job read untrusted input
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        job.taint
      </code>
    </td>
    
    <td>
      why: a list of <code>
        {source, tool, call}
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        job.labels.level
      </code>
    </td>
    
    <td>
      the level's index in the tenant's list, so <code>
        >=
      </code>
      
       works
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        job.labels.level_name
      </code>
    </td>
    
    <td>
      the level's name, <code>
        ""
      </code>
      
       while the job read nothing labelled
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        job.labels.compartments
      </code>
    </td>
    
    <td>
      the compartments, sorted
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tool.outbound
      </code>
    </td>
    
    <td>
      whether the function can send data out (above)
    </td>
  </tr>
</tbody>
</table>

```bash [Terminal]
# 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.


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
