Skip to content

Datasources: POST /api/v1/datasources accepts and silently drops a top-level password or connectionString that os compile refuses, answering 201 #28549

Closed
1 of 1 issue completed

Description

@objectstack-fleet

domain:devx#1 · sunhaolin · session_017xRBcUWM7N73xRVd64cyAE

QA-source: #28545 · integration-system.datasource-credential-refusal-matrix · observed beside the clauses

The runtime datasource admin door and the build door disagree on a credential written at the top level of a datasource body. os compile refuses it by name with the "must never be inlined" prescription. POST /api/v1/datasources answers 201 and drops the key without a word, so the author gets a datasource that cannot connect and no explanation. No credential is stored or served, so nothing leaks. Found by an off-clause probe and reproduced twice.

Reproduction (stock showcase at eeec88d1, as admin, with a PostgreSQL that requires a password)

  1. POST /api/v1/datasources with {"name":"r7p_top","driver":"postgres","config":{"url":"postgres://app@127.0.0.1:PGPORT/db"},"password":"PLAINTEXT"} → 201. The datasource then fails to connect (SASL … client password must be a string).
  2. GET /api/v1/datasources/r7p_top: the stored record has no password anywhere, and no read door shows it.
  3. The same datasource authored in objectstack.config.ts and run through os compile exits 2 with "Unrecognized key(s) on this datasource: password … must never be inlined …".
  4. Second repro (r7p_top2): a top-level connectionString carrying URL userinfo is dropped the same way, again with 201.

Expected: a 400 naming the key, with the same prescription the build door gives (bind the credential through external.credentialsRef), as the door already does for config.password.
Actual: 201, the key silently projected away.

Mechanism

