Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions docs/qa/platform-checklist/areas/access-security.json
Original file line number Diff line number Diff line change
Expand Up @@ -1318,10 +1318,10 @@
},
{
"id": "access-security.owd-save-gate",
"title": "OWD save gate: external ≤ internal on every object write, and a packaged object's OWD can only be tightened at runtime — Studio inline AND server-side",
"title": "OWD save gate: external ≤ internal on every object write, and a packaged object's OWD cannot be changed at runtime — the managed-content seal refuses the write before any OWD rule judges it — Studio inline AND server-side",
"since": "v16",
"status": "active",
"revision": 3,
"revision": 4,
"priority": "P1",
"surface": "mixed",
"personas": [
Expand All @@ -1335,16 +1335,16 @@
"the Studio object designer Settings tab (objectui ObjectSettingsPanel / PackageOwdOverviewPanel), which computes isExternalWider inline"
],
"knownGaps": [
"runtime EVALUATION of externalSharingModel against external principals is liveness `planned` (#2696) — this item tests the AUTHORING/save gate (external ≤ internal at write; packaged tighten-only), NOT external-principal enforcement; do not tick an external-principal read/write result",
"R1 (owd_widening_forbidden) fires on a STOCK deploy too — the escape hatch is NOT what reaches it. What selects the answering layer is the DIRECTION OF THE SUBMITTED BODY, not the deploy posture (measured on stock showcase, #9477): saveMetaItem runs the plugin-security authoring gate BEFORE the overlay/package doors on the host-config kernel the showcase boots (environmentId undefined, so protocol.ts's env-partitioned NOT_OVERRIDABLE branch is skipped), so a WIDENING body meets R1 first and always answers 403 owd_widening_forbidden — hatch open or closed, ?package= named or not. A body that does NOT widen passes R1 and only then meets the overlay door: 403 NOT_OVERRIDABLE (no ?package=) or 403 ITEM_LOCKED (?package= names the read-only base) on stock, and 2xx under OS_METADATA_WRITABLE=object. ⚠ WRITABLE_PACKAGE_REQUIRED was observed on NONE of those six legs — do not expect it here. The run must still record WHICH layer answered the 4xx, and must read the code from `declaredCode`: the dispatcher maps this refusal to `code: PERMISSION_DENIED` on the wire and carries the ledgered spelling in `declaredCode: owd_widening_forbidden`, so a check keyed on `code` alone MISSES it"
"runtime EVALUATION of externalSharingModel against external principals is liveness `planned` (#2696) — this item tests the AUTHORING/save gate (external ≤ internal at write; a packaged object sealed against runtime change), NOT external-principal enforcement; do not tick an external-principal read/write result",
"R1 (owd_widening_forbidden) is UNREACHABLE through the metadata door on a stock deploy, and with OS_METADATA_WRITABLE=object too. saveMetaItem asks the package door (refusePackagedBaseOverride, predicate isSealedManagedItem: the item is artifact-backed and `object` opens no overlay channel) on every kernel topology, ahead of the runtime lint door and the plugin-security authoring gate (#22338). Since ADR-0131 D6 (#22401) the escape hatch no longer opens an item a managed package ships. So any OWD write onto showcase_announcement is refused 403 NOT_OVERRIDABLE (no ?package=) or 403 ITEM_LOCKED (?package= names the read-only base), whatever its direction: widening or tightening, hatch open or shut. The revision-3 reading, measured at #9477, no longer holds: a widening body met R1 first and answered 403 PERMISSION_DENIED with declaredCode owd_widening_forbidden, and a tightened control passed R1 and met NOT_OVERRIDABLE, or 2xx under OS_METADATA_WRITABLE=object. R1 keeps its oracle in unit pins: packages/plugins/plugin-security/src/object-posture-gate.test.ts for its verdict, and packages/rest/src/meta-object-owd-gate.test.ts for the door order that now puts the seal in front of it. ⛔ Do not build a fixture package only to reach R1 live (#23121 triage). If a run does see the R1 refusal on some other door, read the code from `declaredCode` (owd_widening_forbidden), not `code`: the wire `code` is PERMISSION_DENIED"
]
},
"steps": [
"boot showcase with the console; admin session",
"Studio inline (browser): open the object designer Settings for a writable/draft object; set sharingModel=private and externalSharingModel=public_read; screenshot — the panel must flag the D11 external-wider violation inline (owd-sharing.ts isExternalWider) and not offer a clean save",
"server reject: PUT /api/v1/meta/object/qa_owd_probe with sharingModel:'private' + externalSharingModel:'public_read' — capture the 4xx + error code. ⚠️ Drive this at the ACTIVE-state door, NOT ?mode=draft: the D11 refusal is raised by the runtime lint block (validateSecurityPosture, rule security-external-wider-than-internal), and draft saves deliberately defer it to the draft→active promotion gate (#4463 D1), so a ?mode=draft PUT is ADMITTED and the clause cannot be observed there",
"server admit: PUT the same object with externalSharingModel:'private' (≤ private) — capture the 2xx (the gate orders, it does not blanket-deny)",
"packaged tighten (R1): PUT /api/v1/meta/object/showcase_announcement widening its shipped sharingModel public_read→public_read_write (keep externalSharingModel:'private' so the D11 lint is not tripped instead) — capture the 4xx AND its code. Expect 403 with `declaredCode: owd_widening_forbidden` (wire `code` is PERMISSION_DENIED) on a STOCK deploy, no escape hatch needed. Control leg, to prove the probe reached R1 rather than a blanket deny: re-PUT the same body TIGHTENED (public_read→private) — R1 passes it and the next door answers 403 NOT_OVERRIDABLE (or ITEM_LOCKED with ?package=com.example.showcase), 2xx under OS_METADATA_WRITABLE=object",
"packaged object (the seal): PUT /api/v1/meta/object/showcase_announcement widening its shipped sharingModel public_read→public_read_write (keep externalSharingModel:'private' so the D11 lint is not the door under test). Capture the 4xx AND its code. Expect 403 NOT_OVERRIDABLE from the managed-content seal (saveMetaItem's package door, ADR-0131 D6), on a stock deploy and under OS_METADATA_WRITABLE=object alike. With ?package=com.example.showcase the same door answers 403 ITEM_LOCKED. Control leg, to show that the seal answers and not a direction-keyed gate: re-PUT the same body TIGHTENED (public_read→private). It is refused the same way, because a managed item is sealed whatever the body says. R1 (owd_widening_forbidden) is not reached on this door; clause 4 names its unit pins",
"GET /api/v1/meta/object/showcase_announcement before and after the denied PUT — confirm byte-identical",
"cross-check the shipped declarations: GET /api/v1/meta/object/showcase_account and showcase_announcement — both satisfy external ≤ internal"
],
Expand All @@ -1368,10 +1368,10 @@
"evidence": "the PUT trace + read"
},
{
"clause": "a packaged object's OWD cannot be widened at runtime: the widening PUT on showcase_announcement answers 4xx with a ledgered code, recorded with WHICH layer answered. On a stock deploy that is 403 owd_widening_forbidden from the plugin-security authoring gate — the gate runs ahead of the overlay/package doors here, so the escape hatch is not what reaches it (#9477)",
"clause": "a packaged object's OWD cannot be changed at runtime: the widening PUT on showcase_announcement is refused 403 with a ledgered code, recorded with WHICH layer answered. On a stock deploy that is 403 NOT_OVERRIDABLE from the managed-content seal, saveMetaItem's package door (ADR-0131 D6, #22401). It answers ahead of every check that judges the body, on every kernel topology (#22338), and OS_METADATA_WRITABLE does not open it. So the plugin-security R1 gate (owd_widening_forbidden) is unreachable here on stock, and R1's oracle is its unit pins, not this run",
"oracle": "api",
"verify": "PUT /api/v1/meta/object/showcase_announcement (widened) status >=400 and the ledgered code is owd_widening_forbidden. ⚠ read it from `declaredCode`, not `code`: the dispatcher answers `{code: 'PERMISSION_DENIED', declaredCode: 'owd_widening_forbidden'}` on the live REST route, so a check keyed on `code` alone misses the gate it is looking for (measured #9477; note the unit pin packages/rest/src/meta-object-owd-gate.test.ts reads the bare spelling off `code`, which is the in-process shape, not the wire shape)",
"evidence": "the PUT trace + the code + the answering-layer note"
"verify": "PUT /api/v1/meta/object/showcase_announcement (widened, no ?package=) answers 403 with code NOT_OVERRIDABLE and no declaredCode; with ?package=com.example.showcase, which names the read-only base, the same door answers 403 ITEM_LOCKED. Either way the answering layer is the package door, not R1. The door order is pinned by packages/rest/src/meta-object-owd-gate.test.ts (the '[#7674] R1 owd_widening_forbidden through PUT /api/v1/meta/object/:name' block: a widening overlay of a packaged object answers 403 NOT_OVERRIDABLE with no declaredCode and stores nothing, hatch open). R1's own verdict is pinned in-process by packages/plugins/plugin-security/src/object-posture-gate.test.ts (the 'R1 — env-tighten-only over a packaged declaration (ADR-0086 D1)' block). Cite both pins per rule 6 for R1; do not build a fixture package to reach it live",
"evidence": "the PUT trace + the code + the answering-layer note (+ the two cited pin outputs for R1)"
},
{
"clause": "the denied widening is inert: GET /api/v1/meta/object/showcase_announcement is byte-identical before and after the refused PUT",
Expand All @@ -1393,7 +1393,7 @@
],
"variants": [
"R2 external ≤ internal (authoring gate, all object writes)",
"R1 env-tighten-only (packaged/artifact-backed objects)"
"the managed-content seal (packaged/artifact-backed objects: 403 NOT_OVERRIDABLE, or ITEM_LOCKED with ?package=, ahead of R1); R1 env-tighten-only is unreachable on this door and unit-pinned"
],
"traps": [
"stale-console-bundle",
Expand All @@ -1404,7 +1404,9 @@
"packages/lint/src/validate-security-posture.ts#OWD_WIDTH (OWD_WIDTH + SECURITY_EXTERNAL_WIDER lint parity)",
"objectui packages/app-shell/src/views/studio-design/owd-sharing.ts (isExternalWider) + PackageOwdOverviewPanel.tsx + ObjectSettingsPanel.tsx",
"examples/app-showcase/src/data/objects/announcement.object.ts#externalSharingModel + account.object.ts (externalSharingModel declarations)",
"ADR-0090 D11, ADR-0086 D1, ADR-0124 D1, #3050, objectui#2508"
"packages/metadata-protocol/src/protocol.ts#refusePackagedBaseOverride (saveMetaItem's package door — the managed-content seal: isSealedManagedItem, 403 NOT_OVERRIDABLE, or ITEM_LOCKED for a named read-only base; asked on every kernel topology ahead of the body checks since #22338, and not opened by OS_METADATA_WRITABLE since ADR-0131 D6, #22401)",
"packages/rest/src/meta-object-owd-gate.test.ts (the door-order pin: a packaged object's overlay is refused by the package door before the lint door and R1, hatch open) + packages/plugins/plugin-security/src/object-posture-gate.test.ts (R1's in-process unit pins)",
"ADR-0090 D11, ADR-0086 D1, ADR-0124 D1, ADR-0131 D6, #3050, objectui#2508"
],
"history": [
{
Expand All @@ -1414,7 +1416,8 @@
"ref": "claude/platform-test-checklist-ocwugl"
},
{ "revision": 2, "date": "2026-08-18", "change": "corrected clauses 1-2 and steps 2-3 for the #8310 retirement (maintainer ruling 2026-08-13). The item tested R2 owd_external_wider / 403 at the ?mode=draft door; R2 is RETIRED from object-posture-gate.ts as a duplicate of the runtime lint door, whose refusal is 422 INVALID_METADATA rule security-external-wider-than-internal raised at the ACTIVE-state door — and draft saves deliberately defer to the draft-to-active promotion gate (#4463 D1), so the clause was unobservable where it was pointed (#9453 CF-2). ⚠️ #9453 CF-3 (that the R1 knownGap mispredicts which layer answers) was NOT applied: object-posture-gate.ts states verbatim that R1 applies only to overlay writes over an artifact-backed object and that 'the default deploy already 403s these before this gate runs' (if (!ctx.isArtifactBacked) return), which is what the knownGap and clause 4 already say", "ref": "#9386" },
{ "revision": 3, "date": "2026-08-19", "change": "the R1 knownGap, step 5 and clause 4 named the WRONG answering layer, and #9453 CF-3 was RIGHT — re-measured on a genuinely stock showcase boot and OVERTURNING the revision-2 note above (#9477). Proven stock from the server process's own /proc/<pid>/environ: zero METADATA_WRITABLE occurrences among 147 vars. Target artifact-backed, proven by GET ?layers=true returning a populated `code` layer (_packageId com.example.showcase) with `overlay: null`. A widening PUT then answered 403 declaredCode owd_widening_forbidden — reproduced twice, object byte-identical after. The mechanism the old text missed: saveMetaItem runs runAuthoringGate BEFORE the overlay/package doors on the host-config kernel the showcase boots (environmentId undefined, so protocol.ts's env-partitioned NOT_OVERRIDABLE branch never arms — the #7674 note in that file says the showcase is exactly that shape). So the deploy posture does not select the layer; the body's DIRECTION does. Control leg (same boot, same object, tightened body) passed R1 and answered 403 NOT_OVERRIDABLE, i.e. the door the knownGap called 'earlier' is in fact LATER. Six legs measured (widen/tighten x stock/hatch x ?package= present/absent) and WRITABLE_PACKAGE_REQUIRED appeared on none. ⛔ Not a product defect: every widening leg refused and the object never changed — object-posture-gate.ts behaved exactly as its executable guard (if (!ctx.isArtifactBacked) return) specifies; only the prose parenthetical in that file's header, and this item, mispredicted the door order", "ref": "#9477" }
{ "revision": 3, "date": "2026-08-19", "change": "the R1 knownGap, step 5 and clause 4 named the WRONG answering layer, and #9453 CF-3 was RIGHT — re-measured on a genuinely stock showcase boot and OVERTURNING the revision-2 note above (#9477). Proven stock from the server process's own /proc/<pid>/environ: zero METADATA_WRITABLE occurrences among 147 vars. Target artifact-backed, proven by GET ?layers=true returning a populated `code` layer (_packageId com.example.showcase) with `overlay: null`. A widening PUT then answered 403 declaredCode owd_widening_forbidden — reproduced twice, object byte-identical after. The mechanism the old text missed: saveMetaItem runs runAuthoringGate BEFORE the overlay/package doors on the host-config kernel the showcase boots (environmentId undefined, so protocol.ts's env-partitioned NOT_OVERRIDABLE branch never arms — the #7674 note in that file says the showcase is exactly that shape). So the deploy posture does not select the layer; the body's DIRECTION does. Control leg (same boot, same object, tightened body) passed R1 and answered 403 NOT_OVERRIDABLE, i.e. the door the knownGap called 'earlier' is in fact LATER. Six legs measured (widen/tighten x stock/hatch x ?package= present/absent) and WRITABLE_PACKAGE_REQUIRED appeared on none. ⛔ Not a product defect: every widening leg refused and the object never changed — object-posture-gate.ts behaved exactly as its executable guard (if (!ctx.isArtifactBacked) return) specifies; only the prose parenthetical in that file's header, and this item, mispredicted the door order", "ref": "#9477" },
{ "revision": 4, "date": "2026-10-13", "change": "clause 4, acceptance[3], re-pointed to the managed-content seal, with step 5, the R1 knownGap, the first knownGap's packaged tighten-only parenthetical, the R1 variant, the title and two new source notes (stale spec, #23104 A3). Revision 3 measured, at #9477, that a widening PUT on showcase_announcement met R1 first and answered 403 PERMISSION_DENIED with declaredCode owd_widening_forbidden. Two later changes reorder that. #22338 made saveMetaItem ask the package door on every kernel topology, ahead of the checks that judge the body. ADR-0131 D6 (#22401, e02833c24) made the OS_METADATA_WRITABLE hatch stop opening an item a managed package ships. A packaged object, whose type opens no overlay channel, is now refused 403 NOT_OVERRIDABLE (ITEM_LOCKED with ?package=) whatever the body says, so R1 is unreachable on this door on stock. The protection holds: nothing widens. Per the #23121 triage, no fixture package is built to reach R1. Its oracle is its unit pins, named in the clause: plugin-security object-posture-gate.test.ts for the verdict, and rest meta-object-owd-gate.test.ts, whose R1 block now pins the package door answering first, hatch open", "ref": "#23121" }
]
},
{
Expand Down
Loading
Loading