mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-15 13:58:20 +00:00
* fix(core): telemetry redaction covers folders with spaces in any script * style(core): shorten the redaction folder note * fix(core): spaced folders without a root redact only before a file name * style(core): drop the redaction note that described the leak as intended * style(core): two fewer comment lines in the redactor * fix(core): bound the spaced-folder match so long lines stay linear
201 lines
8.0 KiB
TypeScript
201 lines
8.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { redactKnownPaths, redactTelemetryString } from "./telemetryRedaction.js";
|
|
|
|
describe("redactTelemetryString", () => {
|
|
it("redacts macOS, Linux, Windows, file URLs, and URL query strings", () => {
|
|
expect(
|
|
redactTelemetryString(
|
|
[
|
|
"/Users/alice/project/video.mp4",
|
|
"/home/ubuntu/project/video.mp4",
|
|
"/workspace/app/video.mp4",
|
|
"C:\\Users\\Alice\\project\\video.mp4",
|
|
"file:///tmp/render/video.mp4",
|
|
"https://example.com/video.mp4?token=secret",
|
|
].join(" "),
|
|
),
|
|
).toBe("[path] [path] [path] [path] [file-url] https://example.com/video.mp4?…");
|
|
});
|
|
|
|
it("redacts a signing parameter cut off from its URL", () => {
|
|
expect(
|
|
redactTelemetryString(
|
|
"z-Signature=supersecret&X-Amz-Credential=AKID x-goog-signature=abc -t 1",
|
|
),
|
|
).toBe("z-Signature=…&X-Amz-Credential=… x-goog-signature=… -t 1");
|
|
});
|
|
|
|
// The redactor used to enumerate roots (/Users, /home, /opt, /tmp, …). Any
|
|
// root outside that list reached telemetry verbatim, which is most of them.
|
|
it.each([
|
|
"/data/media/interview.mov",
|
|
"/mnt2/nfs/share/take3.wav",
|
|
"/srv2/renders/2026/final.mp4",
|
|
"/nix/store/abc123/asset.png",
|
|
])("redacts the non-allowlisted absolute root in %s", (path) => {
|
|
const out = redactTelemetryString(`ffprobe failed reading ${path}`);
|
|
expect(out).not.toContain("/");
|
|
expect(out).toContain("[path]");
|
|
});
|
|
|
|
it("redacts relative paths, including a dash-prefixed one", () => {
|
|
expect(redactTelemetryString("could not open ./assets/-weird-name.mp3")).toBe(
|
|
"could not open [path]",
|
|
);
|
|
expect(redactTelemetryString("could not open ../-out.wav")).toBe("could not open [path]");
|
|
expect(redactTelemetryString("could not open .\\tmp\\-x.aac")).toBe("could not open [path]");
|
|
});
|
|
|
|
it("redacts a bare basename — a caller may pass one instead of a path", () => {
|
|
expect(redactTelemetryString("Invalid data found in my-client-cut.mp4")).toBe(
|
|
"Invalid data found in [file]",
|
|
);
|
|
});
|
|
|
|
// Over-redaction is cheap; these are ordinary in ffprobe stderr and turning
|
|
// them into [path] would make a diagnostic string useless.
|
|
it.each(["N/A", "24/1", "Stream #0:0", "moov atom not found", "48000/1001"])(
|
|
"leaves %s alone",
|
|
(text) => {
|
|
expect(redactTelemetryString(`ffprobe: ${text}`)).toBe(`ffprobe: ${text}`);
|
|
},
|
|
);
|
|
|
|
// A `?` is illegal in a Windows filename, so this is not a query string —
|
|
// the whole token is path, and must not survive by hiding behind a `?`.
|
|
it("consumes the rest of the token once a path is established", () => {
|
|
expect(redactTelemetryString("Navigation failed for C:\\Users\\A\\v.mov?not-a-query")).toBe(
|
|
"Navigation failed for [path]",
|
|
);
|
|
});
|
|
|
|
it("truncates after redacting, so a long path cannot survive by being cut", () => {
|
|
const out = redactTelemetryString(`/data/${"x".repeat(500)}/a.mp4`, 40);
|
|
expect(out).not.toContain("xxx");
|
|
});
|
|
|
|
// Named explicitly in review: a relative path with NO `./` prefix was
|
|
// missed by both the absolute rule (needs a leading slash) and the `./`
|
|
// rule (needs the dot), so it reached telemetry completely unredacted.
|
|
it.each([
|
|
"customer/acme-secret/video.mp4",
|
|
"assets/bgm.mp3",
|
|
"projects/client-name/cut/final.mov",
|
|
"a\\b\\c.wav",
|
|
])("redacts the bare relative path %s", (path) => {
|
|
const out = redactTelemetryString(`Invalid data found when processing ${path}`);
|
|
expect(out).toBe("Invalid data found when processing [path]");
|
|
});
|
|
|
|
it("redacts a dash-prefixed bare basename", () => {
|
|
expect(redactTelemetryString("could not open -customer-secret-intro.mp4")).toBe(
|
|
"could not open [file]",
|
|
);
|
|
});
|
|
});
|
|
|
|
// The segment classes were ASCII `\w`, so a non-Latin path went out verbatim.
|
|
// This redactor also feeds CLI telemetry and producer observation messages,
|
|
// where no known-path list is supplied to compensate.
|
|
describe("non-Latin paths", () => {
|
|
it.each([
|
|
"/数据/客户/秘密视频.mp4",
|
|
"/данные/клиент/видео.mp4",
|
|
"/data/客户/secret.mp4",
|
|
"/Users/alice/проект/видео.mp4",
|
|
])("redacts the absolute path %s", (path) => {
|
|
const out = redactTelemetryString(`ffprobe failed reading ${path}`);
|
|
expect(out).toBe("ffprobe failed reading [path]");
|
|
});
|
|
|
|
it("redacts a non-Latin bare relative path without stranding the first segment", () => {
|
|
// The lookbehind used to be `\w`-based, so a match could start mid-token
|
|
// when the preceding character was non-ASCII: this redacted to `客户[path]`.
|
|
expect(redactTelemetryString("could not open 客户/秘密/视频.mp4")).toBe(
|
|
"could not open [path]",
|
|
);
|
|
});
|
|
|
|
it.each(["./资产/背景.mp3", "../输出/final.wav"])("redacts the relative path %s", (path) => {
|
|
expect(redactTelemetryString(`could not open ${path}`)).toBe("could not open [path]");
|
|
});
|
|
|
|
it("redacts a non-Latin bare basename", () => {
|
|
expect(redactTelemetryString("Invalid data found in 秘密视频.mp4")).toBe(
|
|
"Invalid data found in [file]",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("paths with spaces", () => {
|
|
it.each([
|
|
"/Users/kim/내 드라이브/영상 프로젝트/clip.mp4",
|
|
"/Users/ivan/Мои документы/Проект Видео/видео.mp4",
|
|
"/Users/tanaka/マイ\u3000ドライブ/動画/clip.mp4",
|
|
"/Users/alex/Library/CloudStorage/GoogleDrive-alex@example.com/My Drive/Claude Projects/edit.mp4",
|
|
"/Users/alex/Dropbox (Personal)/Client Work/edit.mov",
|
|
"C:\\Users\\Kim\\OneDrive - Contoso\\Client Videos\\clip.mp4",
|
|
"\\\\fileserver\\Team Share\\clips\\clip.mp4",
|
|
])("redacts every folder of %s", (path) => {
|
|
expect(redactTelemetryString(`ffprobe failed reading ${path}`)).toBe(
|
|
"ffprobe failed reading [path]",
|
|
);
|
|
});
|
|
|
|
it("keeps the words after the path", () => {
|
|
expect(redactTelemetryString("could not read /Users/kim/My Drive/clip.mp4 was not found")).toBe(
|
|
"could not read [path] was not found",
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
" Duration: N/A, start: 0.000000, bitrate: N/A",
|
|
"Stream #0:0: Video: h264, 24/1 fps, 30000/1001 tbr",
|
|
"r_frame_rate=24/1 avg_frame_rate=30000/1001",
|
|
"input/output error then read/write failed",
|
|
])("never joins ordinary ffprobe words into a path: %s", (line) => {
|
|
expect(redactTelemetryString(line)).toBe(line);
|
|
});
|
|
|
|
it("redacts spaced folders of a path that lost its root", () => {
|
|
const out = redactTelemetryString("could not open My Drive/Claude Projects/edit.mp4");
|
|
expect(out).not.toMatch(/Drive|Claude|Projects|edit/);
|
|
});
|
|
});
|
|
|
|
describe("redactKnownPaths", () => {
|
|
// Shape matching has holes by construction. A caller that built the argv
|
|
// knows the exact path, so it can name it instead of hoping a regex does.
|
|
it("redacts an exact path a regex would not recognise as one", () => {
|
|
const weird = "acme_secret_project";
|
|
expect(redactKnownPaths(`ffprobe: ${weird}: Invalid data`, [weird])).toBe(
|
|
"ffprobe: [path]: Invalid data",
|
|
);
|
|
});
|
|
|
|
it("redacts the basename too — ffprobe often reports only that", () => {
|
|
const out = redactKnownPaths("moov atom not found in secret-cut.mp4", [
|
|
"/data/x/secret-cut.mp4",
|
|
]);
|
|
expect(out).toContain("[path]");
|
|
expect(out).not.toContain("secret-cut");
|
|
});
|
|
|
|
it("leaves the message alone when no path was supplied", () => {
|
|
expect(redactKnownPaths("moov atom not found", [])).toBe("moov atom not found");
|
|
});
|
|
|
|
// Guards against a one/two-character basename turning every occurrence of
|
|
// that letter into [path].
|
|
it("ignores paths too short to be distinctive", () => {
|
|
expect(redactKnownPaths("a stream at a rate", ["a"])).toBe("a stream at a rate");
|
|
});
|
|
|
|
// This sits on an error path: throwing here turns a reported failure into an
|
|
// unhandled rejection, which is what a non-Error rejection's `undefined`
|
|
// message caused.
|
|
it.each([undefined, null, 42, {}])("returns a string for the non-string input %s", (value) => {
|
|
expect(() => redactKnownPaths(value as unknown as string, ["/tmp/x.mp4"])).not.toThrow();
|
|
});
|
|
});
|