docs(plugin-security,skills): re-premise member_default's removed wildcard in a published customer skill and in the plugin's own README (#7151) - #7171
Conversation
…dcard in published prose (#7151) Two shipped documents still asserted a `member_default` shape the platform no longer has. Re-measured against the real imported `defaultPermissionSets` at this branch point: `member_default.objects['*']` is `undefined`, and neither `member_default` nor `viewer_readonly` carries a `tenant_isolation` policy — tenant isolation is Layer 0 (`tenant-layer.ts`) since ADR-0095 D1. - `packages/plugins/plugin-security/README.md` described the pre-ADR-0095 probe-and-strip mechanism as the plugin's own current behaviour. Rewritten to the posture resolution, the Layer 0 wall, and the provenance-based strip that actually survives (ADR-0105 D3). - `skills/objectstack-data/SKILL.md` built a customer-facing warning on a leak that cannot happen. The recipe is unchanged and still correct; every stated reason is now the measured one, and the callout names the surviving hazard (any set with a wildcard read grant — `viewer_readonly` still ships one). No runtime behaviour changes; documentation only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BM1tNf5U3nEbHKR4fo5qVQ
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 12 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
|
PM review — PASS. Marked ready and enqueued ( The hard part of this card was handled the right wayThe dispatch envelope said the The callout was re-premised, not deleted. Verified in the diff:
The recipe ( And that turns on the
|
Fixes #7151
The two sites outside #6964 / PR #7149's named file face, carrying the same two expired premises. No runtime behaviour changes — published prose only.
Both premises re-measured at this branch point (
3e8e669c0), not transcribedA probe over the real imported
defaultPermissionSets(npx tsx, real array, real predicates):member_default.objects['*']isundefined— the'*'grant removed bymember_default's*wildcard object grant (C/R/E) union-merges into every org member — app-side explicit-allow object gates are erased on three axes #5491 / PR fix(plugin-security): enforce both declared write-wideners; the platform baseline becomes explicit-allow (#5492, #5491) #6684. Confirmed.tenant_isolationentry, and no wildcard tenant policy of any name, in either set — retired by ADR-0095 D1 in favour of the Layer 0 wall. Confirmed.The
viewer_readonlysub-question the filing left open — answeredThe README names
viewer_readonlybesidemember_default, and #6964 only ever measuredmember_default. Measured here:viewer_readonlyships notenant_isolationpolicy either — itsrowLevelSecurityis_selfidentity-table carve-outs only, and it carries noobject: '*'policy at all. So premise (b) is false for both sets the README names, and no third surface opens.It is not symmetric on premise (a), and this turned out to matter:
viewer_readonlystill shipsobjects['*'] = { allowRead: true, … }. That is the fact the SKILL.md callout is re-premised on below.1.
skills/objectstack-data/SKILL.md— published customer guidanceThis one needed judgement, not a find-and-replace: at⚠️ recommendation on a leak that cannot happen. The recommended recipe (
:764(premise b) and:781(premise a) it built atenancy: { enabled: false }+requiredPermissions) is still correct, so the work was to find what the recommendation legitimately protects and whether anything still protects it.It does — for a different, stronger reason, and the⚠️ now says the true one.
member_defaultships a wildcardtenant_isolationRLS, so untagged rows are denied and the list renders emptysingleposture the wall is inert, so this bites only in multi-org deployments.viewAllRecordsis posture-gated toaccess.default: 'private'ortenancy: { enabled: false }meta.isPrivate || meta.tenancyDisabled || meta.isBetterAuthManaged), and under W2 the bit short-circuits business RLS only — crossing Layer 0 additionally needs a truePLATFORM_ADMIN(superuser bit and a platform-exclusive capability). Anorganization_adminholds the bit and stays org-scoped.tenancy.enabled:falsealone ⇒member_default's'*': allowReadleaks every rowcheckObjectPermission('find', obj, [member_default])isfalseon an object the set does not name, public and private. But the hazard is real and still shipped — it is just notmember_default:viewer_readonly's surviving'*': allowReadreads that object (true, measured), as may an app-declared default profile or a customer-authored set. Theaccess.default:'private'alone ⇒ the admin's'*'grant opts out, admin sees nothing'*'grant that carries the superuser bits, andadmin_full_access's does:read=true. Replaced with the arm the recipe actually pairs —requiredPermissionsalone leaves the object a tenant object, so the wall keeps denying the untagged rows and even a platform admin sees nothing (posturePermitsis false, so the bypass never arms).Measured evidence for the last two rows —
PermissionEvaluator.checkObjectPermission('find', …)on an object no set names:and
computeTenantLayer0Filterunder theisolatedposture:The customer is left with a warning whose stated cause is checkable and true. The recipe, its two keys, and the "admin sees all / non-admins 403" outcome are unchanged.
2.
packages/plugins/plugin-security/README.md:51— the plugin describing itself wronglyIt presented the pre-ADR-0095 probe-and-strip mechanism as current behaviour. The correction is not "delete the paragraph": a probe and a strip both still exist, they just do something else.
single|group|isolated) comes from thetenancyservice, andgetService('org-scoping')is consulted only when that service is not wired.single. Hence neither named set ships a wildcardtenant_isolationpolicy.organization_admin/organization_admin_no_bypass'ssys_member_org/sys_invitation_org/sys_team_org, plus thesys_organization_selfcarve-out — and never an app-authored policy, which reaches the compiler and fails closed there with a one-time operator warning (ADR-0105 D3).Reverse verification — direction stated before running, and it is not a red/green table
Predicted, in writing, before the probes ran: this change edits only prose. No test in the repo reads either file, so there is no assertion that can move in either direction — no red/green table exists to produce, and manufacturing one would be a fabrication. The honest substitute predicted in advance was: run the probe against the real array first, and let the prose follow whatever it says, including the case where it contradicts the issue.
Measured: it did contradict it once. The issue and its triage both frame
:781purely as premise (a). The probe showed the same callout's second sentence (access.default:'private'⇒ "admin sees nothing") is also false, for an unrelated reason nobody had flagged — ADR-0066 D2's superuser carve-out. That sentence was inside the block being rewritten, so leaving a measured-false clause in a callout whose whole point is now correctness was not an option; it is fixed and called out here rather than folded in silently.On adding a guard so this cannot drift again — measured, and the answer is no. The obvious candidate is growing
audience-anchor-set-claims.pin.test.ts'swatchedSurfaces()to cover these two files. It would not have caught this defect. That pin's own header states its limit: it pins the classification against the array, not the prose against the classification — "a row that MISDESCRIBES what its sentences claim is green".member_default's row already readswildcard: false, matching the array, while the SKILL.md sentence claiming'*': allowReadsat green beside it. Adding these surfaces would add rows that cannot go red on the drift they are advertised to catch — coverage that is not. Separately, whether that pin should grow a repo-wide surface is the scoping question #6964 raised and triage has not ruled on; PR #7149 deliberately left it, and deciding it here would be the guess the filing warned against.Gates
pnpm lint(ESLint--no-inline-config, carries the family gates)pnpm --filter @objectstack/plugin-security typechecktsc --noEmit, exit 0)pnpm --filter @objectstack/plugin-security testTest Files 43 passed (43)/Tests 878 passed (878)check:skill-frame-synccheck:doc-authoringcheck:docs-audit-scopecheck:skill-frame-freshnesscheck:skill-compatibilitycheck:role-wordcheck:adr-anchors/check:adr-linkscheck:quick-reference-countscheck:nul-bytesgrep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'self-scan of all three touched filescheck:empty-changesetScope
Deliberately not done: #6964 / PR #7149's file face (
platform-objects,qa/dogfood,content/docs/permissions/index.mdx); nothing undercontent/docs/releases/; noCHANGELOG.mdor release-history row (correctly frozen records that match the same grep); thewatchedSurfaces()scoping question, per above.Checked and left alone:
security-plugin.ts's class docstring says the strip covers "the tenant-scoped RLS policies that ship with the default permission sets" — measured true (it does not say wildcard, andorganization_admin's per-object tenant policies do ship), so it is not a third site.Generated by Claude Code