Skip to content

[security] A datasource read path serves connection topology to signed-in members below the capability the datasource admin door requires — detail withheld pending maintainer #21087

Description

@objectstack-fleet

Filing gate: ① a reproducible defect with a named landing site — the read gate for datasource metadata; the rationale it contradicts is written in packages/services/service-datasource/src/admin-routes.ts (the manage_platform_settings gate). reach: the public REST API, reproduced twice by the P0 run's runner (not yet independently re-derived — the dev's premise check is the second derivation). Reader: the maintainer, who holds the reproduction; dispatched directly at the maintainer's request. Dedupe: semantic search on datasource metadata reads by members below the platform-settings gate (open + closed) → 3 hits, none this path; nearest, closed: #9593 (the admin door's capability gate).

QA-source: #21056 · integration-system.datasource-credential-refusal-matrix · acceptance[7]

What is known publicly

  • The datasource admin door refuses a signed-in member without manage_platform_settings (403), and admin-routes.ts explains why: a lower gate "would publish a deployment's connection topology to every authenticated tenant user".
  • A second read path for the same datasources does not apply that gate: such a member reads the connection topology (driver, host, port, user part of the url, file paths) and the secret-binding handle. No secret VALUE is served — the bodies equal the admin's redacted bodies — so the run's acceptance[7] held as written.

Detail withheld pending maintainer (RUNNER rule 2). Maintainer ruling, verbatim (Claude Code session session_01MRdbfpy4sQT8bUjmMhxsN7, 2026-10-01):

只给我看的两条,你也直接派发处理

Landing site

The read gate for datasource metadata on the path that bypasses the admin door's capability; the maintainer holds the reproduction.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2
    Session: session_01MRdbfpy4sQT8bUjmMhxsN7
    Account: os-bill
    Branch: claude/issue-21087-datasource-meta-read-gate
    Worktree: objectstack-issue-21087
    Domain: domain:services (expected — dispatched at the maintainer's direction ahead of a triage grade; the landing sits in the datasource read paths)
    File surface: the read gate for datasource metadata (packages/services/service-datasource/src/admin-routes.ts and the metadata read path that bypasses its capability; exact file declared by the dev); stop on breach
    Container & model: M, mode:subagent, tier default (security-boundary fix; dispatch-gates --tier: no path-derived mandate)
    Clause-②: no
    Thread-read: none
    Serial constraints cleared: 34 in-flight pm:dispatched claims scanned, none declares the datasource redaction or read-gate files. Sibling #21086 is dispatched this round on an adjacent datasource read path: each dev declares its files before editing, and on any shared file the later one stops and reports.

    Dispatched at the maintainer's direction. Ruling verbatim (Claude Code session session_01MRdbfpy4sQT8bUjmMhxsN7, 2026-10-01): 「只给我看的两条,你也直接派发处理」. The reproduction goes to the dispatched dev in-session only. ⛔ It does not go into any GitHub text.

    Landing: a security/permission-boundary change, so the PR stays draft for the maintainer's review (human floor).


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: grade after dispatch — bug · security · priority:p1 · domain:services · area:access. ⛔ The claim, the assignee and the state are unchanged

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-01T06:03Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ It carries no detail beyond the card's own sanitized text.

    The card was dispatched at the maintainer's direction ahead of a grade. This act adds the grade only.


    Generated by Claude Code

  3. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    bugSomething isn't working
    priority:p1High: required for production / M2
    on Oct 1, 2026
  4. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
    "issue": 21087,
    "status": "done",
    "branch": "claude/issue-21087-datasource-meta-read-gate",
    "pr": "#21119",
    "session": "session_01MRdbfpy4sQT8bUjmMhxsN7 — this run's harness-stamped id (subagent: the parent's; the relay derived the same id from CLAUDE_CODE_REMOTE_SESSION_ID)",
    "premise_still_valid": true,
    "summary": "Premise re-derived independently on a booted showcase (fresh SQLite, seeded admin, a member created and password-changed): the second derivation agrees with the runner's observation and confirms the PM's mechanism hypothesis (detail in-session only). Fix: one type-level read admission, metaTypeReadRefusal over META_TYPE_READ_CAPABILITIES in packages/rest/src/meta-item-read-gate.ts, asked by both transports at their single /meta entry right after the anonymous deny and before any store read (RestServer's guarded registrar in packages/rest/src/rest-server.ts; handleMetadataRequest in packages/runtime/src/domains/meta.ts; export in packages/rest/src/index.ts). GET/HEAD of datasource on every /meta read shape (list, item and its switches, /published, /layers, /history, /audit, /diff, /references) now requires manage_platform_settings, the admin door's capability, with the same 403 PERMISSION_DENIED for existing and absent names; holders are served as before; writes untouched. Bounded in-place addition (same class, same files, one row): external_catalog held to the federation read door's capability (same one) — flagged in open_questions. No spec schema, @objectstack/spec export or response shape changes; this narrows who may read a published route (runtime permission change). Dispatch question 1: no member-facing consumer of /meta/datasource at the objectui pin e420df310 (Setup datasource page uses the admin door; MetadataProvider loads object/dashboard/report/page/view/app only; DraftChangesPanel is the one generic lister, author-facing, noted in the PR); CLI os meta list/get is generic; packages/mcp exposes no datasource body. Question 2: the shared per-document gate (createMetaItemReadGate/createMetaListReadGate) runs after the store read so a datasource arm there would be an existence oracle; the type registry declares no read capability and adding one is a spec change; so the seam is a type-level predicate in the same shared module, asked at both entries. PR is draft and stays draft (human floor). Worktree removed after the PR opened; dev server I started torn down by its recorded process group.",
    "tests": "All on HEAD 220d080 unless stated. NEW packages/rest/src/meta-type-read-capability.test.ts 53 passed (predicate battery; every GET door under /meta/:type read off the route table plus ?state=draft/?preview=draft/?layers=true/?package= for datasource|datasources|external_catalog|external_catalogs x member/author(manage_metadata+studio.access+setup.access)/other-grant → 403 PERMISSION_DENIED with 0 store reads, byte-identical for an absent name, holder 200 on every shape, unlisted type served; admin-door agreement over resolveAuthzContext + one grant store, only the holder admitted by both). NEW packages/runtime/src/domains/meta-type-read-capability-parity.test.ts 19 passed (dispatcher refuses member/author on list/item/published/layers/absent name with 0 reads, serves holder, answers every caller as RestServer does). Adjusted fixtures: meta-unknown-type-read-refusal.test.ts caller now also holds manage_platform_settings (its external_catalogs row), meta-list-protocol-fault.test.ts datasource row reads as a manage_platform_settings holder. Suites: @objectstack/rest vitest --project local 259 files 5075 passed/143 skipped, --project repo 1 file 8 passed; @objectstack/runtime vitest --project local 298 files 4268 passed/5 skipped, --project repo 3 files 751 passed; pnpm --filter @objectstack/rest typecheck exit 0 and @objectstack/runtime typecheck exit 0 (both include check:test-typecheck). Reverse verification via scripts/ablation-replace.mjs (anchor hit 1→0, blob changed, restore proven blob==HEAD and git diff HEAD empty; tests read src through the vitest @objectstack/rest → src alias, so no dist leg): RestServer seam removed → rest 19 red/34 green, runtime parity 4 red/15 green; dispatcher seam removed → runtime 12 red/7 green; datasource row removed → rest 18 red/35 green, runtime 4 red/15 green (external_catalog rows green). Direction: turn red, as predicted. A first attempt at the row ablation was refused by the tool (replacement count did not rise: a no-op), redone with --delete; the same three legs were first run at 73c8f41 with the same reds. Live: showcase booted on packages built at 611db07, member 403 PERMISSION_DENIED on every datasource and external_catalog read shape incl. absent name, plural and percent-encoded spellings, admin 200, member object/app/view reads 200; env-scoped mount not mounted in that composition (404 for admin too), covered by construction (same guarded registrar), NOT MEASURED live. Gates: dispatch-gates --commands --repo objectstack-ai/objectstack (no paths) 62 commands, all exit 0 on 220d080 (first pass on fd2faaf caught check-system-context-census and check-objectql-double-limit red, both fixed: isSystem arm dropped, grants double replaced with the core suite's bounded double; plugin-teardown-shape --self-test and dual-build-cjs-loads exit 3 prerequisites met by fetching the pinned fixture commit and building 8 unrelated packages); --ran reconciliation: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN (exit codes recorded). Same derivation over this diff applied to origin/main fed0db8: identical 62-command list, patch applies cleanly. Lint narrowed: eslint --no-inline-config --format json over the 8 changed .ts files: 8 files, 0 errors, 0 warnings; eslint.config.mjs enables no type-aware linting (no parserOptions.project), so untouched files' verdicts cannot move; pnpm lint itself declared to CI. CI at report time: 11 check runs completed with no failure, 20 in_progress.",
    "mcp_calls": "0",
    "api_writes": "3 — each one repository_dispatch through scripts/pm fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]: pr_create → POST /repos/objectstack-ai/objectstack/pulls (#21119, draft); label-write assign → POST /repos//issues/21119/assignees (os-bill; no labels written, size/l came from the size labeler); this report → POST /repos//issues/21087/comments. git push (several) is not a REST write.",
    "open_questions": [
    {
    "question": "Keep the external_catalog row in this PR? It closes the same defect class (a /meta read of a datasource-family type admitting callers the type's own door refuses): the cached federation catalog vs the federation read door, FEDERATION_READ_CAPABILITY = manage_platform_settings. ADR-0015 6.2 had written reads at metadata:read; the mounted federation reads were later moved to manage_platform_settings by the ruling recorded on FEDERATION_READ_CAPABILITY, and this row extends that rule to the cached snapshot.",
    "options": [
    "A keep the row (one map entry, pinned by the same tests)",
    "B drop the row here and file the catalog as its own card"
    ],
    "recommendation": "A, because it is the same mechanism and capability, measured on the booted showcase (member refused by both doors, admin served by both), no consumer at the objectui pin reads it, and dropping it is a one-line change if the maintainer prefers B."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: public REST metadata write door — a datasource write there admits a caller holding an authoring capability but not the capability the datasource admin door requires for create/update (status measured once on the booted showcase; whether a row saved that way reaches a live pool NOT MEASURED); exact request held for the maintainer, as this card's · evidence: the same type's metadata save admission and the admin door's create/update admission name different capabilities · dedupe words: datasource write capability, metadata save door datasource, manage_platform_settings write parity, datasource admin door twin"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    PM review — PR #21119: ACCEPT (CI pending); landing awaits the maintainer (human floor: permission boundary)

    Reviewed-by: PM seat, session_01MRdbfpy4sQT8bUjmMhxsN7. Implemented-by: os-dev subagent, same session.

    • Path surface (read from the PR file list): packages/rest (meta-item-read-gate.ts, rest-server.ts, index.ts plus tests), packages/runtime/src/domains/meta.ts plus tests, and a changeset. There is no governed path. It is disjoint from fix(metadata-protocol)!: stored metadata bodies on the generic data door are served as their type's read projection (#21086) #21115 and from the [security] Family closeout: stored metadata bodies (stored datasource credential material included) are still reachable outside the redacting doors at several further positions — detail withheld pending maintainer #21120 family card.
    • Shape: one type-level read admission (metaTypeReadRefusal over META_TYPE_READ_CAPABILITIES), asked at the single /meta entry of BOTH transports, right after the anonymous deny and before any store read. That ordering means the refusal is identical for existing and absent names. The existing per-document gate runs after the store read, so the dev rightly did not put the arm there. No spec schema, export or response shape changes.
    • Consumers: measured at the objectui pin. There is no member-facing reader of /meta/datasource: the Setup datasource page uses the admin door. The CLI is generic. MCP serves no datasource body.
    • Evidence accepted:
      • 53 new rest cases (every GET under /meta/:type, read off the route table) and 19 new runtime parity cases;
      • three ablations each turn red in the predicted direction and restore byte-identical;
      • full rest and runtime suites pass, typecheck exits 0;
      • 62/62 derived gates exit 0;
      • live: a member is refused on every shape, an admin is served.
    • Disclosure check: the PR title, body, changeset and report comment carry no reproduction.
    • Open question (for the maintainer): the bounded in-place external_catalog row holds the cached federation catalog to the federation read door's capability. It is the same class and the same capability. The PM recommends keeping it.

    The PR stays draft until the maintainer decides the landing.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: routing correction — domain:services → domain:cli. ⛔ The claim, the assignee and the state are unchanged

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-01T08:02Z. ⛔ Not a claim, ⛔ not a dispatch.

    This seat's grade 5925714835 followed the claim's expected domain. The fix, PR #21119, lands in packages/rest and packages/runtime, and the lane table puts both in domain:cli. The error is triage's. Nothing else changes.


    Generated by Claude Code

  7. added 2 commits that reference this issue on Oct 7, 2026
    7a606a9
    c6954d6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:clipriority:p1High: required for production / M2security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions