Repository navigation
fix(lint): security-owd-unset's fix line on a writable external object names security-external-owner-unmapped's ways out, not 'private' - #27639
Conversation
…t names security-external-owner-unmapped's ways out Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2c5z3q4h8inrn5kwqV
…meta-test Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2c5z3q4h8inrn5kwqV
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2c5z3q4h8inrn5kwqV
📓 Docs Drift CheckThis PR changes 1 package(s): 19 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 87e015dccc0f2c38c594c63e59e5f60369bf0181 && git checkout 87e015dccc0f2c38c594c63e59e5f60369bf0181
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin efa2915e4c2dc1d5bd09bedbd4eef9bc31cb9fe6 78e68ad43b69cdfc71665680aa652e25e3635ccc && git checkout -B drift-repro efa2915e4c2dc1d5bd09bedbd4eef9bc31cb9fe6 && git merge --no-ff 78e68ad43b69cdfc71665680aa652e25e3635ccc
node scripts/docs-audit/affected-docs.mjs --json efa2915e4c2dc1d5bd09bedbd4eef9bc31cb9fe6
|
|
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV The seat review and an independent full Fable review agree. Must fix
Nit (optional)
HoldsThe trigger is otherwise exact. The external rule's findings are byte-identical. The managed hint is byte-identical, and the CLI pin uses a managed object. The Same branch, same claim. The surface stays |
…al object with no provable owner; a read-only binding is told 'public_read' Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2c5z3q4h8inrn5kwqV
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2c5z3q4h8inrn5kwqV
|
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV 维护者速读
席位意见:ACCEPT。独立 Fable 完整复核( 席位核对
|
Requested by 田騏 · project thread
Fixes #27472
Clause-②: no
Metadata-protocol: none
维护者速读
external.writable: true)没写sharingModel时,security-owd-unset的修复提示不再推荐private,改为给出security-external-owner-unmapped的两条出路;os explain security-owd-unset补了一段说明这个例外。private,而可写外部对象照做后,另一条警告又说成员每次编辑都会被拒,两条提示互相打架。packages/lint/src/validate-security-posture.ts(SECURITY_OWD_UNSET分支的 hint)、packages/lint/src/validate-external-owner-sharing.ts(导出复用的两条出路措辞与判定)、packages/lint/src/validate-external-owner-sharing.test.ts(末尾新 describe)。pnpm --filter @objectstack/lint exec vitest run src/validate-external-owner-sharing.test.ts,预期全绿,其中security-owd-unset's fix line on a writable external object五条通过。What changes
security-owd-unsetfix line (validate-security-posture.ts). For a custom object with nosharingModelthatsecurity-external-owner-unmappedwould judge, the hint now names that rule's two ways out instead of recommending'private'. Those objects are a writable binding (external.writable: true), not on a datasource this stack declares withexternal.allowWrites: false, and declaring no ownership column a remote row serves (owner_id, orcreated_byon an object without one). The new hint reads, forspike_owned:declare sharingModel: 'public_read_write' and decide who may edit in a beforeUpdate hook, or declare owner_id in fields and have the driver return the remote owner under that name (a built-in SQL driver does it through external.columnMap; a plugin driver must return it itself) and declare sharingModel: 'private' or 'public_read'; 'controlled_by_parent' for master-detail childrenSeverity (
error), the verdict line andpathare unchanged. Every other object keeps today's hint byte for byte.validate-external-owner-sharing.ts).PUBLIC_READ_WRITE_WAY_OUTand the declaration clause (declareOwnershipColumnWayOut(column)) are now exported from the rule that owns them. The judgement "does this object have an ownership gap" moves intoexternalOwnershipGap(obj, readOnlyDatasources), and the datasource set intoreadOnlyExternalDatasources(stack). Both rules call the same predicate, so the hint swaps on exactly the objects the warning fires on. These are module exports, not barrel exports: the package's public surface is unchanged. The external rule's own findings are byte-identical, and its existing whole-hint pins stay green.os explain security-owd-unset(rule-explanations.ts). A new paragraph explains the writable-external exception to the'private'recommendation, names the two ways out and points atos explain security-external-owner-unmapped.@objectstack/lintpatch.Tests
validate-external-owner-sharing.test.tsadds the describe blocksecurity-owd-unset's fix line on a writable external object:spike_owned(the spike stack, verbatim) gets the external hint (both shared clauses, norecommended), with severity, path and message unchanged;leave_request) keeps the old hint, pinned whole;ownership: 'none'external object is told to declarecreated_by;owner_idand anallowWrites: falsedatasource keep the old hint;public_read_write, orprivateplus a declaredowner_id), clears both rules.validate-security-posture.test.ts: the explanation pin also asserts the external paragraph. The receiver meta-test (validate-expressions / validate-security-posture 也有同形的 spec 不声明键的??别名读法(#5009 建议 3 的核对结果) #5017) excusesownershipGap, which is the helper's own return value and not metadata.scripts/ablation-replace.mjsreplacedconst ownershipGap = externalOwnershipGap(obj, readOnlyDatasources);withundefined(anchor 1 → 0, blobce9dd0f8e857→f178c04309b6). Two of the new pins went red (spike_owned …,names created_by …), 24 stayed green. The tool restored the file (blob == HEAD,git diff HEADempty). The test importssrc/by relative path, so nodist/rebuild was involved.Acceptance notes
external.writable: true". An external object that already declaresowner_id(orcreated_byon an object withoutowner_id), or that sits on anallowWrites: falsedatasource, keeps the'private'recommendation. Theresecurity-external-owner-unmappedis silent and'private'is not the refused shape: the owner edits, and since fix(plugin-sharing)!: an unproven owner anchor on a federated object narrows the read and bulk-write scopes, never leaves them unscoped #27476 reads are scoped by the owner it proves. This follows the card's first clause ("names the ways outsecurity-external-owner-unmappednames"). Where that rule names none, there is nothing to name.content/docs/data-modeling/virtual-objects.mdx, the "Choose the sharing model on purpose" callout) still reads true and is not edited.packages/cli/test/explain-rule-id.test.tspins the managed-object hint, which is unchanged. It is declared to CI: the CLI's dependency closure is not built in this container (the file fails to load on@objectstack/typesresolution, before any assertion runs).Verification
Local runs (head 4e9b79c)
The tests ran on the code tree of
c4b4bd65. The head4e9b79caadds only the changeset, so its code is the same. The gate commands ran at4e9b79ca.pnpm --filter '@objectstack/lint^...' buildunderos-verify-lock.sh: VERDICT command-exit 0.pnpm --filter @objectstack/lint typecheck && pnpm --filter @objectstack/lint testunder the lock: VERDICT command-exit 0;Test Files 177 passed (177),Tests 7604 passed | 5 skipped (7609). The typecheck rantsc --noEmitandcheck:test-typecheck.vitest run src/validate-external-owner-sharing.test.ts src/validate-security-posture.test.ts src/rule-explanations.test.ts src/validate-security-posture.runtime-surface.test.ts:Test Files 4 passed (4),Tests 228 passed (228).scripts/ablation-replace.mjs, see Tests): VERDICT command-exit 1,Tests 2 failed | 24 passed (26), restoredblob == HEAD (ce9dd0f8e857),git diff HEADempty.node scripts/pm/dispatch-gates.mjs --commands: 58 commands derived; all 58 were run with their exit codes recorded.--ran: "58 derived famil(ies) accounted for — 55 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)". 55 exited 0, includingcheck:docs-transcript-drift("4 declared transcript value(s) across 471 page(s) … equal what the registry derives today"),check:doc-authoring,check:nul-bytes,check:cross-package-test-inputsandcheck:type-check-debt.node scripts/check-plugin-teardown-shape.mjs --self-test: a shallow clone cannot reach its pinned positive-control commit;pnpm check:dual-build-cjs-loadsandpnpm check:lean-entry-closure: both read builtdist/of packages outside this closure.packages/cli/test/explain-rule-id.test.ts. The CLI closure is not built here, so the file fails to load (@objectstack/typesentry unresolved) before any assertion runs. It pins the managed-object hint, which this diff leaves unchanged.🤖 Generated with Claude Code
https://claude.ai/code/session_017ihL2c5z3q4h8inrn5kwqV