Skip to content

Commit 6527062

Browse files
committed
chore(changeset): 20136 patch note; one-clause corrections to the pending 18783, 18931 and 20134 notes the fix made false
Claude-Session: https://claude.ai/code/session_01Bvd69VPa6puiNzzPUroDBx Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4c86410 commit 6527062

4 files changed

Lines changed: 23 additions & 3 deletions

‎.changeset/18783-server-can-option-visibility.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ stage: Field.select({
3434
- If the security service cannot resolve the map, a write that needs it is refused with the resolution's own error — fail closed. It is never read as "no grants".
3535
- With no security plugin, or an engine older than the seam, there is no permission data. The gate stays unevaluable and the value is admitted with the same `warn` as before, which names the missing input. The security plugin logs one `warn` at start when the engine lacks the seam.
3636

37-
**Plain-wildcard coverage, closed in this release.** `can()` reads only the per-object entries of the map. Before #20083, `/auth/me/permissions` listed an object for a `'*'` wildcard grant only when that grant carried a super-user bit, so a subject whose access to an object came only from a plain wildcard — for example `organization_admin_no_bypass`, which a deployment without an organization wall grants to organization owners and admins — got `false` from `current_user.can()` for that object, although the data plane admits the write, and was refused on a `can`-gated option. That gap is closed in this same release by #20083 (`.changeset/20083-effective-map-plain-wildcard.md`): `buildEffectiveObjectPermissions` now puts each set's plain `'*'` on the registered public objects that set does not name, so that population's map — and any client that answers `can()` from the same `/auth/me/permissions` map — carries an entry for each object the wildcard covers, with the wildcard's grants, narrowed on a guarded managed object by the same managed-write clamp as every other entry. The map still differs from `PermissionEvaluator.checkObjectPermission` for subjects holding a super-user wildcard: an entry the super-user set itself names narrower can read as granted. Its missing `transfer` is closed in this same release (`.changeset/20134-super-user-entries-every-bit.md`).
37+
**Plain-wildcard coverage, closed in this release.** `can()` reads only the per-object entries of the map. Before #20083, `/auth/me/permissions` listed an object for a `'*'` wildcard grant only when that grant carried a super-user bit, so a subject whose access to an object came only from a plain wildcard — for example `organization_admin_no_bypass`, which a deployment without an organization wall grants to organization owners and admins — got `false` from `current_user.can()` for that object, although the data plane admits the write, and was refused on a `can`-gated option. That gap is closed in this same release by #20083 (`.changeset/20083-effective-map-plain-wildcard.md`): `buildEffectiveObjectPermissions` now puts each set's plain `'*'` on the registered public objects that set does not name, so that population's map — and any client that answers `can()` from the same `/auth/me/permissions` map — carries an entry for each object the wildcard covers, with the wildcard's grants, narrowed on a guarded managed object by the same managed-write clamp as every other entry. The map also differed from `PermissionEvaluator.checkObjectPermission` for subjects holding a super-user wildcard: an entry the super-user set itself names narrower read as granted, which is closed in this same release (`.changeset/20136-super-user-fold-per-set.md`). Its missing `transfer` is closed in this same release (`.changeset/20134-super-user-entries-every-bit.md`).
3838

3939
**No spec key, route or config key is added or removed.**

‎.changeset/18931-me-permissions-unrestricted-export-annotation.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ For that principal an unrestricted object got no entry, so annotate never saw it
1212

1313
- **The seed and annotate cannot diverge again.** Since #20134 the seed places an entry for every registered object a super-user wildcard reaches that the merge left without one, and `annotateEffectiveApiOperations` alone decides which entries carry `apiOperations`: an object that is unrestricted **and** keeps `export` gets none — unless its `enable.apiEnabled` is `false`, which is annotated `[]` since #20135 because the REST door answers 404 for every verb on it.
1414
- **The export axis is the only axis this reaches.** Measured across the `enable` shapes an unrestricted object can carry: withholding `export` subtracts `export` and nothing else, and `mode` stays `unrestricted` either way — which is why the old `mode`-only guard could not tell the two cases apart. The CRUD axis needed no annotation and still gets none.
15-
- **What the response gains**: for such a principal, one entry per unrestricted object, each the full closure minus `export`. Its CRUD bits are folded `true` — the same answer the client already computed by falling back to `'*'`, now stated explicitly rather than inherited.
15+
- **What the response gains**: for such a principal, one entry per unrestricted object, each the full closure minus `export`. Its CRUD bits are folded to what its wildcard grants (all four for the built-in admin sets) — the same answer the client already computed by falling back to `'*'`, now stated explicitly rather than inherited.
1616
- **Denial is unchanged.** `enforceExportPermission` → `security.canExport` still answers `403 EXPORT_NOT_PERMITTED`, and no request that was refused is now accepted. This is the affordance half: the channel that is supposed to tell the client now does.

‎.changeset/20134-super-user-entries-every-bit.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ fix(core): an effective-map entry reached through a super-user `'*'` carries eve
1414

1515
**What a reader of `/auth/me/permissions` sees.** For a subject holding a super-user wildcard, entries gain `true` bits (`allowTransfer`, and the wildcard's own plain and export bits). Where that subject's wildcards also grant `allowExport`, the map gains an entry for each registered unrestricted object that had none. That entry carries no `apiOperations` — unless the object declares `enable.apiEnabled: false`, which is annotated `[]` since #20135 — so for every other such object the operation channel says what it said before and a client's default-allow path is unchanged. Nothing is removed and no `true` bit turns `false`. The response is byte-identical for a subject holding no super-user wildcard: `member_default` alone, and `viewer_readonly` or `organization_admin_no_bypass` beside it. The response shape, its keys and the route are unchanged. On the write path, a `can(object, 'transfer')`-gated option or default is now admitted for these subjects wherever the server grants `transfer`.
1616

17-
**Still broader than the server, unchanged here.** The fold still folds the merged super-user bits into an entry the super-user set itself names narrower. It also still pulls `allowCreate` on `modifyAllRecords` alone, which the server does not grant. Both over-grants are left exactly as they were.
17+
**Still broader than the server, unchanged here.** The fold still folds the merged super-user bits into an entry the super-user set itself names narrower. It also still pulls `allowCreate` on `modifyAllRecords` alone, which the server does not grant. Both over-grants are left exactly as they were by this change, and both are closed in this same release (`.changeset/20136-super-user-fold-per-set.md`).
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/core': patch
3+
---
4+
5+
fix(core): the effective object-permission map grants no cell the server refuses for a super-user subject — a super-user set's own narrower entry is that set's answer, and `modifyAllRecords` alone grants no create (#20136)
6+
7+
`buildEffectiveObjectPermissions` builds the `objects` slot of `GET /auth/me/permissions` (`@objectstack/plugin-hono-server`) and the map `ISecurityService.getEffectiveObjectPermissions` returns (`@objectstack/plugin-security`), which the engine hands to `current_user.can(object, verb)` on the write path. For a subject holding a super-user wildcard — a `'*'` carrying `viewAllRecords` or `modifyAllRecords` — the map granted cells that `PermissionEvaluator.checkObjectPermission` refuses. Before building each set's own contribution, it ran a fold over the MERGED map that put the merged bypass bits on every entry:
8+
9+
- **Into an entry the super-user set names itself.** The server answers each set with its explicit entry for the object when it has one, so the set's wildcard never reaches that object. The walled `organization_admin` names `sys_position`, `sys_permission_set`, `sys_position_permission_set`, `sys_user_permission_set` and `sys_user_position` read-only, and the identity tables write-denied; the map granted create, edit and delete on the first five anyway, and edit on `sys_organization`. With `member_default` that was 38 cells, and 44 without it (edit on `sys_user` and `sys_api_key` as well). An explicit `{}` entry read as readable and writable. An explicit entry granting `allowExport` without read read as readable and exportable, so `apiOperations` offered `export` where the export door answers `403 EXPORT_NOT_PERMITTED`.
10+
- **`allowCreate` on `modifyAllRecords` alone.** The spec's `objectPermissionGrants` gives the write bypass no create cell, and the server grants none. A `'*': { modifyAllRecords: true }` without `allowCreate` read `create` and `import` as granted on every object the managed-write clamp does not cover.
11+
12+
On the write path this failed OPEN: an option gated on `current_user.can('sys_position', 'edit')` was admitted for the walled `organization_admin`, whom the server refuses that edit.
13+
14+
Clause-②: no
15+
16+
**What changes.** The merged fold is no longer a step of `buildEffectiveObjectPermissions`. The super-user fold is the per-set one alone: each set's super-user `'*'` puts on every entry that set does not name exactly the bits the spec's `objectPermissionGrants` says that wildcard grants. A set that names an object keeps its explicit entry as its whole answer for that object, and another set's super-user wildcard still widens that entry bit by bit, as `checkObjectPermission` combines sets. The seed, the plain-wildcard coverage, the managed-write clamp and the `apiOperations` annotation are unchanged. `checkObjectPermission` and every route are unchanged, so no request changes its answer on the server.
17+
18+
**What a reader of `/auth/me/permissions` sees.** For a subject whose super-user set names an object narrower than its wildcard, or whose only create grant was `modifyAllRecords`, the entry reads what the server enforces: `allowCreate`, `allowEdit`, `allowDelete` or `allowRead` turn from `true` to `false` on those cells, and an entry whose export no longer holds gains an `apiOperations` list without `export`. No entry is added or removed and no bit turns from `false` to `true`. The response is byte-identical for every subject whose super-user sets name no object narrower and grant `allowCreate` wherever they carry `modifyAllRecords` — `admin_full_access` alone or beside `member_default` — and for every subject holding no super-user wildcard. The response shape, its keys and the route are unchanged. On the write path, a `can()`-gated option for those cells is now refused and a `can()` default reads `false`, as the server refuses the write they describe.
19+
20+
**For a caller of the exported helper.** `foldWildcardSuperUser` keeps its name, signature and body, and `@objectstack/plugin-hono-server` still re-exports it. It is no longer what the map is built from: over a merged map it cannot tell a set's own entry from another set's. Build the map with `buildEffectiveObjectPermissions`.

0 commit comments

Comments
 (0)