Skip to content

Information-flow authorisation

Most rules look at what an action is. Information-flow rules look at where the data came from: customer records read from core banking must never reach an external model or an outbound email, and data that originated in the Kingdom stays in the Kingdom by origin, not only by a classification someone remembered to declare. This is the agentic version of a problem operating systems solved long ago, and the natural extension of the residency mandates in the Saudi Enterprise pack.

Labels

A label says what a value is:

{"class": "CONFIDENTIAL", "pii": true, "origin": "SA", "source": "core_banking"}

Classes follow the NDMO scheme, TOP_SECRET, SECRET, CONFIDENTIAL, PUBLIC. RESTRICTED and INTERNAL are accepted as CONFIDENTIAL, HEALTH and PHI as SECRET, so the classification vocabulary already in use keeps working. Labels propagate by union: a value derived from two sources carries the higher class, PII if either had it, and both origins. Nothing is ever declassified by default; only a mandate with a flow rule whose effect is declassify can lower a label, and the Provenance Vault records it.

Labels sit on sources:

  • an MCP server (in the MCP view, Label data on the server row, or flow_labels on the server config): everything its tools return carries the label;
  • a tool, connector, data class or action family registered by the SDK or the API:
client.register_flow_source("tool", "core_banking", {"class": "CONFIDENTIAL", "pii": True, "origin": "SA"})
client.register_flow_source("action_family", "hr_*", {"class": "SECRET", "pii": True, "origin": "SA"})

API: GET/POST /v1/enforce/flow/sources, DELETE /v1/enforce/flow/sources/<id>.

Session taint tracking

The control plane never sees the values an agent handles (decision D5): it sees their hashes. When a labelled source produces an output, the values in it (dictionary and list leaves, plus the lines and identifier-like tokens of any text) are hashed and recorded in the session's taint set with the source's label. When the agent later sends any of those values on, in an action's metadata, its content, or the hashes the SDK declares, the layer finds the labels behind them.

from xybern.flow import Taint
taint = Taint(client, session_id=session_id, agent_id="reporter")
rows = core_banking.search(customer)
taint.label("core_banking", rows)                                  # hashed locally, hashes registered
r = client.intercept("send_email", metadata={"to": to, "body": body}, agent_id="reporter",
                     session_id=session_id, flow=taint.flow_for({"to": to, "body": body}))

The MCP gateway does this on its own for a labelled server: every tools/call result taints the gateway session. GET /v1/enforce/flow/taint/<session_id> summarises what a session has touched; DELETE clears it. A caller may also declare labels outright (metadata.flow.labels), which is how the benchmark scenarios state what an action carries.

Flow rules

A mandate compiles to a flow primitive whenever the outcome names the source of the data:

{"type": "flow", "decision": "block",
 "conditions": {"labels": {"pii": true, "sources": ["core_banking"]},
                "destination": {"class_in": ["model", "external"], "region_not_in": ["SA"]}}}

labels describes the data the action carries (class_at_least, class_in, pii, origin_in, sources, tags); destination describes where the action sends it (class_in of internal, external or model, region_in, region_not_in, tools). The destination is read from the action's metadata (destination, destination_region or region, provider) and otherwise from the action family: send, upload, publish and http go external; llm, chat and model calls go to a model. An undeclared region does not count as inside the allowed set, the same fail-closed rule the residency mandates use.

The reason on a refusal reads like the rule: Information flow: CONFIDENTIAL data, personal data, originating in SA to model (region undeclared). The compiler emits flow primitives from outcomes such as "customer PII read from core banking may never reach an external model or an outbound email".

Receipts and stamps

The decision lineage, the response and the receipt carry flow: the merged labels the action carried, how many tainted values it contained, and the provenance, which source introduced which label and when. The receipt sentence says it: carrying CONFIDENTIAL, PII, origin SA introduced by core_banking. An Authorisation Stamp carries a data summary (class, PII, origins, never values or hashes) so a receiver knows what class of data the stamped action touched.

Saudi Enterprise pack

The pack gained two flow mandates: residency by origin (Saudi-origin data, confidential or above, may only leave to a destination declared inside SA) and personal data from core systems (never to a model outside the Kingdom, held for a person before any outbound channel). They sit beside the classification-based residency mandate, so the Kingdom's boundary now follows the data whether or not a classification was declared on the action.

Benchmark

XAAB gained an information_flow category: PII from core banking to an external model, Saudi-origin data exported to another region, personal data emailed outside, secret data on a public form, and the legitimate counterparts (in-Kingdom model, public data, internal warehouse).