07 · Tables A→ZL · Row & column security
Security limitations & caveats
Cardinality bounds, propagation delays, and the operations that shut off under a restrictive policy.
Row & column security fails closed: wherever a corner is cut, the cut hides data rather than leaking it. Know these edges before governing production tables.
RLS column constraints
- The bound column must be low-cardinality categorical: at most 10,000 distinct values. Beyond the cap, queries on the table are rejected outright.
- Values are compared as text - numeric columns are matched on their string form.
NULLrows are hidden from any caller with a partial row grant. Only callers with unrestricted access to the table keep itsNULLrows.
Propagation
- Policy writes reach the engine within ~60 seconds (policy cache TTL) - this includes an organisation's first policy.
- The distinct-value allow-set refreshes every ~30 seconds - rows with a brand-new category value stay hidden until then (fail closed, never open).
Operations disabled under restriction
For callers restricted on a table, some operations can't be filtered meaningfully and are refused instead:
| Operation | Behaviour |
|---|---|
| Version diff | Refused |
| Domain validation scan | Refused |
| Allowed-values resolution | Refused |
| Table metadata with zero readable columns | 403 - no schema, no row counts |
Write restrictions
- Writes must include the RLS column, and its value must be inside your
allow-set;
NULLis rejected (it would create a row invisible to you). - Updates and deletes on a column-restricted table require the table to have primary key columns, and none of them may be hidden from you.
- Constraint errors are redacted for restricted callers ("The write violates a table constraint") since raw messages can echo hidden values.
Authoring pitfalls
- A broken policy denies the whole organisation. If any stored policy fails to compile, every table in the org is denied until it's fixed - disable or fix a bad policy immediately.
tableFqnbinds by exact string match againstcatalog.schema.table- a typo stores an enabled policy that never enforces. Validation catches malformed names, not wrong ones.- An advanced-mode CLS policy with a
columnNamemust actually scope itself to that column (resource.nameorcontext.column) - an unscoped forbid would hide every column and is rejected at write time. - Only string attribute values participate in policies - nested objects, arrays,
and
nulls are dropped from the attribute bag. - Builder-to-advanced conversion is one-way; keep builder policies in builder mode as long as they fit the grammar.
Heads up
Test with a second account. The fastest way to verify a policy
does what you meant is to assign a test member the target attributes and query the
table as them - remember the ~60 s propagation delay before judging results.