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 framesPOST /api/v1/dev/metadata-events — manual 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:
- 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. - 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". - 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
Measured while authoring the
metadataroute ledger for #11882. Filed unassigned — anobservation 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 throughgetRawApp()(packages/metadata/src/plugin.ts:468), and hands it toregisterMetadataHmrRoutes(), 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 framesPOST /api/v1/dev/metadata-events— manual reload trigger; the handler re-loads theartifact 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:
routes/hmr-routes.tscontains no occurrence ofauth,session,principal,401,resolveAuthzortoken. Nothing on the mount path adds one — thehandle comes straight from
getRawApp(), so the route does not sit behind REST'senforceAuthseam.MetadataPluginapplies noNODE_ENV/isDevcondition to this mount. The registration comment says the endpoint is registered
"regardless of the
watchoption".isDevguard in the tree is somewhere else.packages/cli/src/commands/serve.tsgates its own supplementary composition of a lightweight MetadataPlugin with
if (isDev && flags.server). That guard protects the CLI's extra instance — it does notreach a
MetadataPlugincomposed 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:
"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-adminreachable unauthenticated, mounts outsidethe
enforceAuthseam) was closed for. This is the same structural position: agetRawApp()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
MetadataPluginwith a raw-app-capable server — that depends on boot shape (host config vsstandalone 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:
about callers, and a pin should keep it that way.
Where the disposition is now recorded
#11882's
packages/metadata/src/metadata-route-ledger.tsledgers both routes and itsPOSTrowcarries 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