Deny rules

Most of what the Gateway does is decide whether a result can come from cache. A deny rule does something different: it refuses the statement outright, at the Gateway, before your warehouse is ever contacted. Cache rules make queries cheaper. Deny rules make some queries not happen.

What a deny rule does

A rule with a deny action is a statement firewall. When an incoming statement matches its conditions, the Gateway answers the client with an error and stops. No cache lookup, no session, no forward to the warehouse. The statement never runs.

Deny rules use the same condition vocabulary as cache rules — statement type, tables, schema, catalog, statement text, user, role, headers, bound parameters, client IP with CIDR matching, time of day and day of week. Anything you can write a cache rule against, you can write a deny rule against.

The client sees a normal SQL error

A blocked statement comes back in the client's own protocol, so it renders as a permissions error rather than a broken connection. This matters: a transport-level failure looks like an outage and generates a ticket, while a SQLSTATE the driver already understands looks like a policy decision and generates a conversation.

ConnectionWhat the client receives
Databricks (Thrift) HTTP 200 carrying a Thrift error status — deliberately not a 403, which JDBC and ODBC would surface as a transport failure.
Databricks (JSON) HTTP 200 with a failed statement state and a structured error.
Snowflake The JSON error envelope Snowflake clients already parse.
PostgreSQL A wire-protocol error frame at severity ERROR, not FATAL — the statement fails, the session survives.

The defaults are chosen to be legible to any SQL client: SQLSTATE 42501 (insufficient_privilege) and the code AIRBRX_STATEMENT_BLOCKED. You can override the SQLSTATE, the code, and the message. The message is rendered verbatim by whatever tool the person is sitting in front of, so it is capped in length and restricted to printable ASCII — write it as something an analyst can act on, not an internal reference. "Exports from customer_pii need an approved extract — ask #data-gov" beats "policy violation".

Why deny rules are evaluated differently

Cache rules are priority-ordered and first-match-wins: the Gateway walks them in order, takes the first rule whose conditions match, and stops. That is correct for caching, where you want a specific rule to override a general one.

It would be dangerous for a block. If deny rules sat in the same ordered list, a cache rule with a higher-precedence priority could match first and the deny would never be reached — meaning a routine cache-tuning edit could silently disarm a security control. So the Gateway does not do that.

Deny fails closed

The caching paths are deliberately forgiving: if rule evaluation fails, the Gateway degrades to passing the query through to the warehouse. A cache that stops working costs money; a cache that starts refusing traffic costs you your analytics.

Deny inverts that, because the failure modes are not symmetric. If the deny pass itself throws, the statement is refused, with AIRBRX_POLICY_EVALUATION_FAILED. The same applies to work that has to happen before the deny pass can run: if the Gateway cannot parse a Postgres statement and the tenant has deny rules configured, the statement is refused rather than forwarded unexamined. A tenant with no deny rules keeps the forgiving behavior; arming one is what switches the parse failure to fail-closed.

Bad deny rules are rejected at load, not discovered in traffic

A cache rule that degrades to "don't cache" costs money. A deny rule that degrades to "don't deny" disarms a control someone is relying on. So deny rules are validated strictly when the rule set loads, and a rule set containing an invalid one is refused rather than partially applied.

The last point is worth sitting with if you are used to policy engines that offer a dry-run. Airbrx does not have one for deny. To rehearse a rule, write it against a narrow condition set you control, or use the traffic page to confirm which statements would have matched before you widen it.

What gets recorded

A denial is emitted as a security event at the severity the rule's author chose, and rules set to critical route to alerting. Every deny rule that matched is named in that event, not just the one whose message the client saw.

One current limitation to know about: a denied statement does not yet produce a per-request traffic-log record, so blocked statements will not appear alongside hits and misses on the traffic page or in the analytics API. The security event is the record of a block today. If you need blocked statements in your own reporting, treat the security event stream as the source, not the traffic log.

What deny rules are not

Deny is a boundary at the Gateway, not a replacement for warehouse-side authorization. Anyone who can reach your warehouse directly — bypassing the gateway address — is unaffected by a deny rule. Row-level security, column masking, and grants are still the controls that hold when someone connects around you. Deny rules narrow what is possible through the path you have pointed your BI tools at; they do not narrow what is possible in your warehouse.

The rules engine currently has exactly two verbs: cache and deny. There is no routing action, no masking or redaction action, no rate-limit action, and no outbound-callout action. If you write an action key the Gateway does not implement, it is not silently ignored — it is logged as a critical event at rule load, so the gap surfaces immediately rather than being inferred from traffic weeks later.

Where to go next

Put a boundary in front of your warehouse

Deny rules are available on every plan. Create an account and arm your first one in the rules workshop.

Create an account