Repository navigation
fix(metadata-protocol)!: refuse a $searchFields entry naming a secret / password field at the data door - #27936
Conversation
… / password field at the data door The door resolved the allowed search set from the object's declared searchableFields, which kept a credential entry, so the request passed and the engine's credential withhold (ADR-0100) then searched the rest of the declared set: a narrower search answered with a wider one. The door now refuses such an entry 400 INVALID_FIELD, judged by isCredentialFieldType, the predicate the engine's withhold reads. The engine's withhold stays as the fail-closed floor. Claude-Session: https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F Co-authored-by: Claude <noreply@anthropic.com>
…archfields-credential-refused
…archfields-credential-refused
📓 Docs Drift CheckThis PR changes 1 package(s): 10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 10 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 504e4860f6827a090b1ccbcedbc12cbffaea0125 && git checkout 504e4860f6827a090b1ccbcedbc12cbffaea0125
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ed0e71db94a73acc02512ff2ee0655106d02e585 f6a224d16daa1c9f5448edbee491053a73c7d1ec && git checkout -B drift-repro ed0e71db94a73acc02512ff2ee0655106d02e585 && git merge --no-ff f6a224d16daa1c9f5448edbee491053a73c7d1ec
node scripts/docs-audit/affected-docs.mjs --json ed0e71db94a73acc02512ff2ee0655106d02e585
|
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F Contract-review-claim: session_01L6bhgUCizoYWQVJWjhdR6F · head f6a224d · 2026-10-30T04:37Z |
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F Contract reviewServed-tier: Read-only review of the net diff ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F 维护者速读(席位终稿)
席位意见: ACCEPT。收窄与引擎的凭据谓词完全一致,每种写法都经过同一道门,契约复核 PASS。 席位核对ACCEPT · head
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Requested by Sales · project thread
Fixes #27829
Clause-②: no (narrowing) — the data doors (
GET /data/:object,POST /data/:object/query, and every other caller offindData) refuse a search-field override naming asecret/passwordfield, which they answered 200 over the rest of the declared search set before. Nothing is widened.Metadata-protocol: none
Skills-docs: none — no skill teaches or emits a credential name in
searchFields(git grep -n "searchFields\|searchableFields" -- skillsat this head: no hit mentions asecret/password/ credential field); the one docs page that lists the search refusals gains the row here (content/docs/api/data-api.mdx).维护者速读
searchableFields声明了secret/password字段时,请求里$searchFields(或 POST 的searchFields/ 对象形search.fields)点名该字段,数据接口现在回 400INVALID_FIELD并点名字段,不再读库。api_key,拿到的是按name匹配的行,看不出任何异常(不泄露凭据,但答非所问)。$searchFields里点名凭据字段的调用方;Console 列表搜索会原样回传视图的searchableFields,若某视图声明了凭据字段,搜索会从 200 变 400(lint 早已把这种声明报为错误)。引擎侧的凭据隐藏不动,仍是兜底。回滚 = revert 本 PR。packages/metadata-protocol/src/protocol-query-validation.ts(assertSearchFieldsAreSearchable新增的credential一支与它的文案),packages/rest/src/data-search-fields-credential-door.test.ts(门级钉子)。pnpm --filter @objectstack/rest exec vitest run src/data-search-fields-credential-door.test.ts,预期 6 个用例全过(secret、password 各三:GET 拒收、POST 拒收、对照组照旧)。What changes
assertSearchFieldsAreSearchable(metadata-protocol/src/protocol-query-validation.ts): after the allowed set is resolved, a requested name the set KEPT whose field type is a credential (isCredentialFieldType,@objectstack/spec/data, the predicate the engine'scollectCredentialFieldswithhold reads) is refused400 INVALID_FIELDwithfield,fields,objectandparamset, in the envelope every other refusal of this gate uses. Message:Field 'api_key' on object 'OBJ' is type 'secret', a credential, and cannot be searched. …, the wording family the auto-default path already gives a credential name ("is type 'secret'"), plus the remedy: remove it fromsearchableFieldswhen the object declares it, search another column. Only a declaredsearchableFieldskeeps a credential in the allowed set (the auto default excludes the types and still refuses them with its own message, unchanged).search.fields, becauseprotocol.tshands both to this one gate (measured: the POST legs below). No line of the hot fileprotocol.tsmoves.rest/src/data-search-fields-credential-door.test.ts: realRestServer→findData→ObjectQL→ sqlite, the data: a blank or comma-only$searchFieldsis read as absent, so a search the caller narrowed runs over the default search fields instead of being refused #27484 harness; two objects declaringsearchableFields: [name, api_key]withapi_keysecretandpassword. GET$searchFields=api_keyand$searchFields=name,api_key, POSTsearchFields: ['api_key']and object-formsearch.fields: ['name', 'api_key']each answer 400INVALID_FIELDnamingapi_keyand its type, with zero reads of the object; controls$searchFields=name, no$searchFields, and POSTsearchFields: ['name']answer 200 with the twonamematches.content/docs/api/data-api.mdx..changeset/27829-search-fields-credential-refused.md(patchon@objectstack/metadata-protocol, BREAKING banner, ADR-0087not-required).Tests
Head
f6a224d1(merge oforigin/maind770b150onto the fix commit1d6b7d0d). Every reading below is from this head unless it says otherwise.Red first, then green (door pin)
637cfc67):data-search-fields-credential-door.test.ts2 failed / 2 passed of 4 —$searchFields=api_keyanswered200withqq7-a,qq7-b(matched inname), for bothsecretandpassword: the card's reading.data-search-fields-credential-door.test.ts+data-empty-search-fields-door.test.ts11/11.Package suites and typecheck
pnpm --filter @objectstack/metadata-protocol test:: exit 0 · Test Files 282 passed, 4 skipped (286) · Tests 21843 passed, 26 skipped (this head; underos-verify-lock.sh, VERDICT command-exit 0)pnpm --filter @objectstack/metadata-protocol typecheck,pnpm --filter @objectstack/rest typecheck:: exit 0 (this head)pnpm --filter @objectstack/rest test:: exit 0 · Test Files 348 passed (348) · Tests 6746 passed, 332 skipped (on1d6b7d0d; the merge brought norestchange)--no-inline-configoverpackages/metadata-protocolandpackages/rest:: exit 0Ablation (red, then restored to the HEAD blob)
node scripts/ablation-replace.mjs --file packages/metadata-protocol/src/protocol-query-validation.ts --anchor ": credential.length > 0 ? [credential, 'credential' as const]" --replacement ": (globalThis as any).ABLATE_27829 === 'refuse' && credential.length > 0 ? [credential, 'credential' as const]"— anchor 1 → 0, blob3028c46e→774faa00; rebuilt;ablation-dist-preflight.mjs @objectstack/metadata-protocol ABLATE_27829hit indist/index.jsanddist/index.cjs.secretandpassword) answered200with thenamematches; the two control legs stayed green.3028c46e,git diff HEADempty; rebuilt; preflight--absentclean over 24 built files, tree clean; door test 6/6 again.Dogfood battery (this head)
pnpm exec turbo run build --concurrency=2 --filter='./packages/**'(76/76) thenpnpm --filter @objectstack/dogfood testunderos-verify-lock.sh, no shard, no path:VERDICT command-exit 0· Test Files 357 passed, 3 skipped (360) · Tests 2875 passed, 20 skipped (2895) · no failures.An earlier run on
c55d2e92(an older merge ofmain) had 4 failures in 3 files (error-sentences-live×2,object-hook-lifecycle-scratch,platform-app-object-entry-views), none search-related;mainfixed them in #27878 and #27873, and this head includes both.Verification
Local subset, head
f6a224d1:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 90 families; every one run with its exit code captured before any pipe, all 90exit 0(check:type-check-debtunderos-verify-lock.sh).--ranreconcile: "90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN" (a derived zero: every entry carries its exit code). On the first pass (1d6b7d0d)spec check:skill-examplesandcheck:dual-build-cjs-loadsanswered exit 3 (PREREQUISITE NOT MET, dists unbuilt); both are exit 0 here after the full package build, not read as a verdict before. CI-run families: see this PR's check-runs.Acceptance notes
ui/packages/plugin-list/src/ListView.tsx,ui/packages/plugin-grid/src/objectGridRowLoad.ts) sends the VIEW'ssearchableFieldsas$searchFieldsverbatim. A view declaring a credential entry used to search the rest of its list silently; it now answers 400 naming the field.validate-searchable-fields(@objectstack/lint) already reports that declaration as an error on every surface, so the fix for the author is the one the lint names.declaredbranch of the existing "not searchable" message lists the allowed set (allowed.join(', ')), which still names a declared credential entry. Cosmetic, not changed here.metadata-protocol/src/protocol.tswas fix(metadata-protocol)!: a datasource restored or published from a stored body may name only the credentials reference its row holds now #27860 (headfd02314b);git merge-tree --write-treeof this branch against it was clean (exit 0), and it has since merged intomain, which this head includes. No open PR touches the other changed files.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F