Uh oh!
There was an error while loading. Please reload this page.
security(rest): require manage_platform_settings on POST /datasources/:name/external/validate (#10255) - #10538
Conversation
…/:name/external/validate The one federation route the #9901 ruling did not name joins the family's read gate: validateAll drives the same live remote-schema introspection the gated read twins expose, so it answers to the same capability. The now-unused 'authenticated' guard kind is removed. Ruled on #10255 (2026-08-20, option A). Fixes#10255 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
📓 Docs Drift Check5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅ What this run could not see
Coarse fallback — 13 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 9b85c4b25fc02bd5d6566a7c9034528863a5e489 && git checkout 9b85c4b25fc02bd5d6566a7c9034528863a5e489
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 359f5956d7910aed7ae9f8fccc9fbb988b3e4882 de8fac4c2b2fb5f11cb3b4d7eb82f5253349a91e && git checkout -B drift-repro 359f5956d7910aed7ae9f8fccc9fbb988b3e4882 && git merge --no-ff de8fac4c2b2fb5f11cb3b4d7eb82f5253349a91e
node scripts/docs-audit/affected-docs.mjs --json 359f5956d7910aed7ae9f8fccc9fbb988b3e4882 |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10255
What
POST /datasources/:name/external/validate— the one route of the external-datasource federation family the #9901 ruling did not name — now requires the family's read capability,manage_platform_settings, above the #9686 authentication floor. The now-unusedauthenticatedguard kind is removed rather than left as a spare lower door a future un-ruled route could silently adopt. Ruled on #10255 (2026-08-20, maintainer accepted option A; the ruling comment records the verbatim acceptance).Why the read capability — established from the service source, not the handler name
packages/services/service-datasource/src/external-datasource-service.ts:validateAllreaches only reads:listObjects, per-objectgetObject/getDatasource, andintrospect. It persists nothing, writes no credential, caches no result — unlikerefreshCatalog, which persists the snapshot and is gated as a write.introspect(datasource)— the same live remote-schema introspection primitive behind the gatedGET /:name/external/tables— and its report names remote columns and types (schema diffs) and carries driver error strings for unreachable remotes. An unentitled caller refused at the tables route could previously still trigger live remote introspection through this route and read what it found.sys_api_key, no org narrowing — because the resolver admits every credential kind the platform admits.Refusal shape is the family's existing one:
403with the standard catalog codePERMISSION_DENIED, the message naming the missing capability; the anonymous floor stays401 UNAUTHENTICATED.Breaking-change surface (clause-②)
This is published SDK surface:
datasources.external.validateonObjectStackClientand the CLI'sos datasource validatereach exactly this route. An integration presenting a valid credential withoutmanage_platform_settingswas served before and is refused now — the same migration as PR #10254's read routes, smaller scale; the changeset documents it in that changeset's shape. No in-repo caller breaks: examples/dogfood do not call this route, the client test mocks fetch, and the auth-guard suite's entitled fixtures already hold the read capability where they must.Test changes — the flip is deliberate
capability: nullrow for validate now carriesREAD_CAPABILITY— the loud table edit the previous pin was designed to force.403 PERMISSION_DENIEDnaming the capability, service never dispatched) with the ruling recorded in the case body, plus a companion case pinning thatmanage_platform_settingsalone clears it.Verification (all at head de8fac4, quoting each runner's own verdict line)
@objectstack/restsuite + typecheck:Test Files 131 passed (131)·Tests 2142 passed (2142);tsc --noEmitexit 0.Tests 3 failed | 8 passed (11)— exactly the three predicted cases (the all-five refusal loop, the flipped[#10255]refusal case, and themanage_metadata-alone cross case), while both success-direction cases stayed green. Fix restored byte-identical from the commit afterwards (cleangit status).node scripts/pm/dispatch-gates.mjs(no path args): all 11 path-matched families green plus the 5 convention-triggered test-file families — includingcheck-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured … none above its recorded number,slot-lookup ratchet holds … none new,where-matcher conformance holds … none new,check-engine-double-contract: OK,check-nul-bytes: OK,✓ This diff introduces no major bump.src, so nodistresolution is involved in either verification leg.Out-of-scope observation filed as #10537 (validate fans out to every federated datasource and post-filters; #10537 is not addressed here).
Generated by Claude Code