Found during the #3358 v16.0 browser/API verification sweep.
Summary
The per-request, admin-gated Server-Timing path (#2408) does not emit any header on the standard hono server (os serve / os dev / os start). An admin sending X-OS-Debug-Timing: 1 (or json) gets no Server-Timing and no X-OS-Debug-Timing-Detail header on any route.
Repro
Boot showcase with objectstack dev --ui --seed-admin, sign in as the seeded admin, then:
curl -s -b <admin-cookies> -D - -o /dev/null \
-H 'X-OS-Debug-Timing: json' \
'http://localhost:<port>/api/v1/data/sys_user?$top=3'
Response headers contain no Server-Timing and no X-OS-Debug-Timing-Detail. Same for /api/v1/meta/object, /api/v1/meta/app?id=.... The admin session is genuine (/api/v1/auth/me/permissions → admin_full_access, 200 on data reads).
Expected (per the v16 release notes / checklist §9): "with OS_SERVER_TIMING / X-OS-Debug-Timing, an admin sees auth/db/hooks/serialize spans; a non-admin does not."
Root cause
- The only production caller of
allowPerfDisclosure() is packages/runtime/src/http-dispatcher.ts:263 (timedResolveExecutionContext, which calls it when isPerfDisclosurePrincipal(ec) is true).
os serve/dev mounts HonoServerPlugin (packages/cli/src/commands/serve.ts:1029).
- The hono plugin resolves the principal via its own self-contained
resolveCtx (packages/plugins/plugin-hono-server/src/hono-plugin.ts:756, "Mirrors the runtime's resolveExecutionContext but self-contained to avoid a cross-package dep") and never calls allowPerfDisclosure().
- The perf middleware starts the disclosure gate closed for per-request mode (
gate.allowed = globalTiming = false; hono-plugin.ts:360) and withholds the header unless the gate is opened during dispatch (hono-plugin.ts:364). Since nothing opens it, the header is always withheld.
Net: only global mode (OS_SERVER_TIMING=true) emits — and that opens the gate for every caller up front, i.e. it is not admin-gated. The documented admin-gated per-request path is unavailable on the shipped OSS server.
Why CI didn't catch it
packages/plugins/plugin-hono-server/src/server-timing.test.ts calls allowPerfDisclosure() manually "as the dispatcher would" (lines 41, 54). Header-emission and gate-opening are unit-tested in isolation; there is no end-to-end test that drives the real hono request pipeline as an admin, so the missing wiring is invisible.
Suggested fix
Have the hono server open the disclosure gate when the resolved principal is privileged — e.g. after resolveCtx, call allowPerfDisclosure() when isPerfDisclosurePrincipal(ec) (export/reuse that predicate + ExecutionContext.posture/principalKind from @objectstack/runtime). Add an e2e test that boots the hono app and asserts an admin gets Server-Timing while a non-admin does not.
Severity
P2 — an opt-in ops-diagnostics feature is non-functional as documented on the standard server. Not a data/security issue (the global path over-discloses but is off by default).
Responsible: #2408. Refs release notes content/docs/releases/v16.mdx (Performance diagnostics: Server-Timing spans).
Found during the #3358 v16.0 browser/API verification sweep.
Summary
The per-request, admin-gated
Server-Timingpath (#2408) does not emit any header on the standard hono server (os serve/os dev/os start). An admin sendingX-OS-Debug-Timing: 1(orjson) gets noServer-Timingand noX-OS-Debug-Timing-Detailheader on any route.Repro
Boot showcase with
objectstack dev --ui --seed-admin, sign in as the seeded admin, then:Response headers contain no
Server-Timingand noX-OS-Debug-Timing-Detail. Same for/api/v1/meta/object,/api/v1/meta/app?id=.... The admin session is genuine (/api/v1/auth/me/permissions→admin_full_access, 200 on data reads).Expected (per the v16 release notes / checklist §9): "with
OS_SERVER_TIMING/X-OS-Debug-Timing, an admin sees auth/db/hooks/serialize spans; a non-admin does not."Root cause
allowPerfDisclosure()ispackages/runtime/src/http-dispatcher.ts:263(timedResolveExecutionContext, which calls it whenisPerfDisclosurePrincipal(ec)is true).os serve/devmountsHonoServerPlugin(packages/cli/src/commands/serve.ts:1029).resolveCtx(packages/plugins/plugin-hono-server/src/hono-plugin.ts:756, "Mirrors the runtime's resolveExecutionContext but self-contained to avoid a cross-package dep") and never callsallowPerfDisclosure().gate.allowed = globalTiming= false;hono-plugin.ts:360) and withholds the header unless the gate is opened during dispatch (hono-plugin.ts:364). Since nothing opens it, the header is always withheld.Net: only global mode (
OS_SERVER_TIMING=true) emits — and that opens the gate for every caller up front, i.e. it is not admin-gated. The documented admin-gated per-request path is unavailable on the shipped OSS server.Why CI didn't catch it
packages/plugins/plugin-hono-server/src/server-timing.test.tscallsallowPerfDisclosure()manually "as the dispatcher would" (lines 41, 54). Header-emission and gate-opening are unit-tested in isolation; there is no end-to-end test that drives the real hono request pipeline as an admin, so the missing wiring is invisible.Suggested fix
Have the hono server open the disclosure gate when the resolved principal is privileged — e.g. after
resolveCtx, callallowPerfDisclosure()whenisPerfDisclosurePrincipal(ec)(export/reuse that predicate +ExecutionContext.posture/principalKindfrom@objectstack/runtime). Add an e2e test that boots the hono app and asserts an admin getsServer-Timingwhile a non-admin does not.Severity
P2 — an opt-in ops-diagnostics feature is non-functional as documented on the standard server. Not a data/security issue (the global path over-discloses but is off by default).
Responsible: #2408. Refs release notes
content/docs/releases/v16.mdx(Performance diagnostics: Server-Timing spans).