From 784ccc25993418f04f2c3b69e6df1346bbad9e33 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Fri, 4 Sep 2026 13:59:50 +0900 Subject: [PATCH 1/4] fix(lab): reject file URI privacy bypasses --- src/lab/events/limits.ts | 2 ++ tests/lab-post-merge-hardening.test.ts | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lab/events/limits.ts b/src/lab/events/limits.ts index f6b5ab1c91..d88434dc61 100644 --- a/src/lab/events/limits.ts +++ b/src/lab/events/limits.ts @@ -33,6 +33,7 @@ const FORBIDDEN_EXACT_KEYS = new Set([ const RAW_POSIX_PATH_RE = /(?:^|[^A-Za-z0-9._~/])\/(?:(?=$|[^A-Za-z0-9._~/])|(?!\/)(?![ \t\r\n])(?:\/|[^/\0\r\n]+)+\/?(?=$|[^A-Za-z0-9._~/]))/u; +const FILE_URI_RE = /(?:^|[^A-Za-z0-9+.-])file:\/\//i; function fieldPath(base: string, key: string | number): string { return base ? `${base}.${String(key)}` : String(key); @@ -80,6 +81,7 @@ export function enforceEventStructureLimits( } if ( /^[A-Za-z]:\\/.test(value) || + FILE_URI_RE.test(value) || RAW_POSIX_PATH_RE.test(value) || value.includes("\\Users\\") ) { diff --git a/tests/lab-post-merge-hardening.test.ts b/tests/lab-post-merge-hardening.test.ts index 89b163fa62..290c5a572b 100644 --- a/tests/lab-post-merge-hardening.test.ts +++ b/tests/lab-post-merge-hardening.test.ts @@ -180,7 +180,7 @@ test("replay discards an oversized unterminated line after reporting it once", ( expect(replay.corruptions[0]?.kind).toBe("malformed_line"); }); -test("event privacy admission rejects raw POSIX path bypass forms", () => { +test("event privacy admission rejects raw filesystem path bypass forms", () => { for (const detail of [ "config=/home/alice/work/repo", "cwd=/usr/local/bin", @@ -192,6 +192,11 @@ test("event privacy admission rejects raw POSIX path bypass forms", () => { "cwd=/home/@alice", "cwd=/home/josé/work", "x-/home/alice", + "file:///etc/passwd", + "FiLe:///home/alice/secret.txt", + "file://localhost/home/alice/secret.txt", + "detail_file:///etc/passwd", + "file://server/share/secret", ]) { try { enforceEventStructureLimits({ detail }); @@ -200,6 +205,10 @@ test("event privacy admission rejects raw POSIX path bypass forms", () => { expect((err as { code?: string }).code).toBe("raw_path"); } } + + for (const detail of ["https://example.com/path", "profile:///etc/passwd"]) { + expect(() => enforceEventStructureLimits({ detail })).not.toThrow(); + } }); test("invalid JSON contract artifacts classify as artifact_mismatch", () => { From c9d40034d480504529b9f29355c4f6369ed3465d Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Fri, 4 Sep 2026 14:13:18 +0900 Subject: [PATCH 2/4] fix(lab): reject backslash file URIs --- src/lab/events/limits.ts | 2 +- tests/lab-post-merge-hardening.test.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lab/events/limits.ts b/src/lab/events/limits.ts index d88434dc61..45c217f936 100644 --- a/src/lab/events/limits.ts +++ b/src/lab/events/limits.ts @@ -33,7 +33,7 @@ const FORBIDDEN_EXACT_KEYS = new Set([ const RAW_POSIX_PATH_RE = /(?:^|[^A-Za-z0-9._~/])\/(?:(?=$|[^A-Za-z0-9._~/])|(?!\/)(?![ \t\r\n])(?:\/|[^/\0\r\n]+)+\/?(?=$|[^A-Za-z0-9._~/]))/u; -const FILE_URI_RE = /(?:^|[^A-Za-z0-9+.-])file:\/\//i; +const FILE_URI_RE = /(?:^|[^A-Za-z0-9+.-])file:[\\/]+/i; function fieldPath(base: string, key: string | number): string { return base ? `${base}.${String(key)}` : String(key); diff --git a/tests/lab-post-merge-hardening.test.ts b/tests/lab-post-merge-hardening.test.ts index 290c5a572b..ff8ddcacba 100644 --- a/tests/lab-post-merge-hardening.test.ts +++ b/tests/lab-post-merge-hardening.test.ts @@ -197,6 +197,8 @@ test("event privacy admission rejects raw filesystem path bypass forms", () => { "file://localhost/home/alice/secret.txt", "detail_file:///etc/passwd", "file://server/share/secret", + String.raw`file:\\server\share\secret`, + String.raw`file:\C:\secret\data`, ]) { try { enforceEventStructureLimits({ detail }); From 2c2f3cbe139d4ab21e2acd481ba3cf322606ccbe Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Fri, 4 Sep 2026 14:30:20 +0900 Subject: [PATCH 3/4] fix(lab): reject separatorless file URIs --- src/lab/events/limits.ts | 2 +- tests/lab-post-merge-hardening.test.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lab/events/limits.ts b/src/lab/events/limits.ts index 45c217f936..dd4b22e8cf 100644 --- a/src/lab/events/limits.ts +++ b/src/lab/events/limits.ts @@ -33,7 +33,7 @@ const FORBIDDEN_EXACT_KEYS = new Set([ const RAW_POSIX_PATH_RE = /(?:^|[^A-Za-z0-9._~/])\/(?:(?=$|[^A-Za-z0-9._~/])|(?!\/)(?![ \t\r\n])(?:\/|[^/\0\r\n]+)+\/?(?=$|[^A-Za-z0-9._~/]))/u; -const FILE_URI_RE = /(?:^|[^A-Za-z0-9+.-])file:[\\/]+/i; +const FILE_URI_RE = /(?:^|[^A-Za-z0-9+.-])file:/i; function fieldPath(base: string, key: string | number): string { return base ? `${base}.${String(key)}` : String(key); diff --git a/tests/lab-post-merge-hardening.test.ts b/tests/lab-post-merge-hardening.test.ts index ff8ddcacba..359d4125ba 100644 --- a/tests/lab-post-merge-hardening.test.ts +++ b/tests/lab-post-merge-hardening.test.ts @@ -199,6 +199,8 @@ test("event privacy admission rejects raw filesystem path bypass forms", () => { "file://server/share/secret", String.raw`file:\\server\share\secret`, String.raw`file:\C:\secret\data`, + String.raw`file:C:\private\secret`, + "file:etc/passwd", ]) { try { enforceEventStructureLimits({ detail }); From 2b0653f4a3297353ef45ebdb3404259fda838230 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Fri, 4 Sep 2026 16:29:50 +0900 Subject: [PATCH 4/4] fix(lab): normalize URL whitespace before file URI admission --- src/lab/events/limits.ts | 2 ++ tests/lab-post-merge-hardening.test.ts | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/lab/events/limits.ts b/src/lab/events/limits.ts index dd4b22e8cf..1447801a15 100644 --- a/src/lab/events/limits.ts +++ b/src/lab/events/limits.ts @@ -33,6 +33,7 @@ const FORBIDDEN_EXACT_KEYS = new Set([ const RAW_POSIX_PATH_RE = /(?:^|[^A-Za-z0-9._~/])\/(?:(?=$|[^A-Za-z0-9._~/])|(?!\/)(?![ \t\r\n])(?:\/|[^/\0\r\n]+)+\/?(?=$|[^A-Za-z0-9._~/]))/u; +const ASCII_URL_WHITESPACE_RE = /[\t\r\n]/g; const FILE_URI_RE = /(?:^|[^A-Za-z0-9+.-])file:/i; function fieldPath(base: string, key: string | number): string { @@ -82,6 +83,7 @@ export function enforceEventStructureLimits( if ( /^[A-Za-z]:\\/.test(value) || FILE_URI_RE.test(value) || + FILE_URI_RE.test(value.replace(ASCII_URL_WHITESPACE_RE, "")) || RAW_POSIX_PATH_RE.test(value) || value.includes("\\Users\\") ) { diff --git a/tests/lab-post-merge-hardening.test.ts b/tests/lab-post-merge-hardening.test.ts index 359d4125ba..695b5526e1 100644 --- a/tests/lab-post-merge-hardening.test.ts +++ b/tests/lab-post-merge-hardening.test.ts @@ -201,6 +201,13 @@ test("event privacy admission rejects raw filesystem path bypass forms", () => { String.raw`file:\C:\secret\data`, String.raw`file:C:\private\secret`, "file:etc/passwd", + "fi\nle:///etc/passwd", + "fil\te:///etc/passwd", + "file\r:///etc/passwd", + "file\n:///etc/passwd", + "detail\nfile:///etc/passwd", + "detail\rfile:etc/passwd", + "detail\tfile:C:\\private\\secret", ]) { try { enforceEventStructureLimits({ detail });