Policy YAML Reference
Complete schema reference for SQL Security Policy YAML files, including all fields, glob patterns, conditions, and rule evaluation behavior.
This page documents every field available in a Kaelio SQL Security Policy. Policies are YAML documents that define table access, column filtering, and row-level security rules for a data source.
Policy Structure
A policy YAML file contains four top-level fields:
version: "1.0"
default_allow_tables: true
table_rules: []
column_rules: []
row_filter_rules: []All fields have defaults, so a completely empty document is a valid (permissive) policy.
Top-Level Fields
| Field | Type | Default | Description |
|---|---|---|---|
version | string | "1.0" | Policy schema version. Currently only "1.0" is supported. |
default_allow_tables | boolean | true | Whether tables not matched by any table rule are accessible. |
table_rules | array | [] | Rules that allow or block access to specific tables. |
column_rules | array | [] | Rules that hide columns from query results. |
row_filter_rules | array | [] | Rules that inject WHERE clauses to filter visible rows. |
default_allow_tables
This field controls what happens when a query references a table that does not match any entry in table_rules. It sets the baseline posture of your policy.
Allowlist Mode (default_allow_tables: false)
All tables are blocked unless explicitly allowed. Use this for high-security environments where you want to enumerate exactly which tables users can query.
version: "1.0"
default_allow_tables: false
table_rules:
- table_name: products
allowed: true
- table_name: categories
allowed: trueIn this example, only products and categories are queryable. Any other table reference causes the query to be rejected.
Blocklist Mode (default_allow_tables: true)
All tables are accessible unless explicitly blocked. Use this when most tables are safe to query and you only need to restrict a few.
version: "1.0"
default_allow_tables: true
table_rules:
- table_name: audit_logs
allowed: false
- table_name: internal_config
allowed: falseWhen you are unsure which mode to pick, start with blocklist mode (default_allow_tables: true). You can always switch to allowlist mode later as your requirements tighten.
Table Rules
Each entry in table_rules controls whether a table can be queried.
| Field | Type | Required | Description |
|---|---|---|---|
table_name | string | Yes | Table name or glob pattern to match. |
allowed | boolean | Yes | true to allow access, false to block it. |
condition | object | No | Attribute-based condition that must pass for this rule to apply. |
table_rules:
- table_name: audit_logs
allowed: false
- table_name: analytics_*
allowed: true
condition:
department: analyticsColumn Rules
Each entry in column_rules specifies columns to hide from query results for a given table.
| Field | Type | Required | Description |
|---|---|---|---|
table_name | string | Yes | Table name or glob pattern to match. |
restricted_columns | array of strings | Yes | Column names to strip from SELECT queries. |
condition | object | No | Attribute-based condition that must pass for this rule to apply. |
column_rules:
- table_name: users
restricted_columns:
- password_hash
- ssn
- table_name: employees
restricted_columns:
- salary
- bank_account
condition:
role: ["hr", "admin"]In the second rule, the salary and bank_account columns are only hidden from users whose role is hr or admin. For all other users, those columns are visible.
Column rules are cumulative. If multiple rules match the same table, all of their restricted_columns lists are merged. A column hidden by any matching rule is hidden regardless of what other rules say.
Row Filter Rules
Each entry in row_filter_rules injects a WHERE clause to restrict which rows a user can see.
| Field | Type | Required | Description |
|---|---|---|---|
table_name | string | Yes | Table name or glob pattern to match. |
filter_sql | string | Yes | SQL WHERE clause fragment. Supports {variable} template placeholders. |
condition | object | No | Attribute-based condition that must pass for this rule to apply. |
Template variables in filter_sql are replaced with values from the querying user's security properties. Use {property_name} syntax.
row_filter_rules:
- table_name: orders
filter_sql: "tenant_id = '{tenant_id}'"
- table_name: documents
filter_sql: "department = '{department}' AND classification != 'TOP_SECRET'"
condition:
role: viewerWhen a user with tenant_id: acme queries the orders table, the filter becomes tenant_id = 'acme' and is appended as a WHERE clause.
If a template variable like {tenant_id} cannot be resolved from the user's security properties, the query is rejected entirely. Ensure that every user who queries a filtered table has the required properties set.
Glob Patterns
The table_name field in all rule types supports glob patterns for matching multiple tables with a single rule.
Supported Syntax
| Pattern | Meaning | Example Match |
|---|---|---|
* | Matches everything (all tables) | Any table |
analytics_* | Matches any table starting with analytics_ | analytics_events, analytics_daily |
*_logs | Matches any table ending with _logs | access_logs, error_logs |
schema.* | Matches any table in a schema | schema.users, schema.orders |
? | Matches a single character | log_? matches log_a but not log_ab |
Priority Order
When multiple rules match the same table, the most specific pattern takes priority. Rules are evaluated in the following order:
- Exact match --
ordersis more specific than any pattern - Specific patterns --
analytics_*is more specific than*because it has more literal characters - Universal wildcard --
*has the lowest priority
Among patterns with the same specificity, the rule that appears first in the YAML list wins (for table rules and row filter rules).
table_rules:
# Priority 3 (lowest): catch-all
- table_name: "*"
allowed: false
# Priority 2: pattern match
- table_name: "public_*"
allowed: true
# Priority 1 (highest): exact match
- table_name: public_secrets
allowed: falseIn this example, public_reports is allowed (matches public_*), public_secrets is blocked (exact match overrides the pattern), and any other table is blocked (matches *).
Declaration order in the YAML does not affect priority. Exact matches always win over patterns, and more specific patterns always win over less specific ones, regardless of where they appear in the list.
Conditions
Conditions make a rule apply only when the querying user has specific security property values. Add a condition object to any rule to enable attribute-based access control.
Condition Fields
The condition object is a dictionary where:
- Keys are user security property names (e.g.,
role,department,tenant_id) - Values are the required property values
Matching Modes
Exact match -- Use a string value. The user's property must equal this value exactly.
condition:
department: engineeringIN match -- Use an array of strings. The user's property must equal one of the listed values.
condition:
role: ["admin", "manager", "analyst"]Multiple keys (AND logic) -- When a condition has more than one key, all of them must match for the condition to pass.
condition:
department: engineering
role: ["lead", "senior"]This condition passes only for users who are in the engineering department AND whose role is either lead or senior.
Fail-Closed Behavior
Conditions are fail-closed: if the user does not have a property that a condition checks, the condition fails. For example, a condition of department: sales will fail for any user who has no department property set, even if all other conditions would pass.
Conditional Rule Example
table_rules:
- table_name: financial_reports
allowed: true
condition:
department: finance
- table_name: financial_reports
allowed: falseThe first rule allows finance department users to access financial_reports. The second rule (no condition) blocks everyone else. Both rules have the same table_name, so they share the same specificity -- the first matching rule wins.
Rule Evaluation Order
Understanding how rules are evaluated helps you predict the outcome of any query.
Table Rules
- Find all
table_rulesentries whosetable_namematches the queried table (exact or glob). - Sort matches by specificity: exact match first, then specific patterns, then
*. - For each match (in order), check the
condition(if any):- No condition: the rule applies immediately. Return
allowedvalue. - Condition passes: the rule applies. Return
allowedvalue. - Condition fails: skip this rule and try the next match.
- No condition: the rule applies immediately. Return
- If no rule applies, fall back to
default_allow_tables.
Result: First matching rule wins.
Column Rules
- Find all
column_rulesentries whosetable_namematches the queried table. - For each match, check the
condition(if any):- No condition or condition passes: add
restricted_columnsto the restricted set. - Condition fails: skip this rule.
- No condition or condition passes: add
- The union of all restricted columns from passing rules is applied.
Result: All matching rules are cumulative.
Row Filter Rules
- Find all
row_filter_rulesentries whosetable_namematches the queried table. - Sort matches by specificity (same ordering as table rules).
- For each match (in order), check the
condition(if any):- No condition or condition passes: apply
filter_sqland stop. - Condition fails: skip this rule and try the next match.
- No condition or condition passes: apply
- If no rule applies, no row filter is added.
Result: First matching rule wins.
When No Rules Match
| Rule Type | Behavior When No Rule Matches |
|---|---|
| Table rules | default_allow_tables determines access |
| Column rules | All columns are visible (no restrictions) |
| Row filter rules | No WHERE clause is injected |
Complete Example
The following policy demonstrates all features working together.
version: "1.0"
default_allow_tables: false
# --- Table access ---
table_rules:
# Block all internal tables
- table_name: "internal_*"
allowed: false
# Allow public-facing tables for everyone
- table_name: products
allowed: true
- table_name: categories
allowed: true
# Allow order tables only for sales and support teams
- table_name: orders
allowed: true
condition:
department: ["sales", "support"]
- table_name: order_items
allowed: true
condition:
department: ["sales", "support"]
# Allow all tables for admins (lowest priority due to wildcard)
- table_name: "*"
allowed: true
condition:
role: admin
# --- Column restrictions ---
column_rules:
# Always hide authentication columns
- table_name: users
restricted_columns:
- password_hash
- mfa_secret
- recovery_codes
# Hide PII from non-compliance users
- table_name: users
restricted_columns:
- ssn
- date_of_birth
- home_address
condition:
department: compliance
# Hide pricing internals across all tables matching the pattern
- table_name: "pricing_*"
restricted_columns:
- cost_basis
- margin_pct
# --- Row-level filtering ---
row_filter_rules:
# Tenant isolation on the orders table
- table_name: orders
filter_sql: "tenant_id = '{tenant_id}'"
# Department-scoped access to documents
- table_name: documents
filter_sql: "department = '{department}'"
condition:
role: viewer
# Admins see all documents (no filter)
- table_name: documents
filter_sql: "1 = 1"
condition:
role: adminHow This Policy Evaluates
Consider a user with these security properties:
department: sales
role: viewer
tenant_id: acme| Query | Result |
|---|---|
SELECT * FROM products | Allowed. products is explicitly permitted for everyone. |
SELECT * FROM internal_metrics | Blocked. Matches internal_* which is set to allowed: false. |
SELECT * FROM orders | Allowed with row filter. The user is in sales, so the table rule passes. Row filter adds WHERE tenant_id = 'acme'. |
SELECT * FROM users | Blocked. No table rule matches users, and default_allow_tables is false. |
SELECT * FROM documents | Blocked. No table rule matches documents, and default_allow_tables is false. |
Now consider an admin user:
role: admin
tenant_id: acme| Query | Result |
|---|---|
SELECT * FROM users | Allowed via the * wildcard table rule (admin condition passes). Columns password_hash, mfa_secret, and recovery_codes are still hidden. |
SELECT * FROM documents | Allowed via wildcard. Row filter 1 = 1 is applied (admin condition on the first matching row filter rule passes), so all rows are visible. |
SELECT * FROM orders | The orders table rule requires department: ["sales", "support"]. The admin has no department property, so that condition fails. The * wildcard rule applies next, and the admin condition passes. Access is allowed. Row filter adds WHERE tenant_id = 'acme'. |
Docs