Skip to main content
One policy governs one connection. The language is the same for every engine — what differs between engines is the level at which each guarantee is met, not the rules you write.

require — what the engine must provide

The gateway refuses to start a connection that cannot meet every line. Each guarantee takes a minimum level from the capability ladder: native, enforced, or advisory.
Asking for native scoping means the database must enforce it — row-level security — so that it holds even for a connection that bypasses Warden. Asking for enforced accepts Warden’s governed relations. Both are defensible; the point is that you choose, and you find out at startup rather than during an incident.

approval — what needs a human

Classes are derived from the structure of the operation, never its leading keyword: a WITH … DELETE … RETURNING wrapped in a SELECT is a delete. unbounded_mutation is a class of its own, because “change every record” deserves a different answer from “change this one”.

resources — what exists for this agent

Anything not listed is not merely denied: it is invisible. warden_describe_schema returns only permitted resources, because a schema listing is reconnaissance.
columns restricts the projection, enforced inside the statement rather than filtered from results.
A table you cannot scope is more dangerous than one you deny. In the example policy, shop.order_lines is deliberately excluded: it has no tenant column, so predicate injection cannot scope it, and an agent granted it would read every tenant’s line items. warden policy lint flags that combination.

scope — restricting rows to this agent

{tenant_id} comes from the agent’s scope values in warden.yaml. For PostgreSQL, each scoped table is replaced by a subquery already filtered, column-projected, and masked — so no alias, join, or expression in the agent’s own SQL can get around it.
Scope is not a bound. “Every order belonging to this tenant” is still unbounded for a reviewer, and Warden counts it that way. Scope limits whose records are affected, not how many. See blast radius.

limits

masking

Masking is applied inside the statement where the engine allows it, which is what stops SELECT upper(email) or a join from unmasking a value. See masking.

Linting

It checks shape and internal consistency — resources, scope bindings, masking rules, approval classes, and combinations that are valid individually but dangerous together, such as a granted table with no column to scope it by. The require: block is checked at startup instead, against the real connection: warden serve probes the engine and refuses to start when a guarantee falls short of the level the policy demands.