Guardrails

Check classified text

Understand classification matching and refusal behaviour.

For: tenant administrators and plugin users.

Understand classification matching and refusal behaviour.

Classified text

A value that carries a security-classification marking of the classified.core pack is refused before a model reads it: a job whose input is marked does not start, and a tool's result that is marked reaches the model as a refusal ("withheld: ... it is marked 1 eu restricted, and classified text never reaches a model here"). Nobody is asked, since a person's yes would pass the text on to the model. The reason names the marking, never the text.

The check runs first, at job_start, post_tool_call and job_end; when it cannot run in time, or a value is longer than it reads (16 MiB), the value is refused. A pack of kind classified (your own, or a sector pack) adds markings; its only action is refuse.

The check reads keys as well as values, and reads the text folded first: zero-width characters, soft hyphens and other invisible format characters are dropped, and fullwidth letters, non-breaking spaces and letters written with a combining mark read as their plain forms (Unicode NFKC). So HEM and LIG with a zero-width space between them, or a stamp from a PDF whose words a line break or a non-breaking space separates, is refused like the plain stamp.

Besides plugin tools' results, the post_tool_call hooks see the results of the built-ins that bring text from outside the job: a person's answer (oz.ask_user), an event's payload (oz.wait_for_event), another job's answer (oz.ask_colleague) and the job tree's board notes (oz.board_read). Each is checked in the job that reads it, by that job's agent's rule.

GUARDRAILS_CONFIG cannot turn this off: "packs": {"classified.core": false} is a setting error (the hooks then fail closed, as for any setting they cannot read), and pii: off does not touch it. When the other settings do not parse, the classified check still runs on the baseline pack.

The one loosening is for agents whose data stays on the node: an agent with data: local, on which the platform refuses every model that is not the node's own. It holds only for agents whose model calls the runtime makes itself: a harness or remote agent's session reaches models on its own, so its jobs are refused as any other's. A tenant admin may allow a marking for such agents by its ID (the pattern IDs: se_kvalificerat_hemlig, se_hemlig, se_konfidentiell, se_begransat_hemlig, de_streng_geheim, de_geheim, de_vs_vertraulich, de_vs_nfd, eu_top_secret, eu_secret, eu_confidential, eu_restricted):

Terminal
oz0 setting set GUARDRAILS_CLASSIFIED_LOCAL '["se_begransat_hemlig"]' \
  --reason "the archive's local model reads BEGRÄNSAT HEMLIG memos"
oz0 setting delete GUARDRAILS_CLASSIFIED_LOCAL --reason "no more"

It is a governance change: it needs the governance-write permission besides settings-write and a reason (--reason, or $OZ0_REASON; --decision-ref for a case number), the audit log keeps both, and allowing a marking waits for a second admin where the tenant has four eyes on (the command prints the change request's ID). Through the admin API, the reason and the decision reference are the reason and decision_ref fields of SetSettingRequest and DeleteSettingRequest, and a change that waits comes back in SetSettingResponse.change_request; the console's plugin settings ask for the reason when the name is this one.

Taking a marking back, or deleting the setting, applies at once. An allowed marking passes into the job's input and its tools' results for a data: local agent only; every other agent is still refused, and so is every job's output, since an output goes on to people, apps or a parent job's model.

Nothing of a data: local agent's job reaches a model behind the edge, so the allowance never sends allowed text there either: a tool that asks a model of its own (uses_model) gets no model key in such a job and answers that it cannot ask one, and the intent check asks no model for such a job and sends the call to a person instead. A note a local agent writes to the board, or an answer it gives a colleague, is checked again in the job that reads it: a cloud agent's job refuses the allowed marking there.

Setting GUARDRAILS_CLASSIFIED_LOCAL needs governance.write besides settings.write, so a custom role with settings.write alone sets every other setting and not this one; a tenant secret of the same name never becomes the setting, nor reaches nodes as one.

What it does not catch: markings written another way (lower case, spelled out, split by a hyphen or across fields, in an image or a file), another country's classes than those listed, and text the hooks do not see (a harness session's own tools and its built-ins, anything a tool sends elsewhere itself). The marking says the text is classified; the check does not judge whether it is, and whether anything may let classified text reach a model is not something this page answers.

On this page
Copyright © 2026