WHERE clause is missing. 500,000 rows change. The statement was valid, the credentials were valid, the permissions were valid — nothing in the database’s own model of correctness had an objection.
Guardrails are the objection. Every statement is decoded and evaluated before it reaches the resource, and a rule that matches refuses it with a message you wrote.
Free tier: one Data Masking rule and one Guardrail per Sidecar are free, forever. Running more than one rule per feature, or managing rules centrally across Sidecars, requires Enterprise.
How a rule set resolves
Three sentences cover the entire semantics:- A rule matches, and by default it denies.
- First match wins among the rules that deny.
- A rule set is an ordered deny list. No match means allowed.
config.yaml
name, message and action in addition to its own fields.
Rule types
operation — deny by effect
operation — deny by effect
operation reads the statement’s worst effect, not its leading verb. A delete hidden in a CTE — WITH d AS (DELETE FROM customers RETURNING *) SELECT count(*) FROM d — reports delete and this rule catches it. EXPLAIN DELETE … reports explain; EXPLAIN ANALYZE DELETE … reports delete, because it runs.Vocabulary: select insert update delete merge create drop alter truncate grant revoke call copy explain show set begin commit rollback, plus other (parsed but unclassified) and unknown (the scanner could not finish). HTTP verbs are their own values: get post put patch head options connect trace.table — deny by relation
table — deny by relation
access is read or write. Add require_table_match: true to also deny when the relations could not be determined — the fail-closed posture for anything genuinely off limits.deny_words_list — deny by substring
deny_words_list — deny by substring
pattern_match — deny by regex
pattern_match — deny by regex
pii — deny sensitive values in the request
pii — deny sensitive values in the request
pii.entities block. A rule naming an entity absent from that list is refused at startup — otherwise the guardrail would look live while allowing through everything it was written to stop.http_resource and http_status
http_resource and http_status
http_status is response-side, which is why an authorization filter running before the upstream can never ask it.ai_analysis — deny by classified risk
ai_analysis — deny by classified risk
action field, and setting action on it is refused at startup.Actions
action on a regular rule is either empty or defer. That is the whole list.
defer with no policy.opa.url configured is refused at startup — a finding nobody reads forwards every statement while looking like enforcement.
action: warn and require_review are refused at startup. warn exists only as a per-tier action on ai_analysis rules. Human review needs a review backend the current build does not ship — see Agentic Access. For everything else, defer is how a rule stops short of deciding.Inheritance: policy rules concatenate
A listener’s rules are evaluated first, then the top-level defaults:config.yaml
policy.opa and policy.enforce replace rather than merge, and mask replaces too.
Roll out without breaking anything
1
Start observe-only
Leave
policy.enforce: false. Every statement is inspected and audited, nothing is denied.2
Validate the config
3
Read what would have been denied
4
Turn on enforcement
Set
policy.enforce: true. /config confirms enforcing per listener.Looking for guardrails on the Hoop Gateway instead? That is a different implementation — Python regex patterns configured in the web app, with Block, Warn and Require Approval actions. See Guardrails.
Next
Policy Rules Reference
Every field of every rule type, deferring to Rego, and the full findings vocabulary.
Data Masking
Control what comes back, not just what goes in.