|
| 1 | +--- |
| 2 | +'@objectstack/plugin-security': minor |
| 3 | +--- |
| 4 | + |
| 5 | +fix(plugin-security)!: no shipped permission set below platform admin reads a row of `sys_verification` or `sys_jwks` — the credential rows those objects declare private (#20027) |
| 6 | + |
| 7 | +Clause-②: no (narrowing) |
| 8 | + |
| 9 | +**BREAKING** — shipped as `minor` under the launch-window convention |
| 10 | +(`check-changeset-no-major` refuses `major` until GA; breaking-ness is carried by |
| 11 | +this banner and the ADR-0087 disposition below, never by the level). |
| 12 | + |
| 13 | +`sys_verification` (one-time verification and password-reset tokens) and |
| 14 | +`sys_jwks` (JWT signing keys) declare `access: { default: 'private' }`, and the |
| 15 | +shipped permission sets documented them as denied to every principal below |
| 16 | +platform admin. The sets did not hold that: the read they grant on every |
| 17 | +better-auth-managed object is an explicit per-object entry, which the `private` |
| 18 | +posture does not govern, and neither object had a row policy behind it. |
| 19 | + |
| 20 | +**What a principal can no longer read.** Every shipped set that grants that read |
| 21 | +and carries row-level security — `member_default`, `viewer_readonly`, |
| 22 | +`organization_admin` and its wall-less variant `organization_admin_no_bypass` — |
| 23 | +now declares a row policy that admits no row on each of the two objects |
| 24 | +(`sys_verification_none`, `sys_jwks_none`). That covers the rows the objects |
| 25 | +declare private, including a caller's own verification row. An MCP agent acting |
| 26 | +for a user is bounded by that user's sets and reads the same. `admin_full_access` |
| 27 | +is unchanged and still reads every row. |
| 28 | + |
| 29 | +**Remedy.** None is expected to be needed: no shipped product surface reads these |
| 30 | +tables under a user context. A deployment that genuinely needs a principal below |
| 31 | +platform admin to inspect them grants that in a permission set of its own, with |
| 32 | +a row-level-security policy that names the rows it may see. |
| 33 | + |
| 34 | +Unchanged: better-auth's own verification, password-reset and token-signing flows |
| 35 | +(they read and write through its adapter under system context, which no row |
| 36 | +policy reaches), the owner-scoped reads of the other `private` identity objects |
| 37 | +(`sys_device_code`, `sys_oauth_access_token`, `sys_oauth_refresh_token`), and |
| 38 | +every other managed object. |
| 39 | + |
| 40 | +<!-- adr-0087: not-required (no-migration-prescription) Nothing authored moves: `packages/spec` is untouched, no object definition or column changes, and the shipped permission sets are code evaluated from the in-memory declaration, so `objectstack migrate meta` has nothing to rewrite and the ledger has no row to gain. The change is a narrowing of what the platform's own sets admit at runtime. The other categories are closed on facts: the package publishes (not `unpublished`); no ADR-0087 id covers it (not `registered` / `already-registered`); and it is runtime behaviour, not a TypeScript declaration (not `runtime-interface-only` / `type-surface-only`). --> |
0 commit comments