Skip to content

The metadata HMR reload door POST /api/v1/dev/metadata-events carries no auth and no environment gate of its own #12140

Description

@os-trump

Measured while authoring the metadata route ledger for #11882. Filed unassigned — an
observation from that card's disposition review, not a claim on anyone's time, and not fixed
there
(#11882 is ledgers-only, zero route-behaviour changes).

What was measured

origin/main @ 2ba4329e.

MetadataPlugin.start() resolves the HTTP server, takes the framework-native handle through
getRawApp() (packages/metadata/src/plugin.ts:468), and hands it to
registerMetadataHmrRoutes(), which mounts two routes
(packages/metadata/src/routes/hmr-routes.ts:126,185):

  • GET /api/v1/dev/metadata-events — SSE stream of metadata-change / reload frames
  • POST /api/v1/dev/metadata-eventsmanual reload trigger; the handler re-loads the
    artifact from disk (in local-file artifact mode) and then broadcasts a reload to every
    connected client

Three facts, each read off source rather than inferred:

  1. No authentication.routes/hmr-routes.ts contains no occurrence of auth, session,
    principal, 401, resolveAuthz or token. Nothing on the mount path adds one — the
    handle comes straight from getRawApp(), so the route does not sit behind REST's
    enforceAuth seam.
  2. No environment gate in the plugin.MetadataPlugin applies no NODE_ENV / isDev
    condition to this mount. The registration comment says the endpoint is registered
    "regardless of the watch option".
  3. The only isDev guard in the tree is somewhere else.packages/cli/src/commands/serve.ts
    gates its own supplementary composition of a lightweight MetadataPlugin with
    if (isDev && flags.server). That guard protects the CLI's extra instance — it does not
    reach a MetadataPlugin composed by a standalone/artifact boot or by a host config.

Why this is worth a triage look rather than a shrug

The plugin's own comment states the posture as a claim about callers, not as a gate:

Production deployments simply won't have a CLI POSTing to this endpoint and won't surface the
route to clients.

"Won't surface the route to clients" is true of the Console, but the route is still mounted and
still answers. The first half — nobody will call it — is an assumption about who is on the
network, which is the shape #9391 (datasource-admin reachable unauthenticated, mounts outside
the enforceAuth seam) was closed for. This is the same structural position: a getRawApp()
mount is invisible to the auth seam by construction.

What an unauthenticated caller gets, if the route is mounted and reachable: force an artifact
re-read from disk and push a reload frame to every connected Studio client. That is not data
exfiltration, but it is an unauthenticated write-shaped side effect plus a broadcast.

What is NOT claimed here

I did not establish that any shipped production configuration actually composes
MetadataPlugin with a raw-app-capable server — that depends on boot shape (host config vs
standalone vs artifact), and I did not enumerate the distributions. That enumeration is the
first thing this card needs
, because it decides whether this is a real exposure or a
documented dev-only surface that merely lacks a gate saying so. Both outcomes are useful:

  • if production boots can mount it → it wants an auth check or an environment gate;
  • if they provably cannot → the plugin's comment should say so as a gate, not as a prediction
    about callers, and a pin should keep it that way.

Where the disposition is now recorded

#11882's packages/metadata/src/metadata-route-ledger.ts ledgers both routes and its POST row
carries this posture verbatim, so the fact is now reviewable in-tree rather than only in this
issue. That ledger records the state; it does not change it.

Refs: #11882 (the ledger card this was measured under) · #9391 (the same structural class, a
mount outside the auth seam) · #3528 (the incident class: a served route surface nobody reviewed
the disposition of)


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions