Skip to content

docs(permissions): say that an empty RLS policy set is unfiltered, not deny-all - #8029

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-7811-rls-deny-all-wording
Aug 12, 2026
Merged

docs(permissions): say that an empty RLS policy set is unfiltered, not deny-all#8029
hotlong merged 1 commit into
mainfrom
claude/issue-7811-rls-deny-all-wording

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#7811

Docs-only. No code changed — the code is right and the prose was wrong.

The defect

content/docs/permissions/authorization.mdx:55, the layer-5 row of the evaluation-order table, read:

If no applicable policy compiles, the result is a deny-all sentinel (fail-closed)

which a reader can take as "an object with no RLS policies is invisible". That reading already cost something: a reviewer on #7792 read the sentence as documenting fixed behaviour and nearly had it cited as evidence in a security PR body.

The contract the code actually has

Verified against packages/plugins/plugin-security/src/rls-compiler.ts rather than the card's line citation (RLSCompiler.compileFilter):

  • policies.length === 0null, i.e. no filter. An object with no RLS policies is readable, not invisible.
  • applicable === 0null as well. A policy that carries no predicate for this clause (a check-only policy seen on the using read pass) is skipped without counting toward the deny — so even a non-empty policy list can legitimately yield no filter.
  • applicable policies exist and every one of them compiled to nothing → RLS_DENY_FILTER, the zero-row sentinel.

The separate RLS_DENY_FILTER returns in security-plugin.ts (getReadFilter) are the resolution-failed branch — sharing-scope resolution threw, an on-behalf-of context reached the analytics read path, permission-set resolution threw. Also fail-closed, also not "empty set denies".

The changes

Two sites, chosen so a careful reader cannot come away with the wrong reading rather than merely not being told the right one:

  • authorization.mdx:55 — states the narrow condition (applicable policies exist but none of them compiles) and then names the empty case explicitly: an object with no applicable policy is unfiltered at this layer, because RLS narrows what the earlier gates allowed and never denies on its own.
  • rls.mdx:18 — the fail-closed contract section (#the-fail-closed-contract) and its "no applicable policy means no restriction" callout were already exact and are untouched. The summary bullet at the top of the page was not: it said a policy that can't compile "denies everything", which overstates in a second direction too — one uncompilable policy alongside a compilable one is excluded from the OR-union, not a deny. The bullet is the first statement of the rule a reader meets, so it now carries the same qualifier the contract section does.

The other six pages: checked, correct, not touched

The sweep is git grep -ln 'fail-closed\|deny-all' -- content/docs/permissions → 8 files. The six not in this diff each state a different layer's fail-closed rule correctly, and none makes a claim about the RLS policy set:

FileHitVerdict
attachments-access.mdx:20 layered gates; :62 "failure to resolve the filter fails closed (deny-all)"Correct — the resolution-failure branch, which is genuinely deny
authentication.mdx:195 fail-closed identity write guard (ADR-0092/0103)Correct — unrelated layer
delegated-administration.mdx:49 empty BU subtree approves nothing; :183 unreadable issuer membershipCorrect — delegated-admin gate
field-level-security.mdx:87 masked field stripped, non-editable write throwsCorrect — FLS
permissions-matrix.mdx:213 unknown/absent sharingModelprivate (ADR-0090 D1)Correct — OWD default. Its RLS passages (:11 layer order, :44 posture-gated bypass) make no compile-rule claim
sharing-rules.mdx:34 private default; :74 depth scopes fail closed to owner-onlyCorrect — OWD + enterprise depth resolver

A widened sweep for restatements of the compile rule specifically (no applicable, no polic*, compiles, uncompilable, denies everything) across all 8 files returned only the two sites edited here.

Verification

node scripts/pm/dispatch-gates.mjs on the final surface, plus the convention-scoped ones — all green:

check-nul-bytes: OK (scanned 7331 text file(s); no raw ASCII control bytes)
✓ doc authoring guard: 375 files clean — no bare metadata literals
✅ check-doc-anchors: 210 internal #fragment link(s) across 399 source file(s) all resolve
✓ docs-accuracy-audit scope is in sync with content/docs/: 179 hand-written doc(s)
check-role-word: OK (44 baselined file(s), no new occurrences)
✓ check-quick-reference-counts: 13 section(s), every heading matches
✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 394 files / 1410 TS blocks clean

skip-changeset: docs ship in no published package.

Out of scope: #7665 remains open; #7792 remains open.


Generated by Claude Code

…t deny-all (#7811)
The layer-5 row of the evaluation-order table read "If **no** applicable
policy compiles, the result is a deny-all sentinel (fail-closed)", which a
reader can take as "an object with no RLS policies is invisible". The
implementation's contract is narrower: `RLSCompiler.compileFilter` returns
`null` (no filter) when no policy is applicable to the clause, and only
returns the `RLS_DENY_FILTER` sentinel when applicable policies exist and
every one of them fails to compile.
The imprecision is not academic — it misled a reviewer into treating the
sentence as evidence that a write path was already fail-closed.
- `authorization.mdx`: state the narrow condition and name the empty case
explicitly, so the wrong reading is closed rather than merely unstated.
- `rls.mdx`: the fail-closed contract section and its callout were already
exact; the summary bullet at the top of the page was not, and it is the
first statement of the rule a reader meets.
Docs-only; no behaviour change.
@vercel

vercelBot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 12, 2026 12:25pm

Request Review

@hotlonghotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/xs labels Aug 12, 2026 — with Claude
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Aug 12, 2026
@hotlong
hotlong marked this pull request as ready for review August 12, 2026 13:29
@hotlong
hotlong added this pull request to the merge queueAug 12, 2026
Merged via the queue into main with commit 95d9485Aug 12, 2026
28 checks passed
@hotlong
hotlong deleted the claude/issue-7811-rls-deny-all-wording branch August 12, 2026 13:47
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@hotlong@claude