Repository navigation
fix(rest): security/explain refuses an empty object with 400 VALIDATION_FAILED instead of 500 - #27892
Conversation
…ON_FAILED An empty `object` passed `ExplainRequestSchema` (`z.string()`), reached the explain engine, and its plain Error fell to the route's 500 EXPLAIN_FAILED arm. The door now refuses it 400 VALIDATION_FAILED in its own envelope before any evaluation, through the same check that refuses an empty `userId`: one table of keys, each with its remedy. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL
📓 Docs Drift CheckThis PR changes 1 package(s): 23 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 19 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 2522f5d6e1c4ba2ba2a53fb6cddc7acea7c65675 && git checkout 2522f5d6e1c4ba2ba2a53fb6cddc7acea7c65675
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 64e5efde9e86f3f823b861da289b547310b041ab 4f5150d8d1f7d5a2230cab4ae7a1460cc81ff5dd && git checkout -B drift-repro 64e5efde9e86f3f823b861da289b547310b041ab && git merge --no-ff 4f5150d8d1f7d5a2230cab4ae7a1460cc81ff5dd
node scripts/docs-audit/affected-docs.mjs --json 64e5efde9e86f3f823b861da289b547310b041ab
|
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL 维护者速读
席位意见:ACCEPT。 席位核对Head-sha:
|
Requested by Elon · project thread
Fixes #27719
Clause-②: no
Metadata-protocol: none
维护者速读
POST/GET /api/v1/security/explain)时,object传空字符串现在直接回 400VALIDATION_FAILED,不再回 500EXPLAIN_FAILED。userId回 400)同一道门、同一种处理。packages/rest/src/security-routes.ts(EMPTY_KEY_REMEDIES表与 handler 里那一个循环)、packages/rest/src/security-routes.test.ts([#27719]一组)。pnpm --filter @objectstack/rest exec vitest run --project local src/security-routes.test.ts,预期 22 个用例全部通过。What changes
ExplainRequestSchema.objectisz.string(), so an emptyobjectpassed the schema, reached the explain engine, and the engine's plainError('[Security] explain: request.object is required')fell to the route's catch-all500 EXPLAIN_FAILEDarm.The route now refuses it
400 VALIDATION_FAILED, in its own envelope and before any evaluation, through the check that already refused an emptyuserId. That check is now one table,EMPTY_KEY_REMEDIES(module-private, no export changes), with one row per key and the remedy its refusal names. The handler loops over it once, so theuserIdrefusal is not copied. TheuserIdmessage is byte-identical to before.Per the triage ruling, the refusal is graded at the route.
packages/specis not touched. Moving both keys intoExplainRequestSchema(min(1)) is a separate spec narrowing that this PR does not grade.Files:
packages/rest/src/security-routes.ts,packages/rest/src/security-routes.test.ts,.changeset/27719-explain-empty-object.md(@objectstack/restpatch).Tests
New
[#27719]block inpackages/rest/src/security-routes.test.ts. Its fake service throws the engine's own plain error for an empty object:POSTwithobject: ""gets 400,success: false,error.codeVALIDATION_FAILED, and no verdict. The engine is never called.GET ?object=is refused the same way.POSTwithupdate, andGETdefaulting toread).The
[#27442]empty-userIdblock runs unchanged and passes.Run on head
4f5150d8(objectstack-ai/objectstack):Red first, on the unfixed route: 2 failed | 20 passed
Green after the fix: 22/22; the whole package 347 files / 6737 tests; typecheck exit 0
Ablation: delete the object row from EMPTY_KEY_REMEDIES, and both new refusal pins go red; restored byte-identical
The route test imports
./rest-serverrelatively, so it runs againstsrc/and no dist rebuild was needed for this leg.End to end: real showcase boot as the seed admin, through a throwaway dogfood probe that was not committed
Acceptance notes
objectrefusal names its remedy ("Give the name of the object whose access to explain."). The test does not pin that wording: it asserts the code, the status, and that the engine was never called.object(the key absent) is still refused by the request schema's own 400. This PR leaves it alone.Verification
Local subset on head
4f5150d8: the gates derived bynode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackfrom 3 changed paths (+93 / -10), run one after another with each exit code captured before any pipe.60 derived, 60 run, 60 exit 0; --ran reconciliation: 0 NOT-MEASURED, 0 UNRUN
pnpm check:dual-build-cjs-loadsfirst exited 3 (PREREQUISITE NOT MET: 8 unrelated packages had nodist/). After building them it exited 0: 113 entry points across 70 packages load. The reconciliation records that rerun.--ranwith:: exit Nper line: "60 derived famil(ies) accounted for — 60 run, 0 NOT-MEASURED (a DERIVED zero — all 60 recorded an exit code and none of them is 3)".npx eslint --no-inline-configon both touched source files: exit 0.test:repo, the full-treepnpm lint, and the type-check lanes and path-scheduled jobs the derivation lists outside its total. No dogfood battery: the title has no!and the clause has no(narrowing)arm.🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL