Skip to main content
Someone runs this against production:
The 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:
  1. A rule matches, and by default it denies.
  2. First match wins among the rules that deny.
  3. A rule set is an ordered deny list. No match means allowed.
config.yaml
Every rule takes name, message and action in addition to its own fields.
guardrails is Hoop’s own rule engine, evaluated in-process. It was previously spelled policy, which still loads and warns at startup — see Migrating from policy. The separate opa section configures the optional external endpoint a defer hands to.
A rule set that reaches a listener enforces. There is no observe-only switch to forget: a rule you wrote denies from the moment the process starts. Roll out on a staging lane, or hand the call to Rego with action: defer.

Rule types

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.
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.
Case-insensitive, matched against the raw statement text.
RE2 syntax — no lookaround, no backreferences. A bad regex is rejected at startup, naming the listener and the rule.
Every supported entity is detected unless a top-level pii.entities list narrows the set. Once that list exists it is exhaustive, and a rule naming an entity absent from it is refused at startup — otherwise the guardrail would look live while allowing through everything it was written to stop.
http_status is response-side, which is why an authorization filter running before the upstream can never ask it.
This is the Agentic Access path. It takes per-risk-level actions instead of an action field, and setting action on it is refused at startup.
CALL and EXECUTE report unknown rather than call, because their bodies live in the catalog and no parser can say what they touch. A rule written operations: [call] matches neither. Write operations: [call, unknown].

Actions

action on a regular rule is either empty or defer. That is the whole list.
defer with no 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: guardrail rules concatenate

A listener’s rules are evaluated first, then the top-level defaults:
config.yaml
Concatenation is safe here precisely because first-match-wins applies to denials: adding rules can never turn a deny into an allow, only change which message the user reads. opa replaces rather than merges, and mask replaces too.

Roll out without breaking anything

1

Validate the config

Nothing binds and nothing is denied. Startup refusals — a bad regex, a rule naming an entity outside pii.entities, a defer with no opa.url — surface here rather than in front of users.
2

Run the rule set on a staging lane first

Point a listener carrying the new rules at a staging copy of the upstream and send real query shapes through it. This is what replaces an observe-only flag: the rules are live, but on a lane whose denials cost nothing.
3

Read what got denied

Each violation names the rule that fired and the message the client read, so an over-broad rule is visible by name before it reaches production.
4

Defer anything you are not ready to decide

A rule with action: defer records a finding and hands the call to OPA instead of denying on its own. Rego that returns allow while you watch the findings gives you the same visibility on the production lane.
5

Move production traffic to the lane

Nothing changes in the rule set. /config reports the resolved rules per listener, which is what to compare against the file when a rule you wrote never fires.

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

Guardrail 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.