Skip to content

fix(oauth): dynamically registered clients have no owner, so no developer can see or manage them #374

Description

@tahirayan

Summary

Both dynamic registration paths create clients with developerId: null:

  • RFC 7591 Dynamic Client Registration — apps/auth-server/src/app/routes/oauth/register.ts:129
  • Client ID Metadata Documents (CIMD) — apps/auth-server/src/app/helpers/cimd.ts:274

Every developer-facing query keys on that column. GET /api/clients lists by
listByDeveloper(developerId) (routes/clients/index.ts:333), and single-client access is gated
by client.developerId !== developerId (:155). A row whose developer_id is null matches no
developer, so a dynamically registered client is invisible and unmanageable from the developer
portal — it cannot be listed, viewed, edited, deleted, or have its secret regenerated.

Why this is worth attention

CIMD is not a marginal path. README.md describes it as "the primary client-registration path
(MCP 2025-11-25)"
, with RFC 7591 dynamic registration as the documented fallback. So the
registration route the project positions as primary produces clients that no developer can
administer through the product's own administration surface.

The gap is currently invisible because the portal has no way to show a client it cannot query —
there is no "unowned clients" view to look wrong.

How it surfaced

While specifying a developer-facing audit feed, whose ownership predicate is the same
oauth_clients.developer_id join. Enumerating which audit events could ever reach a developer
turned up oauth.client.registered as permanently unreachable — not because of the event, but
because no row from any dynamically registered client can match the predicate at all. A
developer whose clients were all registered dynamically would see a permanently empty feed and no
explanation.

The design question underneath

This may be intentional: a client that registers itself has no authenticated developer to attribute
to, and inventing one would be wrong. If so, the gap is not developerId: null but the absence of
any other ownership signal — and the product should decide what happens to these clients rather
than leaving them orphaned by default.

Options worth weighing:

  • Attribute at registration when the request carries developer authentication (DCR is open-mode
    today, so this may not apply).
  • A claim flow: a developer proves control of a CIMD document's origin and adopts the client.
  • Realm-scoped visibility for operators, distinct from developer ownership.
  • Decide explicitly that dynamic clients are unmanageable by design, and say so in the docs — which
    at least stops the next person rediscovering it.

Tasks

  • Decide whether dynamically registered clients should be ownable, and by what signal
  • If they should: implement attribution or a claim flow, and backfill or document the position for existing rows
  • If they should not: document it where a developer will meet it, and give the portal an explanation rather than an empty list
  • Either way, make sure the developer-facing audit feed's empty state distinguishes "no activity" from "your clients cannot be attributed"

Acceptance Criteria

  • A developer using the primary registration path can either manage their clients, or is told plainly why not
  • GET /api/clients and any future developer-scoped query have a documented, deliberate answer for developer_id IS NULL
  • The position is recorded somewhere a future contributor will find before reimplementing an ownership predicate

References

  • apps/auth-server/src/app/routes/oauth/register.ts:129, apps/auth-server/src/app/helpers/cimd.ts:274 — the two null-owner creation sites
  • apps/auth-server/src/app/routes/clients/index.ts:155,333 — the ownership predicate and the list query
  • README.md — CIMD described as the primary client-registration path

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

  • Status
    Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions