Skip to content

security(rest): the external-datasource federation family still admits any authenticated caller, while its declared admin twin now requires a capability #9901

Closed

Description

@os-warren

Found while implementing #9593 (the datasource-admin capability gate). Filed rather than fixed there: it is a different registrar in a different package, and #9593's file surface was ruled to be the datasource-admin family.

Disclosure posture — inherited deliberately

⛔ No reproduction here, in any PR body, or in any commit message. #9391 was revised on 2026-08-17 to remove its request/response table after the maintainer deleted the public reproduction, and #9686 inherited that posture for this same area. What follows is the cause, the source location, and the contract a fix would restore.

What is now true

listRemoteTables and generateObjectDraft are each reachable through two mounted routes, in two packages, and the two doors no longer apply the same admission policy:

operation admin spelling (@objectstack/service-datasource) federation spelling (@objectstack/rest)
listRemoteTables GET /api/v1/datasources/:name/remote-tables GET /api/v1/datasources/:name/external/tables
generateObjectDraft POST /api/v1/datasources/:name/object-draft POST /api/v1/datasources/:name/external/tables/:remote/draft
anonymous 401 401
authenticated, no capability 403 PERMISSION_DENIED (#9593) served
authenticated + manage_platform_settings served served

So an authenticated caller holding no platform capability is refused live remote-schema introspection at one spelling and served it at the other — same operation, same external-datasource service slot, same method, two doors, one gate.

Cause

Both guards are correct for what each was scoped to; the asymmetry is the residue of the two cards being deliberately split.

#9686 made the same call from the other side: "Whether these routes should require a capability beyond authentication is a separate question of the kind #9593 answers for the admin family, and should not be folded in." Both deferrals were right individually; this card is the question they both deferred to, now that #9593 has answered it for the admin half.

Why it matters beyond tidiness

This family's equivalence is a maintained invariant, not a coincidence — #4249 gave the two spellings one failure contract, #7955 one request shape, #9686 one admission answer on the anonymous axis. The capability axis is the first on which they are pinned as disagreeing.

The federation family is also the larger surface: it carries three routes the admin spelling has no twin for, two of which write (POST .../external/tables/:remote/import creates a live runtime-origin federated object; POST .../external/refresh-catalog). Those are not covered by #9593 at all and are on the same authentication-only footing.

Unlike the admin spelling, this family is SDK-expressed (datasources.external.* on ObjectStackClient, per the REST route ledger), so any fix must be checked against those callers presenting a credential — that is the half most likely to break, and the reason this is a decision rather than a mechanical copy.

Where it is currently pinned

packages/rest/src/remote-tables-twin.equivalence.test.ts — the twin-equivalence suite — was updated by #9593's PR to record the divergence explicitly rather than hide it, in a case labelled as a record of a known gap. ⚠️ That case is expected to FAIL when this card is fixed, and the fix should fold the row back into the agreement block above it rather than delete it. Read the block header there before changing it.

What a fix would look like (a lead, not a decision)

The federation registrar already receives resolveExecutionContext from direct-mount-composition.ts, and @objectstack/rest's sibling package-routes.ts already reads systemPermissions off that resolved context for its own capability gate — so the seam exists and the shape is established in this package. The open questions are genuinely open and should not be answered by copying:

  1. Which capability, and whether it is the same one for read and write here. security(service-datasource): datasource-admin routes require a platform-settings capability beyond authentication #9593 measured manage_platform_settings with no read/write split for the admin family; this family's write routes (import, refresh-catalog) create metadata, which is manage_metadata territory in this package's other gate.
  2. The SDK callers. datasources.external.* is published surface; a capability requirement is a behaviour change for anyone using it today.
  3. Whether the two spellings should converge at all, or whether the federation family is deliberately the lower-privilege door for a use case the admin family is not. That question is what makes this a card rather than a patch.

Backlinks: #9593 (the admin half, where the capability was measured) · #9686 (this family's authentication guard, which deferred this question) · #9391 (the admin family's authentication guard) · #7955 / #4249 (the twin-equivalence invariants).

Unassigned and unqueued — for the triage seat to level and route.


Generated by Claude Code

Activity

  1. added theissue type on Aug 19, 2026
  2. os-warren commented on Aug 19, 2026

    @os-warren
    CollaboratorAuthor

    Triage: lands in packages/rest/src/external-datasource-routes.ts ⇒ domain:cli; routed to the maintainer inbox (needs-user-decision), type Task — a security/permission-boundary question is on the human floor regardless of facet alignment, and the card's own question 3 (is the federation family deliberately the lower-privilege door?) is a product call.

    Recommendation: A — require a capability on the federation family: manage_platform_settings for the read twins (matching #9593), and manage_metadata for the two write routes (import, refresh-catalog), since they create metadata and that is this package's established gate for metadata creation; fold the equivalence-test divergence row back into the agreement block. B — mirror #9593 exactly (manage_platform_settings on everything, no read/write split). C — rule the federation family deliberately lower-privilege and document it as such.

    Confidence gap: this analysis cannot see real-world datasources.external.* SDK callers — if any production integration calls these routes with a non-admin credential, A and B both break it, and the migration story (staged warn? version gate?) has not been designed.


    Generated by Claude Code

  3. removed their assignment
    on Aug 20, 2026
  4. os-zhuang commented on Aug 20, 2026

    @os-zhuang
    Contributor

    Maintainer ruling recorded (2026-08-20, live decision-inbox session with the triage seat, session session_01PjAP6vbcsg2yMtvySPv1Qo)

    Ruled: tighten the federation family. Provenance: maintainer accepted the batch, verbatim: 「其他接受你的建议。」; this card's presented recommendation:

    1. Read routes (external/tables, external/tables/:remote/draft) gate on manage_platform_settings — converging with the security(service-datasource): datasource-admin routes require a platform-settings capability beyond authentication #9593-measured admin twin, restoring the maintained twin-equivalence invariant on the capability axis.
    2. Write routes (external/tables/:remote/import, external/refresh-catalog) gate on manage_metadata — they create live metadata, matching this package's existing metadata-write gate.
    3. The remote-tables-twin.equivalence.test.ts known-gap case is expected to flip — fold the row back into the agreement block per its own header, ⛔ don't delete it.
    4. datasources.external.* is published SDK surface — the changeset states the new capability requirement plainly for existing credentialed callers.

    The seam exists (resolveExecutionContext already handed to the registrar; package-routes.ts is the in-package precedent). Clause-②: yes (accept-set narrowing) — contract-review tier. Question 3 of the body (deliberate lower-privilege door?) is answered in the negative by this ruling.

    State transition in the same stroke: needs-user-decision → pm:queue.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions