Skip to content

Commit ddbb76c

Browse files
committed
docs(plugin-auth): 精确化守卫读取 input.options.where 与 HookContext 契约表的关系
#5964 刚把 `HookEvent` 的枚举注释对齐到契约表的「谓词不在 `input` 上」。 两句都对,但要分清:钩子拿不到的是 composed `ast`(生效谓词);`before*` 期间 `input.options` 仍是调用方那只 engine options 包(engine.ts 5516→5517 / 6137→6152 之后才重建成 DriverOptions),`where` 与 `multi` 都在 —— 守卫读的 正是它。中间件只收窄不放宽,所以把调用方谓词当目标集是上界近似,对 fail-closed 守卫恰是安全方向。契约表那两处 `before` 行的措辞另记为 #5997。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JwwiU9bjhwy2SWj13ho8uv
1 parent 075844a commit ddbb76c

1 file changed

Lines changed: 15 additions & 5 deletions

File tree

packages/plugins/plugin-auth/src/last-admin-guard.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,17 @@
8484
* - **by-id** (`delete(obj, { where: { id } })` — what better-auth's adapter
8585
* emits, and what every cascade recursion re-enters with): `input.id`
8686
* carries the scalar id.
87-
* - **predicate / `multi`**: `input.id` is unbound and the row-scoping
88-
* predicate rides on `input.options.where` — the same shape #5273 pinned
89-
* for update.
87+
* - **predicate / `multi`**: `input.id` is present-but-undefined, and the
88+
* CALLER's own options bag is still on `input.options`, predicate included
89+
* — `delete()` only rebuilds that slot into `DriverOptions` *after* the
90+
* `before*` hooks return. That is the same slot the ban half reads, and it
91+
* does not contradict the `HookContextSchema.input` contract table
92+
* (#5273 / #5899): what is unreachable from `input` is the composed
93+
* `ast` — the *effective* predicate, onto which the filters middleware may
94+
* add RLS / sharing scoping. Middleware can only NARROW it, so treating the
95+
* caller's predicate as the doomed set over-approximates it, which is the
96+
* fail-closed direction: this guard may refuse a delete that would have
97+
* removed fewer rows, and can never miss one that removes more.
9098
* - `ctx.previous` (the engine's #5272 pre-image, and objectql's
9199
* `sys_fetch_previous_delete` builtin — `object: '*'`, priority 5) is bound
92100
* for the by-id shape ONLY; a batch dispatch names no single row, so it
@@ -341,8 +349,10 @@ export function registerLastAdminGuard(
341349
* Which `sys_user` rows this one write addresses — the same answer for both
342350
* halves. A scalar id when the engine dispatched by id (an update payload
343351
* also carries it in `data.id`; a delete's `input` has no `data` at all),
344-
* and otherwise the row-scoping predicate on `input.options.where`: the
345-
* shape #5273 pinned for update and the one measured on `beforeDelete`.
352+
* and otherwise the caller's predicate, still on `input.options.where` while
353+
* `before*` runs (see the header: the composed `ast` is the part hooks
354+
* cannot read, and middleware may only narrow it — so this set is an
355+
* over-approximation, the safe direction).
346356
*/
347357
const resolveTargetIds = async (
348358
op: GuardedOp,

0 commit comments

Comments
 (0)