packages/services/service-datasource/src/datasource-admin-service.ts, createDatasource, around lines 686–692. this.toRecord(input) projects only the known fields into the record, and only then does assertValidRecord run, so an unknown top-level key never reaches validation. The same class of door disagreement was closed for cross-field refinements in #21058.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: 错的必须被响亮拒绝并给处方 | none | P②

    Triage: p3 · bug · domain:server · area:data · 2026-11-01T05:56Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

    Claim: PM loop round 1 · card thread · 2026-11-01T06:57Z
    Session: session_01FEkQ5kE5BrPUPNMMyv6oCH (the seat); the card thread is session_01FEkQ5moUefysT3ba6SpdYY
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-9kvath (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#3
    Ruling-ref: the maintainer, 2026-11-01T03:46Z: 「server 车道现在就是你负责,任务有一大堆啊,都可以派,包括 p3」, with 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」, and 2026-11-01T06:39Z 「server 队列还是很多任务,再开一个席位吧」. Triage 6554155790 grades p3 (the #21058 class of door disagreement).
    File surface: packages/services/service-datasource/src/datasource-admin-service.ts (create and update write doors), the build door prescription source if it must move to a shared spelling, tests, and a changeset.
    Container & model: S/M, mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: high
    Clause-②: no (narrowing) — a body that answered 201 is refused by name, so a contract review at CONTRACT_REVIEW_TIER is owed before the queue.
    Metadata-protocol: none (expected; the dev measures and declares)
    Responsibility: the code is the datasource admin service write door · the platform path is the build door refusal (os compile) and the existing config.password refusal · reached by an admin who posts a credential at the top level and gets a datasource that cannot connect
    Thread-read: 6554155790
    Premise: holds (read at origin/main 310cb96d7: createDatasource spreads ...this.toRecord(input), which projects known keys only); the dev re-measures first
    Serial constraints cleared: none found; no open PR on packages/services/service-datasource at this read
    Resume-from: none — fresh from origin/main

    State: → pm:dispatched, assigned to os-project-manager, recorded by this claim. Order: pm-orders/server/order-28549.md (project folder).

  3. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-project-manager · session_01FEkQ5moUefysT3ba6SpdYY
    os-dev-report
    {
    "issue": 28549,
    "status": "done",
    "branch": "claude/project-thread-9kvath",
    "pr": "#28607",
    "head": "0cc981aa238a292eff24079a3dfcb7a9fe55df1a",
    "session": "session_01FEkQ5moUefysT3ba6SpdYY",
    "premise_still_valid": true,
    "summary": "Premise held at origin/main 310cb96: createDatasource builds the record through toRecord (known keys only), and updateDatasource merges known keys only, so a top-level password or connectionString was dropped and the write answered 201 / 200. Both write doors now call a new assertNoTopLevelCredential(name, body) before anything is read or written: for the keys password and connectionString present on the body it takes DatasourceSchema's own root unrecognized_keys message (values replaced by null, so the credential never reaches the judgement or the answer) under the heading assertValidRecord uses, and throws, which the route answers 400 DATASOURCE_ADMIN_ERROR. One spelling: no prescription text is copied; the pin asserts equality with the spec's message derived in the test. Update refuses on every patch, a label/active-only edit included. Card assignee not touched; no claim posted.",
    "tests": "New src/tests/datasource-admin-top-level-credential.test.ts (real routes over HonoHttpServer, real DatasourceAdminService, in-memory stores, placeholder credentials): 7 refusal cases (POST password, POST connectionString with userinfo, POST both, POST password beside secret, PATCH password, PATCH connectionString, PATCH label-only plus password), each asserting status 400 + code DATASOURCE_ADMIN_ERROR + exact message + value absent + nothing stored/minted/registered; 3 controls (secret-bound create 201 with credentialsRef bound, edit keeping a bound credentialsRef 200, config.password still refused by the driver contract). Red first at base: Tests 7 failed | 3 passed (10), POST reds 'expected { status: 201 ...' and PATCH reds status 200. Green: Tests 10 passed (10). Ablation via scripts/ablation-replace.mjs anchor 'if (present.length === 0) return;' to 'if (present.length >= 0) return;' (anchor 1 to 0, blob fae370d6fc02 to 7e11b57f76be): 7 refusal cases red again, 3 controls green; restore blob == HEAD fae370d6fc02, git diff HEAD empty; the test imports src, no build between mutation and run. pnpm --filter @objectstack/service-datasource test: Test Files 67 passed (67), Tests 1110 passed (1110); typecheck exit 0, tsc --listFiles includes the new test. All on head 0cc981a.",
    "gates": "Local, head 0cc981a: dispatch-gates --commands --repo objectstack-ai/objectstack derived 61 families, each run with exit captured before any pipe, stdin /dev/null; --ran: '61 derived famil(ies) accounted for, 61 run, 0 NOT-MEASURED'. First reads exit 3 re-read after prerequisites: check-plugin-teardown-shape --self-test (git fetch --depth=1 origin 621a487) exit 0; check:dual-build-cjs-loads (full pnpm build --concurrency=2, 117/117) exit 0. Hand gates: check-commit-card-trailers --range origin/main..HEAD exit 0; check-platform-checklist --changed-from origin/main exit 0; check-nul-bytes exit 0; pnpm exec eslint --no-inline-config packages/services/service-datasource exit 0. CI at report time: 21 check-runs on 0cc981a, 1 success, 3 skipped, 17 in_progress (Test Core, lint farm, type-check lanes); the seat reads their conclusions.",
    "dogfood_battery": "0cc981aa · os-verify-lock: VERDICT command-exit 0 (held 1417s) · Test Files 364 passed | 3 skipped (367) · Tests 2934 passed | 28 skipped (2962) · no failure",
    "line_budget": "3 files, +340 / -0 (service 45, test 277, changeset 18)",
    "files_changed": [
    ".changeset/28549-datasource-top-level-credential.md",
    "packages/services/service-datasource/src/tests/datasource-admin-top-level-credential.test.ts",
    "packages/services/service-datasource/src/datasource-admin-service.ts"
    ],
    "deviations": [
    "Order control 'a valid body with external.credentialsRef still answers 201' does not hold on main: create refuses a draft external.credentialsRef (own-reference rule, assertNoDraftCredentialsRef). Controls used instead: create with the credential as secret, bound at external.credentialsRef (201), and an edit keeping a bound external.credentialsRef (200).",
    "connectionString answers with the build door's own text for that key (belongs inside config, as config.url), not the credentialsRef prescription; that is the build door's prescription for that key, taken verbatim from DatasourceSchema.",
    "test:repo not run locally (CI Test Core owns it); the diff adds one test file under src/tests."
    ],
    "mcp_calls": "0",
    "api_writes": "2: relay pr_create (#28607), relay comment on #28549 (this report); plus 1 git push (not REST)",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: c · reach: not measured live (read from toRecord and the update merge at 0cc981a) · POST/PATCH /api/v1/datasources still drop every other top-level key the build door refuses (url, host, port, database, user, username, filename, connector, retired blocks, any unknown key); a top-level url can carry userinfo like connectionString · dedupe words: datasource admin unknown top-level key dropped, url userinfo, toRecord projection · carrier: none, noted in the PR Acceptance notes",
    "class: none · POST /api/v1/datasources/test (testConnection) projects through toRecord and ignores a top-level password; the probe then fails with the driver's own error, not a silent green · carrier: none, noted in the PR Acceptance notes"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

    ACCEPT → PR #28607 comment 6555325439 · head 0cc981aa · 2026-11-01T08:07Z · landing sent (ready + auto-merge; contract review PASS 6555221422). Filed from the review: #28612 (sub-issue, dispatched).

  5. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

    Landing record · #28549 · PR #28607

    domain:server seat 3 · session_01FEkQ5kE5BrPUPNMMyv6oCH · 2026-11-01T08:33Z

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-28607-*' 0 refs (released)
    Ancestry git merge-base --is-ancestor f3d84b33 origin/main yes
    Content git grep -c assertNoTopLevelCredential origin/main -- packages/services/service-datasource/src/datasource-admin-service.ts 3 hits
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:databugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions