mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 23:03:09 +00:00
fix(cli,studio,core): close five R5 telemetry and canary findings
- A long-lived preview cached its telemetry posture in two places (readConfig and shouldTrack). Running `telemetry disable` in another terminal left it resolving canaries and injecting the CLI id for hours. Both caches are now dropped together at a request boundary. - Studio minted and shipped a telemetry id for every render regardless of the browser profile's opt-out, and the server emitted the outcome under CLI policy, which cannot see localStorage or DNT. The browser now sends an explicit telemetryOptOut, distinct from an old client's omission. - Any non-empty HYPERFRAMES_PREVIEW_HOST disabled the DNS-rebinding guard, so even a loopback bind accepted a hostile Host. The guard now holds for loopback binds and, on a LAN bind, admits only names this machine answers on. - sunsetAfter had no reader of the current date. A scheduled workflow runs scripts/check-canary-sunset.ts weekly, so a failure lands on the rollout's owner rather than on an unrelated PR author. - The install-state seed memo outlived `rm -rf ~/.hyperframes`, resurrecting a cleared cohort. Removed; it only saved a read on a readConfig cache miss. Docs updated for the Host rule and the 100% exclusion carve-out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3f69a2c635
commit
6f0df2640b
@@ -602,6 +602,55 @@ describe("POST /projects/:id/render — telemetryDistinctId forwarding", () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Explicit suppression, forwarded so the CLI can honour a browser opt-out
|
||||
// it has no other way to observe.
|
||||
it("forwards an explicit telemetryOptOut", async () => {
|
||||
const spy = vi.fn();
|
||||
const { app, cleanup } = buildApp(spy);
|
||||
try {
|
||||
const res = await app.request("http://localhost/projects/demo/render", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
fps: 30,
|
||||
quality: "standard",
|
||||
format: "mp4",
|
||||
telemetryOptOut: true,
|
||||
}),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
expect(spy.mock.calls[0][0].telemetryOptOut).toBe(true);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
// An old client omits the flag, and a non-boolean is not a signal either.
|
||||
// Defaulting those to "opted out" would silently drop every pre-upgrade
|
||||
// render outcome.
|
||||
it.each([undefined, false, "true"])(
|
||||
"treats telemetryOptOut %s as not opted out",
|
||||
async (flag) => {
|
||||
const spy = vi.fn();
|
||||
const { app, cleanup } = buildApp(spy);
|
||||
try {
|
||||
await app.request("http://localhost/projects/demo/render", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
fps: 30,
|
||||
quality: "standard",
|
||||
format: "mp4",
|
||||
telemetryOptOut: flag,
|
||||
}),
|
||||
});
|
||||
expect(spy.mock.calls[0][0].telemetryOptOut).toBe(false);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("ignores a non-string telemetryDistinctId", async () => {
|
||||
const spy = vi.fn();
|
||||
const { app, cleanup } = buildApp(spy);
|
||||
|
||||
@@ -66,6 +66,10 @@ export function registerRenderRoutes(api: Hono, adapter: StudioApiAdapter): void
|
||||
// Browser telemetry id, so the server-emitted render outcome is
|
||||
// attributed to the user who triggered the render (joinable funnel).
|
||||
telemetryDistinctId?: string;
|
||||
// Explicit "this browser profile opted out" flag. Distinct from simply
|
||||
// omitting the id: an OLD client omits it too, and that case falls back
|
||||
// to the install anonymousId. Only an explicit `true` suppresses.
|
||||
telemetryOptOut?: boolean;
|
||||
// Composition-variable overrides ({variableId: value}), injected as
|
||||
// window.__hfVariables — same channel as `hyperframes render --variables`.
|
||||
variables?: Record<string, unknown>;
|
||||
@@ -126,6 +130,7 @@ export function registerRenderRoutes(api: Hono, adapter: StudioApiAdapter): void
|
||||
variables,
|
||||
distinctId:
|
||||
typeof body.telemetryDistinctId === "string" ? body.telemetryDistinctId : undefined,
|
||||
telemetryOptOut: body.telemetryOptOut === true,
|
||||
});
|
||||
(jobState as RenderJobState & { createdAt: number }).createdAt = Date.now();
|
||||
renderJobs.set(jobId, jobState as RenderJobState & { createdAt: number });
|
||||
|
||||
@@ -144,6 +144,13 @@ export interface StudioApiAdapter {
|
||||
fps: import("@hyperframes/core").Fps;
|
||||
quality: string;
|
||||
jobId: string;
|
||||
/**
|
||||
* The triggering browser profile has telemetry disabled (localStorage
|
||||
* opt-out, DNT, dev build...). The CLI cannot observe any of that, so the
|
||||
* browser has to say so — without it the server emitted render outcomes
|
||||
* for a user who had opted out, under the CLI's own policy.
|
||||
*/
|
||||
telemetryOptOut?: boolean;
|
||||
/**
|
||||
* Optional output resolution preset. See `resolveDeviceScaleFactor` in
|
||||
* the producer for the integer-scale + aspect + HDR constraints.
|
||||
|
||||
Reference in New Issue
Block a user