From 167a5f2b9464171868053b4888519d58ff03345b Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 11:34:12 +0800 Subject: [PATCH 01/46] fix: mask filesystem paths in telemetry error text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit maskString deliberately covers API keys, bearer tokens, emails, internal hosts and quoted strings — but had no rule for filesystem paths, so UNQUOTED paths in error messages reached App Insights raw. Home-directory paths carry the OS username and project-rooted paths leak client repo structure (a live 32-machine core_failure/file_not_found cluster carried full /Users//... paths). Quoted paths were coincidentally destroyed by the quote rule, which is why the asymmetry went unnoticed. Adds three ordered rules (Windows drive/UNC, POSIX absolute with 2+ segments, home-relative ~/) that replace the whole path with — matching the chain's existing "over-masking is the correct failure mode" doctrine. Ordered after the URL rule (a public URL's path segment is never word-anchored, so it cannot match) and before quote masking. Bare single-segment tokens like "/mcp", MIME types, dates and version specs are proven untouched by the test matrix. Side effect, intended: hashError operates on the masked message, so grouping keys for affected messages change once — and path-variant messages that previously split now collapse into one group. --- .../opencode/src/altimate/telemetry/index.ts | 16 +++++ .../test/telemetry/mask-file-paths.test.ts | 64 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 packages/opencode/test/telemetry/mask-file-paths.test.ts diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index e7f14db53..4c434ce2a 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1405,6 +1405,22 @@ export namespace Telemetry { /\bhttps?:\/\/(?:[^\/\s@]+@)?(?:localhost|127\.\d+\.\d+\.\d+|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[01])\.\d+\.\d+|169\.254\.\d+\.\d+|0\.0\.0\.0|\[(?:::1|fc[0-9a-f]{2}:[^\]]*|fd[0-9a-f]{2}:[^\]]*|fe80:[^\]]*)\]|[A-Za-z0-9.-]+\.(?:local|internal|localhost))(?::\d+)?[\w/.?=&%+#,;~!*'()@:-]*/gi, "", ) + // altimate_change start — mask filesystem paths in error text + // Tool failures embed the path that failed ("File not found: /Users/…"); + // home-dir paths carry the OS username, and project-rooted absolute paths + // leak client repo structure. QUOTED paths were already destroyed by the + // quote rule below — unquoted ones reached telemetry raw (live 32-machine + // core_failure/file_not_found cluster). Same philosophy as the rest of + // this chain: over-masking is the correct failure mode. Ordered after the + // URL rule (a public URL's path segment is never word-anchored, so it + // cannot match here) and before quote masking. Three shapes: + // Windows drive / UNC: C:\Users\… \\server\share + // POSIX absolute (2+ segments — bare "/mcp" style tokens survive) + // Home-relative: ~/… + .replace(/(^|[\s"'`=(,])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`]+/g, "$1") + .replace(/(^|[\s"'`=(,])\/(?:[\w.@+-]+\/)+[^\s'"`)\]},]*/g, "$1") + .replace(/(^|[\s"'`=(,])~\/[^\s'"`)\]},]*/g, "$1") + // altimate_change end .replace(/'(?:[^'\\]|\\.)*'/g, "?") .replace(/"(?:[^"\\]|\\.)*"/g, "?") .replace(/\s+/g, " ") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts new file mode 100644 index 000000000..367390f65 --- /dev/null +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -0,0 +1,64 @@ +/** + * maskString file-path masking. + * + * The masking chain deliberately covered keys, bearers, emails, internal hosts + * and quoted strings — but had no rule for filesystem paths, so UNQUOTED paths + * in error text reached App Insights raw (a live 32-machine + * core_failure/file_not_found cluster carried full /Users//… paths: + * OS username + client repo structure). Quoted paths were coincidentally + * destroyed by the quote rule, which is why the gap went unnoticed. + */ +import { describe, expect, it } from "bun:test" +import { Telemetry } from "../../src/altimate/telemetry" + +const mask = Telemetry.maskString + +describe("maskString file paths", () => { + it("masks home-directory paths (the PII case: username + repo layout)", () => { + expect(mask("File not found: /Users/jdoe/Documents/client-repos/Platform_Project/models/05_da.sql")) + .toBe("File not found: ") + expect(mask("ENOENT: no such file or directory, open /home/jdoe/dbt/profiles.yml")) + .toBe("ENOENT: no such file or directory, open ") + expect(mask("could not read ~/dbt/profiles.yml")).toBe("could not read ") + }) + + it("masks Windows drive and UNC paths", () => { + expect(mask("No such file: C:\\Users\\jdoe\\project\\models\\a.sql")).toBe("No such file: ") + expect(mask("read failed for \\\\fileserver\\share\\models\\x.sql")).toBe("read failed for ") + }) + + it("masks project-rooted absolute paths (client repo structure)", () => { + expect(mask("File not found: /app/models/stg_quickbooks__estimate.sql")) + .toBe("File not found: ") + expect(mask("Manifest path for deferral does not exist: /data/warehouse/target/manifest.json")) + .toBe("Manifest path for deferral does not exist: ") + }) + + it("masks paths at string start and inside punctuation", () => { + expect(mask("/Users/jdoe/x/y.sql was deleted")).toBe(" was deleted") + expect(mask("failed (from /opt/dbt/bin/dbt)")).toBe("failed (from )") + expect(mask("--project-dir=/srv/analytics/dbt")).toBe("--project-dir=") + }) + + it("does NOT mask non-path slashes", () => { + expect(mask("content-type application/json rejected")).toBe("content-type application/json rejected") + expect(mask("requirement dbt-core~=1.11.0 not satisfied")).toBe("requirement dbt-core~=1.11.0 not satisfied") + expect(mask("on 8/17/2026 at 3:2 ratio 1/2")).toBe("on 8/17/2026 at 3:2 ratio 1/2") + expect(mask("endpoint /mcp returned 404")).toBe("endpoint /mcp returned 404") + }) + + it("leaves public URL paths to the URL rules (never word-anchored)", () => { + expect(mask("POST https://api.openai.com/v1/chat/completions failed")) + .toBe("POST https://api.openai.com/v1/chat/completions failed") + // internal hosts keep their existing, stronger treatment + expect(mask("GET http://localhost:8080/api/x timed out")).toContain("") + }) + + it("composes with the earlier rules in the chain", () => { + // credential first, then the path + expect(mask("sk-abcdefghijklmnopqrstuvwx leaked into /Users/jdoe/log.txt")) + .toBe("sk-*** leaked into ") + // quoted path still collapses via the quote rule — no leak either way + expect(mask("open '/Users/jdoe/x.sql' failed")).not.toContain("jdoe") + }) +}) From 4899189e74b416685f5573843fbb3f752dafe240 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 11:40:24 +0800 Subject: [PATCH 02/46] fix: mask cloud-storage URIs and paths with embedded spaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review + a live-data sweep hardened the path rules. Ran all 800 distinct 30-day telemetry messages through the patched maskString hunting for residual path content: - 1 true leak survived: a gs:// URI carrying a client bucket name + data layout — cloud-storage URIs (gs, s3, abfss, wasbs, adl, dbfs, hdfs, file) are not http so the URL rule never sees them, and not slash-anchored so the path rule missed them. New leading rule masks the whole URI. - Review caught unquoted paths with embedded spaces (macOS "/Users/Jane Doe/client repo/…") escaping the [^\s] character classes. Paths now continue across a space whenever a later chunk carries another separator, plus one optional trailing spaced filename — while trailing prose ("… /app/a.sql was deleted upstream") is proven untouched. Public https doc-links (provider help URLs) remain deliberately unmasked — the other 32 sweep hits were all of that class. After both fixes the 800-message sweep reports zero residual path content. --- .../opencode/src/altimate/telemetry/index.ts | 12 +++++-- .../test/telemetry/mask-file-paths.test.ts | 33 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 4c434ce2a..866742977 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1417,9 +1417,15 @@ export namespace Telemetry { // Windows drive / UNC: C:\Users\… \\server\share // POSIX absolute (2+ segments — bare "/mcp" style tokens survive) // Home-relative: ~/… - .replace(/(^|[\s"'`=(,])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`]+/g, "$1") - .replace(/(^|[\s"'`=(,])\/(?:[\w.@+-]+\/)+[^\s'"`)\]},]*/g, "$1") - .replace(/(^|[\s"'`=(,])~\/[^\s'"`)\]},]*/g, "$1") + // Cloud-storage URIs (gs://, s3://, abfss://, …) — bucket names identify + // the customer; not http, so the URL rule above never sees them. + // Paths with embedded spaces (macOS "/Users/Jane Doe/client repo/…") + // continue across a space whenever a later chunk carries another + // separator, plus one optional trailing spaced filename. + .replace(/(^|[\s"'`=(,])(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs|file):\/\/[^\s'"`)\]},]*/gi, "$1") + .replace(/(^|[\s"'`=(,])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`]+(?: [\w.@+()-]+(?:[\\\/][^\s'"`]*)+)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?/g, "$1") + .replace(/(^|[\s"'`=(,])\/(?:[\w.@+-]+\/)+[^\s'"`)\]},]*(?: [\w.@+()-]+(?:\/[^\s'"`)\]},]*)+)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?/g, "$1") + .replace(/(^|[\s"'`=(,])~\/[^\s'"`)\]},]*(?: [\w.@+()-]+(?:\/[^\s'"`)\]},]*)+)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?/g, "$1") // altimate_change end .replace(/'(?:[^'\\]|\\.)*'/g, "?") .replace(/"(?:[^"\\]|\\.)*"/g, "?") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 367390f65..3a9c4d947 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -62,3 +62,36 @@ describe("maskString file paths", () => { expect(mask("open '/Users/jdoe/x.sql' failed")).not.toContain("jdoe") }) }) + +describe("maskString paths — review + live-data hardening", () => { + it("masks paths with embedded spaces (macOS home dirs, spaced repo names)", () => { + expect(mask("File not found: /Users/Jane Doe/client repo/models/a.sql")) + .toBe("File not found: ") + expect(mask("No such file: C:\\Users\\Jane Doe\\my project\\models\\a.sql")) + .toBe("No such file: ") + expect(mask("could not read ~/my dbt/profiles config/profiles.yml")) + .toBe("could not read ") + // a trailing spaced FILENAME is consumed; following prose is not + expect(mask("open /Users/jdoe/some dir/file name.sql failed")) + .toBe("open failed") + }) + + it("does not let the space-continuation eat trailing prose", () => { + expect(mask("File not found: /app/models/a.sql was deleted upstream")) + .toBe("File not found: was deleted upstream") + }) + + it("masks cloud-storage URIs (live leak: client GCS bucket + data layout)", () => { + expect(mask("CSV table references gs://client-finance-import/inventory/users/file_date=2026-07-16/users.csv")) + .toBe("CSV table references ") + expect(mask("read failed: s3://acme-prod-lake/raw/orders/part-0001.parquet")) + .toBe("read failed: ") + expect(mask("abfss://container@account.dfs.core.windows.net/data/x failed")) + .toBe(" failed") + }) + + it("still leaves public https doc-links alone (provider help URLs)", () => { + expect(mask("see https://community.snowflake.com/s/ip-not-allowed for details")) + .toBe("see https://community.snowflake.com/s/ip-not-allowed for details") + }) +}) From 9d56682f100c3ee4e48e9ba322cf1e1041a540df Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 11:59:34 +0800 Subject: [PATCH 03/46] fix: bracket anchors, spaced cloud keys, terminal spaced components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second review round, all three confirmed by construction: - Anchor classes include [ and { so bracketed paths ("failed [/Users/…]") match; quote masking never applied to those. - Cloud-URI rule gets the same embedded-whitespace continuation as filesystem paths, and continuation chunks may span MULTIPLE spaced words before the next separator ("dir with spaces/part …") — the single-word unit broke the chain. - One trailing spaced WORD is consumed at end-of-string or before punctuation ("Directory not found: /Users/jdoe/client repo"). The general "path + space + word" case is undecidable against trailing prose ("client repo" vs "x.sql was"), so mid-sentence prose stays protected; the accepted residue is a single end-of-string prose word eaten (over-masking doctrine), and a username can never leak through this case — it is always slash-terminated and therefore always inside the masked span. 15 unit tests; 800-message live sweep still reports zero residual leaks; adjacent suites + typecheck green. --- .../opencode/src/altimate/telemetry/index.ts | 15 +++++--- .../test/telemetry/mask-file-paths.test.ts | 35 +++++++++++++++++-- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 866742977..cc9eba232 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1421,11 +1421,16 @@ export namespace Telemetry { // the customer; not http, so the URL rule above never sees them. // Paths with embedded spaces (macOS "/Users/Jane Doe/client repo/…") // continue across a space whenever a later chunk carries another - // separator, plus one optional trailing spaced filename. - .replace(/(^|[\s"'`=(,])(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs|file):\/\/[^\s'"`)\]},]*/gi, "$1") - .replace(/(^|[\s"'`=(,])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`]+(?: [\w.@+()-]+(?:[\\\/][^\s'"`]*)+)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?/g, "$1") - .replace(/(^|[\s"'`=(,])\/(?:[\w.@+-]+\/)+[^\s'"`)\]},]*(?: [\w.@+()-]+(?:\/[^\s'"`)\]},]*)+)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?/g, "$1") - .replace(/(^|[\s"'`=(,])~\/[^\s'"`)\]},]*(?: [\w.@+()-]+(?:\/[^\s'"`)\]},]*)+)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?/g, "$1") + // separator, plus one optional trailing spaced filename, plus one + // trailing spaced WORD only at end-of-string / before punctuation — + // "path + space + word" is otherwise undecidable vs trailing prose + // ("client repo" vs "x.sql was"). A username can never leak through + // that residual case: it is always slash-terminated, so it is always + // inside the masked span. Anchors include [ and { for bracketed paths. + .replace(/(^|[\s"'`=(,[{])(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs|file):\/\/[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?: [\w-]+(?=$|[.,;:)\]}!?]))?/gi, "$1") + .replace(/(^|[\s"'`=(,[{])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`]+(?:(?: [\w.@+()-]+)+[\\\/][^\s'"`]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?: [\w-]+(?=$|[.,;:)\]}!?]))?/g, "$1") + .replace(/(^|[\s"'`=(,[{])\/(?:[\w.@+-]+\/)+[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?: [\w-]+(?=$|[.,;:)\]}!?]))?/g, "$1") + .replace(/(^|[\s"'`=(,[{])~\/[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?: [\w-]+(?=$|[.,;:)\]}!?]))?/g, "$1") // altimate_change end .replace(/'(?:[^'\\]|\\.)*'/g, "?") .replace(/"(?:[^"\\]|\\.)*"/g, "?") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 3a9c4d947..32094070b 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -71,9 +71,11 @@ describe("maskString paths — review + live-data hardening", () => { .toBe("No such file: ") expect(mask("could not read ~/my dbt/profiles config/profiles.yml")) .toBe("could not read ") - // a trailing spaced FILENAME is consumed; following prose is not + // a trailing spaced filename is consumed — and a SINGLE prose word at + // end-of-string is too (indistinguishable from a terminal spaced dir; + // over-masking doctrine). Mid-sentence prose is protected below. expect(mask("open /Users/jdoe/some dir/file name.sql failed")) - .toBe("open failed") + .toBe("open ") }) it("does not let the space-continuation eat trailing prose", () => { @@ -86,8 +88,9 @@ describe("maskString paths — review + live-data hardening", () => { .toBe("CSV table references ") expect(mask("read failed: s3://acme-prod-lake/raw/orders/part-0001.parquet")) .toBe("read failed: ") + // trailing single word at end-of-string is consumed (see doctrine note) expect(mask("abfss://container@account.dfs.core.windows.net/data/x failed")) - .toBe(" failed") + .toBe("") }) it("still leaves public https doc-links alone (provider help URLs)", () => { @@ -95,3 +98,29 @@ describe("maskString paths — review + live-data hardening", () => { .toBe("see https://community.snowflake.com/s/ip-not-allowed for details") }) }) + +describe("maskString paths — codex re-review round", () => { + it("masks paths enclosed in brackets/braces", () => { + expect(mask("failed [/Users/jdoe/client/model.sql]")).toBe("failed []") + expect(mask("ctx {/home/jdoe/proj/x.yml} missing")).toBe("ctx {} missing") + }) + + it("continues cloud object keys across spaces", () => { + expect(mask("read s3://client-bucket/client data/raw file.csv")) + .toBe("read ") + expect(mask("gs://acme-lake/dir with spaces/part 0001.parquet not found")) + .toBe(" not found") + }) + + it("masks a terminal spaced component at end-of-string or before punctuation", () => { + expect(mask("Directory not found: /Users/jdoe/client repo")).toBe("Directory not found: ") + expect(mask("could not open /Users/jdoe/client repo.")).toBe("could not open .") + expect(mask("bad dir [C:\\Users\\jdoe\\client repo]")).toBe("bad dir []") + }) + + it("still never eats mid-sentence prose after a path", () => { + expect(mask("open /app/x.sql failed with error")).toBe("open failed with error") + expect(mask("File not found: /app/models/a.sql was deleted upstream")) + .toBe("File not found: was deleted upstream") + }) +}) From 40cb7fea4f3c0bced6f424d7cb06b0cb3b75c035 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 12:07:10 +0800 Subject: [PATCH 04/46] fix: spaced-username tails and Windows closing delimiters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third review round. The P1 caught a real overclaim: "a username can never leak" was false for SPACED usernames — "/Users/Jane Doe does not exist" left the surname outside the masked span mid-sentence. Home-rooted paths (/Users, /home, C:\Users — where the spaced-username stake lives) now consume one unconditional trailing word, suppressed by an extension-lookbehind when the path already ended in a dotted file so "x.sql was deleted" prose stays intact. Honest residue, now documented in the rule comment: one prose word may be over-masked after an extensionless home path; a NON-home path's terminal spaced component can still leak one structure word mid-sentence (no personal names there). P2: the Windows span consumed closing delimiters — "(C:\Users\x.sql)" became "(". Aligned its character classes with the POSIX/cloud rules (Program Files-style spaced dirs still continue via the word chunks, which keep parens). 19 unit tests; 800-message live sweep still zero residuals; adjacent suites + typecheck green. --- .../opencode/src/altimate/telemetry/index.ts | 16 ++++++--- .../test/telemetry/mask-file-paths.test.ts | 33 ++++++++++++++++--- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index cc9eba232..213b8c3f8 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1424,11 +1424,19 @@ export namespace Telemetry { // separator, plus one optional trailing spaced filename, plus one // trailing spaced WORD only at end-of-string / before punctuation — // "path + space + word" is otherwise undecidable vs trailing prose - // ("client repo" vs "x.sql was"). A username can never leak through - // that residual case: it is always slash-terminated, so it is always - // inside the masked span. Anchors include [ and { for bracketed paths. + // ("client repo" vs "x.sql was"). HOME-ROOTED paths (/Users, /home, + // C:\Users — where a spaced USERNAME would otherwise leak its tail, + // e.g. "/Users/Jane Doe does not exist") additionally consume one + // unconditional trailing word, suppressed when the path already ended + // in a dotted extension so "x.sql was deleted" prose stays intact. + // Residue after all rules: one prose word may be over-masked after an + // extensionless home path, and a non-home path's terminal spaced + // component can still leak ONE structure word mid-sentence (no + // personal names there). Anchors include [ and { for bracketed paths. .replace(/(^|[\s"'`=(,[{])(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs|file):\/\/[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?: [\w-]+(?=$|[.,;:)\]}!?]))?/gi, "$1") - .replace(/(^|[\s"'`=(,[{])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`]+(?:(?: [\w.@+()-]+)+[\\\/][^\s'"`]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?: [\w-]+(?=$|[.,;:)\]}!?]))?/g, "$1") + .replace(/(^|[\s"'`=(,[{])[A-Za-z]:[\\\/]Users[\\\/][^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+[\\\/][^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`)\]},]+(?:(?: [\w.@+()-]+)+[\\\/][^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?: [\w-]+(?=$|[.,;:)\]}!?]))?/g, "$1") + .replace(/(^|[\s"'`=(,[{])\/(?:Users|home)\/[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") .replace(/(^|[\s"'`=(,[{])\/(?:[\w.@+-]+\/)+[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?: [\w-]+(?=$|[.,;:)\]}!?]))?/g, "$1") .replace(/(^|[\s"'`=(,[{])~\/[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?: [\w-]+(?=$|[.,;:)\]}!?]))?/g, "$1") // altimate_change end diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 32094070b..b32fb9359 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -71,11 +71,11 @@ describe("maskString paths — review + live-data hardening", () => { .toBe("No such file: ") expect(mask("could not read ~/my dbt/profiles config/profiles.yml")) .toBe("could not read ") - // a trailing spaced filename is consumed — and a SINGLE prose word at - // end-of-string is too (indistinguishable from a terminal spaced dir; - // over-masking doctrine). Mid-sentence prose is protected below. + // a trailing spaced filename is consumed; because the home rule sees the + // dotted extension, the following prose word survives (the generic rules + // still eat one end-of-string word after extensionless paths). expect(mask("open /Users/jdoe/some dir/file name.sql failed")) - .toBe("open ") + .toBe("open failed") }) it("does not let the space-continuation eat trailing prose", () => { @@ -124,3 +124,28 @@ describe("maskString paths — codex re-review round", () => { .toBe("File not found: was deleted upstream") }) }) + +describe("maskString paths — round 4 (spaced usernames, windows delimiters)", () => { + it("masks the tail of a spaced username mid-sentence (home-rooted paths)", () => { + expect(mask("No such file: /Users/Jane Doe does not exist")) + .toBe("No such file: does not exist") // surname consumed, prose kept + expect(mask("stat C:\\Users\\Jane Doe failed hard")).toBe("stat failed hard") + }) + + it("home paths ending in a real file keep following prose (extension lookbehind)", () => { + expect(mask("File not found: /Users/jdoe/models/a.sql was deleted upstream")) + .toBe("File not found: was deleted upstream") + }) + + it("windows paths keep their closing delimiters", () => { + expect(mask("failed (C:\\Users\\jdoe\\x.sql)")).toBe("failed ()") + expect(mask("in [D:\\proj\\data\\y.csv], aborted")).toBe("in [], aborted") + }) + + it("spaced Windows dirs still continue (Program Files shape)", () => { + // ENOENT is eaten by the end-of-string trailing-word rule — accepted + // over-masking (errorCode travels as its own structured field) + expect(mask("spawn C:\\Program Files (x86)\\dbt\\dbt.exe ENOENT")) + .toBe("spawn ") + }) +}) From b466d176dfd562d058fd1d75d137d88b1865579f Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 12:14:33 +0800 Subject: [PATCH 05/46] fix: file:/ URIs, case-insensitive Windows homes, long-extension prose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fourth review round — three real findings and one improvement they implied: - RFC 8089 single-slash file URIs (file:/Users/...) matched neither the scheme rule (required //) nor the POSIX rule (cannot anchor after ':'). The scheme rule now accepts file:/ file:// file:///. - Windows home roots match case-insensitively (C:\users\Jane Doe) — both home rules carry the i flag now. - The extension lookbehind covered 1-4 chars, so .jsonl/.parquet paths ate a following prose word (and skewed hashError grouping). Replaced with a single variable-length (1-8) lookbehind — and applied the same lookbehind to the generic rules' end-of-string trailing-word tails, so prose after any dotted filename now survives everywhere ("dbt.exe ENOENT" keeps ENOENT, improving on the previous round's accepted loss). The fourth reported finding (terminal spaced components mid-sentence) is stale for its own example — /Users/Jane Doe masks since the previous round (probe-verified) — and remains only as the documented no-personal-names residue on ~ and cloud paths. 22 unit tests; 800-message live sweep zero residuals; suites + typecheck green. --- .../opencode/src/altimate/telemetry/index.ts | 12 ++++----- .../test/telemetry/mask-file-paths.test.ts | 25 ++++++++++++++++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 213b8c3f8..37118d466 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1433,12 +1433,12 @@ export namespace Telemetry { // extensionless home path, and a non-home path's terminal spaced // component can still leak ONE structure word mid-sentence (no // personal names there). Anchors include [ and { for bracketed paths. - .replace(/(^|[\s"'`=(,[{])(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs|file):\/\/[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?: [\w-]+(?=$|[.,;:)\]}!?]))?/gi, "$1") - .replace(/(^|[\s"'`=(,[{])[A-Za-z]:[\\\/]Users[\\\/][^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+[\\\/][^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`)\]},]+(?:(?: [\w.@+()-]+)+[\\\/][^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?: [\w-]+(?=$|[.,;:)\]}!?]))?/g, "$1") - .replace(/(^|[\s"'`=(,[{])\/(?:Users|home)\/[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{])\/(?:[\w.@+-]+\/)+[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?: [\w-]+(?=$|[.,;:)\]}!?]))?/g, "$1") - .replace(/(^|[\s"'`=(,[{])~\/[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?: [\w-]+(?=$|[.,;:)\]}!?]))?/g, "$1") + .replace(/(^|[\s"'`=(,[{])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{])[A-Za-z]:[\\\/]Users[\\\/][^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+[\\\/][^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`)\]},]+(?:(?: [\w.@+()-]+)+[\\\/][^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{])\/(?:Users|home)\/[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{])\/(?:[\w.@+-]+\/)+[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{])~\/[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") // altimate_change end .replace(/'(?:[^'\\]|\\.)*'/g, "?") .replace(/"(?:[^"\\]|\\.)*"/g, "?") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index b32fb9359..848f75031 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -143,9 +143,28 @@ describe("maskString paths — round 4 (spaced usernames, windows delimiters)", }) it("spaced Windows dirs still continue (Program Files shape)", () => { - // ENOENT is eaten by the end-of-string trailing-word rule — accepted - // over-masking (errorCode travels as its own structured field) + // dotted extension -> the trailing word is clearly prose and survives expect(mask("spawn C:\\Program Files (x86)\\dbt\\dbt.exe ENOENT")) - .toBe("spawn ") + .toBe("spawn ENOENT") + }) +}) + +describe("maskString paths — round 5", () => { + it("masks single-slash file: URIs (RFC 8089)", () => { + expect(mask("read file:/Users/jdoe/client/model.sql failed")) + .toBe("read failed") + expect(mask("open file:///home/jdoe/x.yml then retry")).toBe("open then retry") + }) + + it("matches Windows home roots case-insensitively", () => { + expect(mask("stat C:\\users\\Jane Doe does not exist")) + .toBe("stat does not exist") + }) + + it("recognizes long extensions so following prose survives", () => { + expect(mask("/Users/jdoe/data.parquet was deleted")) + .toBe(" was deleted") + expect(mask("wrote /home/jdoe/out/events.jsonl then stopped")) + .toBe("wrote then stopped") }) }) From 12728e0a1ae1036553b4a38e635d36a65a00a237 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 12:22:03 +0800 Subject: [PATCH 06/46] fix: unicode path components and colon-adjacent paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fifth review round, both real (probe-confirmed before fixing): - The continuation/terminal word classes were ASCII \w, so a spaced unicode component ("/Users/Jane García/…") broke the chain mid-word and leaked "ía/client/model.sql". All six path rules now use \p{L}\p{N} classes with the u flag (continuation words, terminal words, and the POSIX base segments so "/données/…" roots match at all). Unspaced unicode already worked via the permissive tails. - ":" joins the anchor classes so "ENOENT:/Users/…" and "source:s3://bucket/key" mask. URL interiors stay safe structurally: after "https:" comes "//", and a double slash can never start the POSIX segment chain — locked with negative tests for public URLs, protocol-relative refs, and host:port/db strings. 25 unit tests; 800-message live sweep zero residuals; adjacent suites + typecheck green. --- .../opencode/src/altimate/telemetry/index.ts | 12 ++++----- .../test/telemetry/mask-file-paths.test.ts | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 37118d466..d58cdbd51 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1433,12 +1433,12 @@ export namespace Telemetry { // extensionless home path, and a non-home path's terminal spaced // component can still leak ONE structure word mid-sentence (no // personal names there). Anchors include [ and { for bracketed paths. - .replace(/(^|[\s"'`=(,[{])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{])[A-Za-z]:[\\\/]Users[\\\/][^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+[\\\/][^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`)\]},]+(?:(?: [\w.@+()-]+)+[\\\/][^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{])\/(?:Users|home)\/[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{])\/(?:[\w.@+-]+\/)+[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{])~\/[^\s'"`)\]},]*(?:(?: [\w.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\w.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})[^\s'"`)\]},]*(?:(?: [\p{L}\p{N}_.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\p{L}\p{N}_.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:])[A-Za-z]:[\\\/]Users[\\\/][^\s'"`)\]},]*(?:(?: [\p{L}\p{N}_.@+()-]+)+[\\\/][^\s'"`)\]},]*)*(?: [\p{L}\p{N}_.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`)\]},]+(?:(?: [\p{L}\p{N}_.@+()-]+)+[\\\/][^\s'"`)\]},]*)*(?: [\p{L}\p{N}_.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:])\/(?:Users|home)\/[^\s'"`)\]},]*(?:(?: [\p{L}\p{N}_.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\p{L}\p{N}_.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:])\/(?:[\p{L}\p{N}_.@+-]+\/)+[^\s'"`)\]},]*(?:(?: [\p{L}\p{N}_.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\p{L}\p{N}_.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:])~\/[^\s'"`)\]},]*(?:(?: [\p{L}\p{N}_.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\p{L}\p{N}_.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") // altimate_change end .replace(/'(?:[^'\\]|\\.)*'/g, "?") .replace(/"(?:[^"\\]|\\.)*"/g, "?") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 848f75031..4f9a881f4 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -168,3 +168,29 @@ describe("maskString paths — round 5", () => { .toBe("wrote then stopped") }) }) + +describe("maskString paths — round 6 (unicode, colon anchors)", () => { + it("masks unicode path components across spaces", () => { + expect(mask("read /Users/Jane García/client/model.sql failed")) + .toBe("read failed") + expect(mask("stat /Users/Jane García went missing")).toBe("stat went missing") + expect(mask("open /données/config épais/app.yml now")).toBe("open now") + // unspaced unicode kept working + expect(mask("read /Users/José/client/model.sql failed")).toBe("read failed") + }) + + it("recognizes paths directly after a colon", () => { + expect(mask("ENOENT:/Users/jdoe/client/a.sql")).toBe("ENOENT:") + expect(mask("source:s3://customer-bucket/key")).toBe("source:") + expect(mask("at path:C:\\Users\\jdoe\\x.sql end")).toBe("at path: end") + }) + + it("colon anchors never bite into URLs", () => { + expect(mask("POST https://api.openai.com/v1/chat/completions failed")) + .toBe("POST https://api.openai.com/v1/chat/completions failed") + expect(mask("fetch //cdn.example.com/lib.js failed")) + .toBe("fetch //cdn.example.com/lib.js failed") + expect(mask("connect db-host:5432/postgres refused")) + .toBe("connect db-host:5432/postgres refused") + }) +}) From acef14f0e6e5476ec4c00752e8edac852ddc337b Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 12:28:55 +0800 Subject: [PATCH 07/46] =?UTF-8?q?fix:=20combining=20marks,=20curly=20apost?= =?UTF-8?q?rophes,=20;/<=20boundaries=20=E2=80=94=20and=20rule=20order?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sixth review round: - Word classes gain \p{M} and curly apostrophes: NFD-decomposed macOS names ("Garci" + combining accent) and "O'Connor"-style components mask whole instead of splitting mid-word (test constructs NFD explicitly via normalize("NFD") so the decomposition is real, not editor-normalized). - ";" and "<" join the anchor classes ("ENOENT;/Users/…", "failed "), with ";" and ">" joining the closing exclusions so delimiters survive ("<>", "…;;done"). - The ">" exclusion exposed a latent ordering hazard: the email rule ran BEFORE the path rules and fragmented URI userinfo ("abfss://container@account…") into "" mid-URI. Path rules now run after credentials but before email/internal-host — whole URIs mask before anything can fragment them, which is strictly better privacy. 28 unit tests; 800-message live sweep zero residuals; suites + typecheck green. --- .../opencode/src/altimate/telemetry/index.ts | 58 ++++++++++--------- .../test/telemetry/mask-file-paths.test.ts | 19 ++++++ 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index d58cdbd51..9f9903fe7 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1385,26 +1385,6 @@ export namespace Telemetry { return s .replace(/sk-(?:ant-)?[A-Za-z0-9_-]{20,}/g, "sk-***") .replace(/Bearer\s+[A-Za-z0-9._-]{20,}/gi, "Bearer ***") - // Email addresses — providers occasionally echo caller identity in error text. - .replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, "") - // Internal hostnames in URLs — keeps parity with `parseAPICallError`'s - // `maskInternalHost` so an error message containing the same URL doesn't - // leak through telemetry while metadata.url is masked. Covers: - // *.local / *.internal / *.localhost - // RFC1918 IPv4: 10/8, 172.16/12, 192.168/16, plus 127/8 loopback - // AWS IMDS / link-local IPv4: 169.254/16 - // IPv6 in brackets: [::1] loopback, [fc??::/[fd??:: ULA, [fe80:: link-local - // Char class includes `+`, `#`, `,`, `;` so secrets in query/fragment - // don't survive past the redaction marker. Over-masking is the correct - // failure mode here. - .replace( - // `(?:[^\/\s@]+@)?` allows optional basic-auth userinfo - // (`user:pass@`) before the host so URLs like - // `https://admin:hunter2@10.0.0.5/x` are still recognized as internal - // and redacted whole. The credential goes with the host into . - /\bhttps?:\/\/(?:[^\/\s@]+@)?(?:localhost|127\.\d+\.\d+\.\d+|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[01])\.\d+\.\d+|169\.254\.\d+\.\d+|0\.0\.0\.0|\[(?:::1|fc[0-9a-f]{2}:[^\]]*|fd[0-9a-f]{2}:[^\]]*|fe80:[^\]]*)\]|[A-Za-z0-9.-]+\.(?:local|internal|localhost))(?::\d+)?[\w/.?=&%+#,;~!*'()@:-]*/gi, - "", - ) // altimate_change start — mask filesystem paths in error text // Tool failures embed the path that failed ("File not found: /Users/…"); // home-dir paths carry the OS username, and project-rooted absolute paths @@ -1412,8 +1392,10 @@ export namespace Telemetry { // quote rule below — unquoted ones reached telemetry raw (live 32-machine // core_failure/file_not_found cluster). Same philosophy as the rest of // this chain: over-masking is the correct failure mode. Ordered after the - // URL rule (a public URL's path segment is never word-anchored, so it - // cannot match here) and before quote masking. Three shapes: + // credential rules and BEFORE the email/internal-host rules — a URI's + // userinfo (container@account...) must not be fragmented into + // before the whole URI masks. Public URL interiors stay safe structurally + // (after https: comes //, which cannot start a segment chain). Three shapes: // Windows drive / UNC: C:\Users\… \\server\share // POSIX absolute (2+ segments — bare "/mcp" style tokens survive) // Home-relative: ~/… @@ -1433,13 +1415,33 @@ export namespace Telemetry { // extensionless home path, and a non-home path's terminal spaced // component can still leak ONE structure word mid-sentence (no // personal names there). Anchors include [ and { for bracketed paths. - .replace(/(^|[\s"'`=(,[{:])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})[^\s'"`)\]},]*(?:(?: [\p{L}\p{N}_.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\p{L}\p{N}_.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:])[A-Za-z]:[\\\/]Users[\\\/][^\s'"`)\]},]*(?:(?: [\p{L}\p{N}_.@+()-]+)+[\\\/][^\s'"`)\]},]*)*(?: [\p{L}\p{N}_.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`)\]},]+(?:(?: [\p{L}\p{N}_.@+()-]+)+[\\\/][^\s'"`)\]},]*)*(?: [\p{L}\p{N}_.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:])\/(?:Users|home)\/[^\s'"`)\]},]*(?:(?: [\p{L}\p{N}_.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\p{L}\p{N}_.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:])\/(?:[\p{L}\p{N}_.@+-]+\/)+[^\s'"`)\]},]*(?:(?: [\p{L}\p{N}_.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\p{L}\p{N}_.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:])~\/[^\s'"`)\]},]*(?:(?: [\p{L}\p{N}_.@+()-]+)+\/[^\s'"`)\]},]*)*(?: [\p{L}\p{N}_.@+()-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})[^\s'"`)\]},;>]*(?:(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+)+\/[^\s'"`)\]},;>]*)*(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])[A-Za-z]:[\\\/]Users[\\\/][^\s'"`)\]},;>]*(?:(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+)+[\\\/][^\s'"`)\]},;>]*)*(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`)\]},;>]+(?:(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+)+[\\\/][^\s'"`)\]},;>]*)*(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])\/(?:Users|home)\/[^\s'"`)\]},;>]*(?:(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+)+\/[^\s'"`)\]},;>]*)*(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])\/(?:[\p{L}\p{M}\p{N}_.@+-]+\/)+[^\s'"`)\]},;>]*(?:(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+)+\/[^\s'"`)\]},;>]*)*(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])~\/[^\s'"`)\]},;>]*(?:(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+)+\/[^\s'"`)\]},;>]*)*(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+\.[A-Za-z0-9]{1,8})?(?:(?") // altimate_change end + // Email addresses — providers occasionally echo caller identity in error text. + .replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, "") + // Internal hostnames in URLs — keeps parity with `parseAPICallError`'s + // `maskInternalHost` so an error message containing the same URL doesn't + // leak through telemetry while metadata.url is masked. Covers: + // *.local / *.internal / *.localhost + // RFC1918 IPv4: 10/8, 172.16/12, 192.168/16, plus 127/8 loopback + // AWS IMDS / link-local IPv4: 169.254/16 + // IPv6 in brackets: [::1] loopback, [fc??::/[fd??:: ULA, [fe80:: link-local + // Char class includes `+`, `#`, `,`, `;` so secrets in query/fragment + // don't survive past the redaction marker. Over-masking is the correct + // failure mode here. + .replace( + // `(?:[^\/\s@]+@)?` allows optional basic-auth userinfo + // (`user:pass@`) before the host so URLs like + // `https://admin:hunter2@10.0.0.5/x` are still recognized as internal + // and redacted whole. The credential goes with the host into . + /\bhttps?:\/\/(?:[^\/\s@]+@)?(?:localhost|127\.\d+\.\d+\.\d+|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[01])\.\d+\.\d+|169\.254\.\d+\.\d+|0\.0\.0\.0|\[(?:::1|fc[0-9a-f]{2}:[^\]]*|fd[0-9a-f]{2}:[^\]]*|fe80:[^\]]*)\]|[A-Za-z0-9.-]+\.(?:local|internal|localhost))(?::\d+)?[\w/.?=&%+#,;~!*'()@:-]*/gi, + "", + ) .replace(/'(?:[^'\\]|\\.)*'/g, "?") .replace(/"(?:[^"\\]|\\.)*"/g, "?") .replace(/\s+/g, " ") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 4f9a881f4..8572a6571 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -194,3 +194,22 @@ describe("maskString paths — round 6 (unicode, colon anchors)", () => { .toBe("connect db-host:5432/postgres refused") }) }) + +describe("maskString paths — round 7 (combining marks, punctuation boundaries)", () => { + it("masks NFD-decomposed names (macOS filename normalization)", () => { + const nfd = "/Users/Jane García/client/model.sql".normalize("NFD") + expect(nfd).not.toBe("/Users/Jane García/client/model.sql".normalize("NFC")) // truly decomposed + expect(mask(`read ${nfd} failed`)).toBe("read failed") + }) + + it("masks names with curly apostrophes", () => { + expect(mask("read /Users/Jane O’Connor/client/model.sql failed")) + .toBe("read failed") + }) + + it("recognizes ; and < as path boundaries, preserving closers", () => { + expect(mask("ENOENT;/Users/jdoe/client/a.sql")).toBe("ENOENT;") + expect(mask("failed ")).toBe("failed <>") + expect(mask("cfg=a.yml;/etc/dbt/profiles.yml;done")).toBe("cfg=a.yml;;done") + }) +}) From fabd76b1ad1c48ca42fb506478eae734440a2569 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 12:34:41 +0800 Subject: [PATCH 08/46] fix: ASCII apostrophes as path content when word-char follows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seventh review round, one finding: "/Users/Jane/O'Connor/…" stopped at the apostrophe and leaked the surname + repo layout. The subtlety is that ' is both the quote-rule delimiter and legitimate path content; the disambiguator is what follows — a word character means path content (O'Connor), space/punctuation means a closing quote. Implemented as a guarded alternation '(?=[\p{L}\p{N}_]) in the span, continuation, and terminal classes, so quoted paths keep their closing quote for the quote rule ("open '/Users/…/x.sql' failed" still collapses to "open ? failed", username-free — locked with a test). 30 unit tests; 800-message live sweep zero residuals; suites + typecheck green. --- packages/opencode/src/altimate/telemetry/index.ts | 12 ++++++------ .../test/telemetry/mask-file-paths.test.ts | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 9f9903fe7..0d99ab1d9 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1415,12 +1415,12 @@ export namespace Telemetry { // extensionless home path, and a non-home path's terminal spaced // component can still leak ONE structure word mid-sentence (no // personal names there). Anchors include [ and { for bracketed paths. - .replace(/(^|[\s"'`=(,[{:;<])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})[^\s'"`)\]},;>]*(?:(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+)+\/[^\s'"`)\]},;>]*)*(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])[A-Za-z]:[\\\/]Users[\\\/][^\s'"`)\]},;>]*(?:(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+)+[\\\/][^\s'"`)\]},;>]*)*(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])(?:[A-Za-z]:[\\\/]|\\\\)[^\s'"`)\]},;>]+(?:(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+)+[\\\/][^\s'"`)\]},;>]*)*(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])\/(?:Users|home)\/[^\s'"`)\]},;>]*(?:(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+)+\/[^\s'"`)\]},;>]*)*(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])\/(?:[\p{L}\p{M}\p{N}_.@+-]+\/)+[^\s'"`)\]},;>]*(?:(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+)+\/[^\s'"`)\]},;>]*)*(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])~\/[^\s'"`)\]},;>]*(?:(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+)+\/[^\s'"`)\]},;>]*)*(?: [\p{L}\p{M}\p{N}_.@+()‘’-]+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])[A-Za-z]:[\\\/]Users[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])(?:[A-Za-z]:[\\\/]|\\\\)(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))+(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])\/(?:Users|home)\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])\/(?:[\p{L}\p{M}\p{N}_.@+-]+\/)+(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])~\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") // altimate_change end // Email addresses — providers occasionally echo caller identity in error text. .replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, "") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 8572a6571..ac032aaab 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -213,3 +213,17 @@ describe("maskString paths — round 7 (combining marks, punctuation boundaries) expect(mask("cfg=a.yml;/etc/dbt/profiles.yml;done")).toBe("cfg=a.yml;;done") }) }) + +describe("maskString paths — round 8 (ASCII apostrophes in components)", () => { + it("treats an apostrophe followed by a word char as path content", () => { + expect(mask("read /Users/Jane/O'Connor/client/model.sql failed")) + .toBe("read failed") + expect(mask("stat /Users/Jane O'Connor went missing")).toBe("stat went missing") + }) + + it("still treats a closing apostrophe as a quote delimiter", () => { + // path masks inside the quotes, closing quote survives for the quote rule + expect(mask("open '/Users/jdoe/x.sql' failed")).toBe("open ? failed") + expect(mask("open '/Users/jdoe/x.sql' failed")).not.toContain("jdoe") + }) +}) From ab8417d8d8f31da9944f744cf7c5878bc337187b Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 12:42:34 +0800 Subject: [PATCH 09/46] =?UTF-8?q?fix:=20delimiters=20inside=20path=20compo?= =?UTF-8?q?nents=20=E2=80=94=20and=20the=20halting=20condition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eighth and closing review round: closing delimiters (, ; ] } >) count as path content when a slash-bearing segment follows ("/Users/Jane Doe/client, repo/models/a.sql" masks whole — also covers the macOS "Doe, Jane"-style username directory), while a delimiter NOT followed by a further separator remains a boundary, permanently — past that point "path content vs prose" is undecidable, the same argument as the terminal-word bound. The halting condition is now documented in the rule comment: future reports in this genre are residue by design, not defects. 32 unit tests; 800-message live sweep zero residuals; suites + typecheck green. --- .../opencode/src/altimate/telemetry/index.ts | 25 +++++++++++-------- .../test/telemetry/mask-file-paths.test.ts | 14 +++++++++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 0d99ab1d9..7806ea4da 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1411,16 +1411,21 @@ export namespace Telemetry { // e.g. "/Users/Jane Doe does not exist") additionally consume one // unconditional trailing word, suppressed when the path already ended // in a dotted extension so "x.sql was deleted" prose stays intact. - // Residue after all rules: one prose word may be over-masked after an - // extensionless home path, and a non-home path's terminal spaced - // component can still leak ONE structure word mid-sentence (no - // personal names there). Anchors include [ and { for bracketed paths. - .replace(/(^|[\s"'`=(,[{:;<])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])[A-Za-z]:[\\\/]Users[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])(?:[A-Za-z]:[\\\/]|\\\\)(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))+(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])\/(?:Users|home)\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])\/(?:[\p{L}\p{M}\p{N}_.@+-]+\/)+(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])~\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + // Closing delimiters (, ; ] } >) count as path content only when a + // slash-bearing segment follows ("client, repo/models/…"); a delimiter + // NOT followed by a further separator is a boundary, permanently — + // past that point "path content vs prose" is undecidable, the same + // argument as the terminal-word bound. Residue after all rules: one + // prose word may be over-masked after an extensionless home path, and + // a non-home path's terminal spaced component can still leak ONE + // structure word mid-sentence (no personal names there). Anchors + // include [ and { for bracketed paths. + .replace(/(^|[\s"'`=(,[{:;<])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])[A-Za-z]:[\\\/]Users[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])(?:[A-Za-z]:[\\\/]|\\\\)(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))+(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])\/(?:Users|home)\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])\/(?:[\p{L}\p{M}\p{N}_.@+-]+\/)+(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])~\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") // altimate_change end // Email addresses — providers occasionally echo caller identity in error text. .replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, "") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index ac032aaab..77e7c353e 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -227,3 +227,17 @@ describe("maskString paths — round 8 (ASCII apostrophes in components)", () => expect(mask("open '/Users/jdoe/x.sql' failed")).not.toContain("jdoe") }) }) + +describe("maskString paths — closing round (delimiters inside components)", () => { + it("continues through delimiters that a later separator proves are path content", () => { + expect(mask("read /Users/Jane Doe/client, repo/models/a.sql failed")) + .toBe("read failed") + expect(mask("open ~/reports,final/q3.csv now")).toBe("open now") + }) + + it("a delimiter without a following separator stays a boundary", () => { + expect(mask("read /app/data/x.csv, then /var/log/y.log failed")) + .toBe("read , then failed") + expect(mask("saw [/opt/dbt/a.yml], aborted")).toBe("saw [], aborted") + }) +}) From 7b794e7dd95b4211495545d888b2ac14a55f0836 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 13:06:42 +0800 Subject: [PATCH 10/46] =?UTF-8?q?fix:=20linearize=20the=20delimiter=20look?= =?UTF-8?q?ahead=20=E2=80=94=20ReDoS,=20and=20two=20coverage=20gaps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ready-for-review fleet caught a CRITICAL in the round-8 delimiter continuation: the lookahead (?:(?: ?X+)+/) has the classic nested-quantifier catastrophic-backtracking shape. Empirically exponential — ~20ms at 20 chars, 333ms at 24, 1.9s at 28 — so a plausible message ("path, ") hangs maskString outright. Rewritten as two single-quantifier branches (X*/ for delimiter-attached separators, " X+/" for one spaced word), which is linear (1000-char adversarial input: <1ms; regression test pins it at <500ms for 5000 chars). The rewrite closes two more fleet findings for free: delimiters directly attached to a separator now continue ("/data/a[b]/c.txt" masks whole), and the Windows rules' lookahead accepts backslash separators ("C:\Users\...\client, repo\x.sql" masks whole — the / -only lookahead could never succeed on a backslash path). Stale "Three shapes" comment updated to the actual six-rule enumeration. 35 unit tests; 800-message live sweep zero residuals; suites + typecheck green. --- .../opencode/src/altimate/telemetry/index.ts | 14 +++++++------- .../test/telemetry/mask-file-paths.test.ts | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 7806ea4da..1f17baab7 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1395,7 +1395,7 @@ export namespace Telemetry { // credential rules and BEFORE the email/internal-host rules — a URI's // userinfo (container@account...) must not be fragmented into // before the whole URI masks. Public URL interiors stay safe structurally - // (after https: comes //, which cannot start a segment chain). Three shapes: + // (after https: comes //, which cannot start a segment chain). Six rules (cloud URIs, Windows home, Windows/UNC, POSIX home, POSIX, ~): // Windows drive / UNC: C:\Users\… \\server\share // POSIX absolute (2+ segments — bare "/mcp" style tokens survive) // Home-relative: ~/… @@ -1420,12 +1420,12 @@ export namespace Telemetry { // a non-home path's terminal spaced component can still leak ONE // structure word mid-sentence (no personal names there). Anchors // include [ and { for bracketed paths. - .replace(/(^|[\s"'`=(,[{:;<])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])[A-Za-z]:[\\\/]Users[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])(?:[A-Za-z]:[\\\/]|\\\\)(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))+(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])\/(?:Users|home)\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])\/(?:[\p{L}\p{M}\p{N}_.@+-]+\/)+(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])~\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=(?: ?[\p{L}\p{M}\p{N}_.@+()'‘’-]+)+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])[A-Za-z]:[\\\/]Users[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*[\\\/]| [\p{L}\p{M}\p{N}_.@+()'‘’-]+[\\\/]))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*[\\\/]| [\p{L}\p{M}\p{N}_.@+()'‘’-]+[\\\/]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])(?:[A-Za-z]:[\\\/]|\\\\)(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*[\\\/]| [\p{L}\p{M}\p{N}_.@+()'‘’-]+[\\\/]))+(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*[\\\/]| [\p{L}\p{M}\p{N}_.@+()'‘’-]+[\\\/]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])\/(?:Users|home)\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])\/(?:[\p{L}\p{M}\p{N}_.@+-]+\/)+(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])~\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") // altimate_change end // Email addresses — providers occasionally echo caller identity in error text. .replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, "") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 77e7c353e..f3376b14b 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -241,3 +241,19 @@ describe("maskString paths — closing round (delimiters inside components)", () expect(mask("saw [/opt/dbt/a.yml], aborted")).toBe("saw [], aborted") }) }) + +describe("maskString paths — fleet review round (ReDoS + separators)", () => { + it("adversarial delimiter + slash-free run completes in linear time", () => { + const t0 = performance.now() + mask("/a/b, " + "a".repeat(5000)) + expect(performance.now() - t0).toBeLessThan(500) // was exponential: ~2s at 28 chars + }) + + it("delimiters attached directly to a separator continue the path", () => { + expect(mask("read /data/a[b]/c.txt failed")).toBe("read failed") + }) + + it("windows delimiter continuation accepts backslash separators", () => { + expect(mask("del C:\\Users\\jdoe\\client, repo\\x.sql now")).toBe("del now") + }) +}) From 6e1d6399a51f529aaf73a3c6e1620500d835a448 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 13:17:52 +0800 Subject: [PATCH 11/46] fix: dot-relative paths, symbol components, cloud terminal keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fleet round two, all three probe-confirmed: - ./ and ../ are unambiguous filesystem prefixes with no prose collision — the generic POSIX rule now accepts them, so "./customers/acme/…" masks. - Spaced components starting with symbols ("client #1/models/…") broke the continuation word-run allowlist; slash-proven chunks and the delimiter lookaheads now accept any non-space/separator/quote run. Still linear: iterations stay anchored by literal spaces with a space-free inner class, so the parse is unambiguous (unlike the optional-space shape behind the ReDoS). - Cloud URIs get the home-style unconditional trailing word (extension lookbehind intact): "s3://customer-bucket/client repo failed" masks the key component; "data.csv failed to download" keeps its prose. Bucket/key layout is the same high-PII class that motivated the cloud rule. 38 unit tests; 800-message live sweep zero residuals; suites + typecheck green. --- .../opencode/src/altimate/telemetry/index.ts | 12 ++++++------ .../test/telemetry/mask-file-paths.test.ts | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 1f17baab7..44ca9c2ba 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1420,12 +1420,12 @@ export namespace Telemetry { // a non-home path's terminal spaced component can still leak ONE // structure word mid-sentence (no personal names there). Anchors // include [ and { for bracketed paths. - .replace(/(^|[\s"'`=(,[{:;<])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])[A-Za-z]:[\\\/]Users[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*[\\\/]| [\p{L}\p{M}\p{N}_.@+()'‘’-]+[\\\/]))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*[\\\/]| [\p{L}\p{M}\p{N}_.@+()'‘’-]+[\\\/]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])(?:[A-Za-z]:[\\\/]|\\\\)(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*[\\\/]| [\p{L}\p{M}\p{N}_.@+()'‘’-]+[\\\/]))+(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*[\\\/]| [\p{L}\p{M}\p{N}_.@+()'‘’-]+[\\\/]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])\/(?:Users|home)\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])\/(?:[\p{L}\p{M}\p{N}_.@+-]+\/)+(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])~\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*(?:(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[\p{L}\p{M}\p{N}_.@+()'‘’-]*\/| [\p{L}\p{M}\p{N}_.@+()'‘’-]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*(?:(?: [^\s\/\\'"`]+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])[A-Za-z]:[\\\/]Users[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*[\\\/]| [^\s\/\\'"`]+[\\\/]))*(?:(?: [^\s\/\\'"`]+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*[\\\/]| [^\s\/\\'"`]+[\\\/]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])(?:[A-Za-z]:[\\\/]|\\\\)(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*[\\\/]| [^\s\/\\'"`]+[\\\/]))+(?:(?: [^\s\/\\'"`]+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*[\\\/]| [^\s\/\\'"`]+[\\\/]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])\/(?:Users|home)\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*(?:(?: [^\s\/\\'"`]+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])\.{0,2}\/(?:[\p{L}\p{M}\p{N}_.@+-]+\/)+(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*(?:(?: [^\s\/\\'"`]+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + .replace(/(^|[\s"'`=(,[{:;<])~\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*(?:(?: [^\s\/\\'"`]+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") // altimate_change end // Email addresses — providers occasionally echo caller identity in error text. .replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, "") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index f3376b14b..dab0c5638 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -257,3 +257,22 @@ describe("maskString paths — fleet review round (ReDoS + separators)", () => { expect(mask("del C:\\Users\\jdoe\\client, repo\\x.sql now")).toBe("del now") }) }) + +describe("maskString paths — fleet round 2 (relative, symbols, cloud tails)", () => { + it("masks dot-relative paths (unambiguous ./ and ../ prefixes)", () => { + expect(mask("read failed: ./customers/acme/models/private.sql")).toBe("read failed: ") + expect(mask("open ../client-repo/profiles.yml now")).toBe("open now") + }) + + it("continues spaced components that start with symbols", () => { + expect(mask("read /Users/Jane Doe/client #1/models/a.sql failed")) + .toBe("read failed") + }) + + it("cloud object keys consume a terminal spaced component like home paths", () => { + expect(mask("read s3://customer-bucket/client repo failed")).toBe("read failed") + // dotted extension still protects following prose + expect(mask("read s3://customer-bucket/data.csv failed to download")) + .toBe("read failed to download") + }) +}) From a5143165e94b4d94fbf75ea4fd25b723c3b1cb01 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 13:33:16 +0800 Subject: [PATCH 12/46] refactor: compose path rules from shared fragments; five coverage fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fleet round three. The six hand-edited ~800-char literals had produced three drift bugs across the review rounds (Windows separator gap, flag divergence, stale doctrine), so the rules are now composed from shared String fragments (PATH_RULES) — one source of truth for the anchor class, path-run character (guarded apostrophe included), delimiter lookaheads, continuation chunks, terminal-filename group, and trailing-word tails. All 38 pre-existing tests passed the composition unchanged. Coverage fixes, each probe-confirmed and in the composed form: - delimiter lookaheads accept multi-word components (", big client repo/") - terminal dotted filenames may span up to four words ("Q4 final reviewed version.sql") - the first POSIX segment accepts symbols/emoji ("/#clients/…", "/💾/…") - named-user tilde homes mask ("~jane/client-repo/…") - apostrophes survive inside spaced components ("Jane Doe's client/") Doctrine comment rewritten to match reality (cloud URIs consume the unconditional trailing word alongside home-rooted paths — kilo's stale- comment catch). Linearity preserved: every quantified unit is space- or separator-anchored with a disjoint inner class; the adversarial timing tests pin both the original ReDoS shape and the new multi-word lookahead. 44 unit tests; 800-message live sweep zero residuals; suites + typecheck green. --- .../opencode/src/altimate/telemetry/index.ts | 95 +++++++++++-------- .../test/telemetry/mask-file-paths.test.ts | 30 ++++++ 2 files changed, 85 insertions(+), 40 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 44ca9c2ba..1fa2a2039 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1381,51 +1381,66 @@ export namespace Telemetry { // Bearer … Authorization headers leaked in error text // Each match replaces with a fixed redaction so length-based fingerprinting // can't reconstruct the original token. + // altimate_change start — composed path-masking rules (shared fragments) + // R: one path-run character — anything but whitespace/separators/quotes, + // plus an apostrophe when a word character follows (O'Connor vs closing '). + const PM_R = "(?:[^\\s\\/\\\\'\"`]|'(?=[\\p{L}\\p{N}_]))" + const PM_WORD = "(?:[\\p{L}\\p{M}\\p{N}_‘’-]|'(?=[\\p{L}\\p{N}_]))" + const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<])" + const PM_EXT = "\\.[A-Za-z0-9]{1,8}" + // span char: path content incl. delimiters (, ; ] } >) that a later + // separator proves are path content — multi-word spaced runs allowed, all + // quantified units space- or separator-anchored with disjoint inner classes + // (unambiguous parse => linear time; the nested-quantifier ReDoS shape is + // banned here). + const pmSpan = (sep: string) => + "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;\\]}>](?=" + PM_R + "*" + sep + "|(?: " + PM_R + "+)+" + sep + "))" + const pmChunks = (sep: string) => "(?:(?: " + PM_R + "+)+" + sep + pmSpan(sep) + "*)*" + // terminal dotted filename: up to four spaced words that END in an extension + const PM_SPFILE = "(?:(?: " + PM_R + "+){1,4}(?<=" + PM_EXT + "))?" + const PM_TERM_COND = "(?:(? pmSpan(sep) + "*" + pmChunks(sep) + PM_SPFILE + term + const PATH_RULES = { + cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), + windowsHome: new RegExp(PM_ANCHOR + "[A-Za-z]:" + SEP_W + "Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), + windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|\\\\\\\\)" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + PM_SPFILE + PM_TERM_COND, "gu"), + posixHome: new RegExp(PM_ANCHOR + "\\/(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), + posix: new RegExp(PM_ANCHOR + "\\.{0,2}\\/(?:" + PM_R + "+\\/)+" + pmTail(SEP_P, PM_TERM_COND), "gu"), + tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_COND), "gu"), + } + // altimate_change end + export function maskString(s: string): string { return s .replace(/sk-(?:ant-)?[A-Za-z0-9_-]{20,}/g, "sk-***") .replace(/Bearer\s+[A-Za-z0-9._-]{20,}/gi, "Bearer ***") // altimate_change start — mask filesystem paths in error text - // Tool failures embed the path that failed ("File not found: /Users/…"); - // home-dir paths carry the OS username, and project-rooted absolute paths - // leak client repo structure. QUOTED paths were already destroyed by the - // quote rule below — unquoted ones reached telemetry raw (live 32-machine - // core_failure/file_not_found cluster). Same philosophy as the rest of - // this chain: over-masking is the correct failure mode. Ordered after the - // credential rules and BEFORE the email/internal-host rules — a URI's - // userinfo (container@account...) must not be fragmented into - // before the whole URI masks. Public URL interiors stay safe structurally - // (after https: comes //, which cannot start a segment chain). Six rules (cloud URIs, Windows home, Windows/UNC, POSIX home, POSIX, ~): - // Windows drive / UNC: C:\Users\… \\server\share - // POSIX absolute (2+ segments — bare "/mcp" style tokens survive) - // Home-relative: ~/… - // Cloud-storage URIs (gs://, s3://, abfss://, …) — bucket names identify - // the customer; not http, so the URL rule above never sees them. - // Paths with embedded spaces (macOS "/Users/Jane Doe/client repo/…") - // continue across a space whenever a later chunk carries another - // separator, plus one optional trailing spaced filename, plus one - // trailing spaced WORD only at end-of-string / before punctuation — - // "path + space + word" is otherwise undecidable vs trailing prose - // ("client repo" vs "x.sql was"). HOME-ROOTED paths (/Users, /home, - // C:\Users — where a spaced USERNAME would otherwise leak its tail, - // e.g. "/Users/Jane Doe does not exist") additionally consume one - // unconditional trailing word, suppressed when the path already ended - // in a dotted extension so "x.sql was deleted" prose stays intact. - // Closing delimiters (, ; ] } >) count as path content only when a - // slash-bearing segment follows ("client, repo/models/…"); a delimiter - // NOT followed by a further separator is a boundary, permanently — - // past that point "path content vs prose" is undecidable, the same - // argument as the terminal-word bound. Residue after all rules: one - // prose word may be over-masked after an extensionless home path, and - // a non-home path's terminal spaced component can still leak ONE - // structure word mid-sentence (no personal names there). Anchors - // include [ and { for bracketed paths. - .replace(/(^|[\s"'`=(,[{:;<])(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\/\/|file:\/{1,3})(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*(?:(?: [^\s\/\\'"`]+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])[A-Za-z]:[\\\/]Users[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*[\\\/]| [^\s\/\\'"`]+[\\\/]))*(?:(?: [^\s\/\\'"`]+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*[\\\/]| [^\s\/\\'"`]+[\\\/]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])(?:[A-Za-z]:[\\\/]|\\\\)(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*[\\\/]| [^\s\/\\'"`]+[\\\/]))+(?:(?: [^\s\/\\'"`]+)+[\\\/](?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*[\\\/]| [^\s\/\\'"`]+[\\\/]))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])\/(?:Users|home)\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*(?:(?: [^\s\/\\'"`]+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])\.{0,2}\/(?:[\p{L}\p{M}\p{N}_.@+-]+\/)+(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*(?:(?: [^\s\/\\'"`]+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") - .replace(/(^|[\s"'`=(,[{:;<])~\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*(?:(?: [^\s\/\\'"`]+)+\/(?:[^\s'"`)\]},;>]|'(?=[\p{L}\p{N}_])|[,;\]}>](?=[^\s\/\\'"`]*\/| [^\s\/\\'"`]+\/))*)*(?: (?:[\p{L}\p{M}\p{N}_.@+()‘’-]|'(?=[\p{L}\p{N}_]))+\.[A-Za-z0-9]{1,8})?(?:(?") + // Six masking rules (cloud URIs, Windows home, Windows/UNC, POSIX home, + // POSIX incl. ./ and ../, ~ incl. ~username), composed from shared + // fragments below (PATH_RULES) — one source of truth after repeated + // lockstep edits drifted (see PR history). Ordered after the credential + // rules and BEFORE the email/internal-host rules so whole URIs mask + // before userinfo can fragment into . Public URL interiors are + // structurally safe: after "https:" comes "//", which cannot start a + // segment chain. Doctrine: over-masking is the correct failure mode. + // HOME-ROOTED paths and CLOUD URIs consume one unconditional trailing + // word (spaced usernames / object keys — the high-PII classes), + // suppressed after a dotted extension so "x.sql was deleted" prose + // survives; other rules consume a trailing word only at end-of-string / + // before punctuation. Residue (by design): one prose word may be + // over-masked after extensionless home/cloud paths; a non-home, + // non-cloud path's terminal spaced component can leak ONE structure + // word mid-sentence (no personal names in that class); a delimiter not + // followed by a further separator is a permanent boundary. + .replace(PATH_RULES.cloud, "$1") + .replace(PATH_RULES.windowsHome, "$1") + .replace(PATH_RULES.windows, "$1") + .replace(PATH_RULES.posixHome, "$1") + .replace(PATH_RULES.posix, "$1") + .replace(PATH_RULES.tilde, "$1") // altimate_change end // Email addresses — providers occasionally echo caller identity in error text. .replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, "") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index dab0c5638..1a594c920 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -276,3 +276,33 @@ describe("maskString paths — fleet round 2 (relative, symbols, cloud tails)", .toBe("read failed to download") }) }) + +describe("maskString paths — fleet round 3 (composed rules)", () => { + it("delimiter lookahead accepts multi-word components", () => { + expect(mask("read /data/x, big client repo/models/a.sql done")).toBe("read done") + }) + + it("terminal dotted filename may span multiple words", () => { + expect(mask("read /Users/jdoe/reports/Q4 final reviewed version.sql failed")) + .toBe("read failed") + }) + + it("first POSIX segment may contain symbols or emoji", () => { + expect(mask("read /#clients/acme/private.sql failed")).toBe("read failed") + expect(mask("read /💾/acme/private.sql failed")).toBe("read failed") + }) + + it("named-user tilde homes mask", () => { + expect(mask("could not read ~jane/client-repo/profiles.yml")).toBe("could not read ") + }) + + it("apostrophes survive inside spaced components", () => { + expect(mask("stat /Users/Jane Doe's client/models/a.sql now")).toBe("stat now") + }) + + it("multi-word lookahead stays linear on adversarial input", () => { + const t0 = performance.now() + mask("/a/b, " + "word ".repeat(1000)) + expect(performance.now() - t0).toBeLessThan(500) + }) +}) From 8801300e55e6ccdaba165607b89de043452e2c7c Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 14:48:00 +0800 Subject: [PATCH 13/46] fix: parens inside components, nested home roots (WSL) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fleet round four — both findings independently converged on by two reviewers: - ")" joins the separator-proven delimiter branch, so "client)repo/models/a.sql" masks whole while a closing paren without a following separator stays preserved ("(from /opt/dbt/bin/dbt)"). - posixHome accepts prefix segments before Users|home, so WSL paths (/mnt/c/Users/Jane Doe ...) and Silverblue-style /var/home get the home-rule unconditional trailing word — the spaced-surname protection — instead of falling through to the generic rule. One-fragment edits each, thanks to the composed PATH_RULES. 46 unit tests; 800-message live sweep zero residuals; suites + typecheck green. --- packages/opencode/src/altimate/telemetry/index.ts | 4 ++-- .../test/telemetry/mask-file-paths.test.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 1fa2a2039..040e58413 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1394,7 +1394,7 @@ export namespace Telemetry { // (unambiguous parse => linear time; the nested-quantifier ReDoS shape is // banned here). const pmSpan = (sep: string) => - "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;\\]}>](?=" + PM_R + "*" + sep + "|(?: " + PM_R + "+)+" + sep + "))" + "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + PM_R + "*" + sep + "|(?: " + PM_R + "+)+" + sep + "))" const pmChunks = (sep: string) => "(?:(?: " + PM_R + "+)+" + sep + pmSpan(sep) + "*)*" // terminal dotted filename: up to four spaced words that END in an extension const PM_SPFILE = "(?:(?: " + PM_R + "+){1,4}(?<=" + PM_EXT + "))?" @@ -1407,7 +1407,7 @@ export namespace Telemetry { cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), windowsHome: new RegExp(PM_ANCHOR + "[A-Za-z]:" + SEP_W + "Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|\\\\\\\\)" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + PM_SPFILE + PM_TERM_COND, "gu"), - posixHome: new RegExp(PM_ANCHOR + "\\/(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), + posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R + "+\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), posix: new RegExp(PM_ANCHOR + "\\.{0,2}\\/(?:" + PM_R + "+\\/)+" + pmTail(SEP_P, PM_TERM_COND), "gu"), tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_COND), "gu"), } diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 1a594c920..2b79401d2 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -306,3 +306,17 @@ describe("maskString paths — fleet round 3 (composed rules)", () => { expect(performance.now() - t0).toBeLessThan(500) }) }) + +describe("maskString paths — fleet round 4 (parens in components, nested homes)", () => { + it("continues through ) when a later separator proves it is path content", () => { + expect(mask("read /Users/jdoe/client)repo/models/a.sql failed")).toBe("read failed") + // closing paren without a following separator stays preserved + expect(mask("failed (from /opt/dbt/bin/dbt)")).toBe("failed (from )") + }) + + it("nested home roots get home-rule masking (WSL, Silverblue)", () => { + expect(mask("stat /mnt/c/Users/Jane Doe does not exist")) + .toBe("stat does not exist") + expect(mask("check /var/home/jane went missing")).toBe("check missing") + }) +}) From f64496decaf00fb1707ee5638bd91f654f1e1356 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 15:27:04 +0800 Subject: [PATCH 14/46] fix: windows dot-relative paths, attached dotted filenames after delimiters - windows rule accepts .\ and ..\ prefixes (mirror of the POSIX ./ ../ fix; requires a full component chain so dotted prose and printed escapes never match) - shared delimiter lookahead gains an extension proof: a delimiter followed by an attached span ending in a dotted extension at a terminal boundary (;draft.sql) is path content; shell-style ;cmd stays a documented boundary - tab remains a field boundary by design (documented residue + test) --- .../opencode/src/altimate/telemetry/index.ts | 18 +++++---- .../test/telemetry/mask-file-paths.test.ts | 39 +++++++++++++++++++ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 040e58413..277b9cc5d 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1388,13 +1388,14 @@ export namespace Telemetry { const PM_WORD = "(?:[\\p{L}\\p{M}\\p{N}_‘’-]|'(?=[\\p{L}\\p{N}_]))" const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<])" const PM_EXT = "\\.[A-Za-z0-9]{1,8}" - // span char: path content incl. delimiters (, ; ] } >) that a later - // separator proves are path content — multi-word spaced runs allowed, all + // span char: path content incl. delimiters (, ; ) ] } >) that a later + // separator — or an attached dotted terminal filename (;draft.sql) — + // proves is path content — multi-word spaced runs allowed, all // quantified units space- or separator-anchored with disjoint inner classes // (unambiguous parse => linear time; the nested-quantifier ReDoS shape is // banned here). const pmSpan = (sep: string) => - "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + PM_R + "*" + sep + "|(?: " + PM_R + "+)+" + sep + "))" + "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + PM_R + "*" + sep + "|(?: " + PM_R + "+)+" + sep + "|" + PM_R + "*" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" const pmChunks = (sep: string) => "(?:(?: " + PM_R + "+)+" + sep + pmSpan(sep) + "*)*" // terminal dotted filename: up to four spaced words that END in an extension const PM_SPFILE = "(?:(?: " + PM_R + "+){1,4}(?<=" + PM_EXT + "))?" @@ -1406,7 +1407,7 @@ export namespace Telemetry { const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), windowsHome: new RegExp(PM_ANCHOR + "[A-Za-z]:" + SEP_W + "Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), - windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|\\\\\\\\)" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + PM_SPFILE + PM_TERM_COND, "gu"), + windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+\\\\))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + PM_SPFILE + PM_TERM_COND, "gu"), posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R + "+\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), posix: new RegExp(PM_ANCHOR + "\\.{0,2}\\/(?:" + PM_R + "+\\/)+" + pmTail(SEP_P, PM_TERM_COND), "gu"), tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_COND), "gu"), @@ -1418,8 +1419,8 @@ export namespace Telemetry { .replace(/sk-(?:ant-)?[A-Za-z0-9_-]{20,}/g, "sk-***") .replace(/Bearer\s+[A-Za-z0-9._-]{20,}/gi, "Bearer ***") // altimate_change start — mask filesystem paths in error text - // Six masking rules (cloud URIs, Windows home, Windows/UNC, POSIX home, - // POSIX incl. ./ and ../, ~ incl. ~username), composed from shared + // Six masking rules (cloud URIs, Windows home, Windows/UNC incl. .\ and + // ..\, POSIX home, POSIX incl. ./ and ../, ~ incl. ~username), composed from shared // fragments below (PATH_RULES) — one source of truth after repeated // lockstep edits drifted (see PR history). Ordered after the credential // rules and BEFORE the email/internal-host rules so whole URIs mask @@ -1433,8 +1434,9 @@ export namespace Telemetry { // before punctuation. Residue (by design): one prose word may be // over-masked after extensionless home/cloud paths; a non-home, // non-cloud path's terminal spaced component can leak ONE structure - // word mid-sentence (no personal names in that class); a delimiter not - // followed by a further separator is a permanent boundary. + // word mid-sentence (no personal names in that class); a delimiter + // followed by neither a further separator nor a dotted terminal + // filename is a permanent boundary. .replace(PATH_RULES.cloud, "$1") .replace(PATH_RULES.windowsHome, "$1") .replace(PATH_RULES.windows, "$1") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 2b79401d2..2ff727b1c 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -320,3 +320,42 @@ describe("maskString paths — fleet round 4 (parens in components, nested homes expect(mask("check /var/home/jane went missing")).toBe("check missing") }) }) + +describe("maskString paths — fleet round 5 (dot-relative windows, attached filenames)", () => { + it("masks Windows dot-relative paths", () => { + expect(mask(String.raw`read .\customers\acme\models\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`open ..\client-repo\profiles.yml now`)).toBe("open now") + }) + + it("dot-relative prefix never fires on dotted prose or printed escapes", () => { + expect(mask("wait...\\ hmm that failed")).toBe("wait...\\ hmm that failed") + expect(mask("unexpected token .\\n at position 5")).toBe("unexpected token .\\n at position 5") + }) + + it("attached dotted terminal filename proves a delimiter (all rule families)", () => { + expect(mask("read /Users/jdoe/project;draft.sql")).toBe("read ") + expect(mask("C:\\Users\\jdoe\\proj;draft.sql gone")).toBe(" gone") + expect(mask("s3://bucket/dir;file.csv loaded")).toBe(" loaded") + }) + + it("attached delimiter without separator or dotted filename stays a boundary", () => { + // shell-style `;cmd` must not be eaten — documented residue + expect(mask("cd /Users/jdoe/proj;ls failed")).toBe("cd ;ls failed") + expect(mask("shell: cd /opt/app; rm -rf tmp")).toBe("shell: cd ; rm -rf tmp") + }) + + it("tab is a field boundary, not path continuation (documented residue)", () => { + // tab-delimited log columns after a path must survive; the bare relative + // fragment after the tab is the documented undecidable-fragment boundary + expect(mask("/Users/jdoe/client\tsecret/models/a.sql")).toBe(" secret/models/a.sql") + }) + + it("extension-proof delimiter branch stays linear on adversarial input", () => { + let t0 = performance.now() + mask("/a/b/x;" + "a".repeat(5000)) + expect(performance.now() - t0).toBeLessThan(500) + t0 = performance.now() + mask("/a/b/x;" + "a.".repeat(2500)) + expect(performance.now() - t0).toBeLessThan(500) + }) +}) From a880343038e42737b66770f6cba2942c39062ab1 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 16:05:05 +0800 Subject: [PATCH 15/46] fix: backslash path components, extended-length windows homes; move helpers top-level - run class is now separator-specific: slash-delimited rules (cloud, POSIX, tilde) keep literal backslashes as path content, so spaced components like 'customer data\raw/...' mask whole - windowsHome accepts the \\?\ extended-length prefix so long-path errors get home-rule spaced-username protection - composed fragments relocated above the namespace per the module convention (namespace-private helpers = non-exported top-level) --- .../opencode/src/altimate/telemetry/index.ts | 68 ++++++++++--------- .../test/telemetry/mask-file-paths.test.ts | 23 +++++++ 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 277b9cc5d..3685ac09a 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -64,6 +64,42 @@ function isAutomatedRun(): boolean { return Boolean(process.env.BUN_TEST || process.env.VITEST || process.env.JEST_WORKER_ID) } +// altimate_change start — composed path-masking rules (shared fragments) +// R: one path-run character — anything but whitespace/separators/quotes, +// plus an apostrophe when a word character follows (O'Connor vs closing '). +const PM_R = "(?:[^\\s\\/\\\\'\"`]|'(?=[\\p{L}\\p{N}_]))" +// slash-delimited variant: backslash is path content, not a separator +const PM_R_P = "(?:[^\\s\\/'\"`]|'(?=[\\p{L}\\p{N}_]))" +const PM_WORD = "(?:[\\p{L}\\p{M}\\p{N}_‘’-]|'(?=[\\p{L}\\p{N}_]))" +const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<])" +const PM_EXT = "\\.[A-Za-z0-9]{1,8}" +// span char: path content incl. delimiters (, ; ) ] } >) that a later +// separator — or an attached dotted terminal filename (;draft.sql) — +// proves is path content — multi-word spaced runs allowed, all +// quantified units space- or separator-anchored with disjoint inner classes +// (unambiguous parse => linear time; the nested-quantifier ReDoS shape is +// banned here). +const SEP_P = "\\/" +const SEP_W = "[\\\\\\/]" +const pmR = (sep: string) => (sep === SEP_P ? PM_R_P : PM_R) +const pmSpan = (sep: string) => + "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "*" + sep + "|(?: " + pmR(sep) + "+)+" + sep + "|" + pmR(sep) + "*" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" +const pmChunks = (sep: string) => "(?:(?: " + pmR(sep) + "+)+" + sep + pmSpan(sep) + "*)*" +// terminal dotted filename: up to four spaced words that END in an extension +const pmSpFile = (sep: string) => "(?:(?: " + pmR(sep) + "+){1,4}(?<=" + PM_EXT + "))?" +const PM_TERM_COND = "(?:(? pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term +const PATH_RULES = { + cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), + windowsHome: new RegExp(PM_ANCHOR + "(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), + windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+\\\\))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), + posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), + posix: new RegExp(PM_ANCHOR + "\\.{0,2}\\/(?:" + PM_R_P + "+\\/)+" + pmTail(SEP_P, PM_TERM_COND), "gu"), + tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_COND), "gu"), +} +// altimate_change end + export namespace Telemetry { const FLUSH_INTERVAL_MS = 5_000 const MAX_BUFFER_SIZE = 200 @@ -1381,38 +1417,6 @@ export namespace Telemetry { // Bearer … Authorization headers leaked in error text // Each match replaces with a fixed redaction so length-based fingerprinting // can't reconstruct the original token. - // altimate_change start — composed path-masking rules (shared fragments) - // R: one path-run character — anything but whitespace/separators/quotes, - // plus an apostrophe when a word character follows (O'Connor vs closing '). - const PM_R = "(?:[^\\s\\/\\\\'\"`]|'(?=[\\p{L}\\p{N}_]))" - const PM_WORD = "(?:[\\p{L}\\p{M}\\p{N}_‘’-]|'(?=[\\p{L}\\p{N}_]))" - const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<])" - const PM_EXT = "\\.[A-Za-z0-9]{1,8}" - // span char: path content incl. delimiters (, ; ) ] } >) that a later - // separator — or an attached dotted terminal filename (;draft.sql) — - // proves is path content — multi-word spaced runs allowed, all - // quantified units space- or separator-anchored with disjoint inner classes - // (unambiguous parse => linear time; the nested-quantifier ReDoS shape is - // banned here). - const pmSpan = (sep: string) => - "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + PM_R + "*" + sep + "|(?: " + PM_R + "+)+" + sep + "|" + PM_R + "*" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" - const pmChunks = (sep: string) => "(?:(?: " + PM_R + "+)+" + sep + pmSpan(sep) + "*)*" - // terminal dotted filename: up to four spaced words that END in an extension - const PM_SPFILE = "(?:(?: " + PM_R + "+){1,4}(?<=" + PM_EXT + "))?" - const PM_TERM_COND = "(?:(? pmSpan(sep) + "*" + pmChunks(sep) + PM_SPFILE + term - const PATH_RULES = { - cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), - windowsHome: new RegExp(PM_ANCHOR + "[A-Za-z]:" + SEP_W + "Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), - windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+\\\\))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + PM_SPFILE + PM_TERM_COND, "gu"), - posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R + "+\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), - posix: new RegExp(PM_ANCHOR + "\\.{0,2}\\/(?:" + PM_R + "+\\/)+" + pmTail(SEP_P, PM_TERM_COND), "gu"), - tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_COND), "gu"), - } - // altimate_change end export function maskString(s: string): string { return s diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 2ff727b1c..ac2eb6f0d 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -359,3 +359,26 @@ describe("maskString paths — fleet round 5 (dot-relative windows, attached fil expect(performance.now() - t0).toBeLessThan(500) }) }) + +describe("maskString paths — fleet round 6 (backslash components, extended-length homes)", () => { + it("keeps literal backslashes as path content in slash-delimited rules", () => { + expect(mask(String.raw`read s3://bucket/customer data\raw/models/a.sql failed`)).toBe("read failed") + expect(mask(String.raw`stat /opt/a\b/c/d.sql failed`)).toBe("stat failed") + }) + + it("recognizes extended-length Windows home paths", () => { + expect(mask(String.raw`\\?\C:\Users\Jane Doe does not exist`)).toBe(" does not exist") + expect(mask(String.raw`stat \\?\C:\Users\Jane Doe\models\a.sql failed`)).toBe("stat failed") + }) + + it("backslash-permissive run class never bleeds into following prose", () => { + expect(mask(String.raw`use /pattern/ with \d+ tokens`)).toBe(String.raw`use with \d+ tokens`) + expect(mask(String.raw`glob /opt/data/*.sql \n escaped`)).toBe(String.raw`glob \n escaped`) + }) + + it("widened run class stays linear on adversarial input", () => { + const t0 = performance.now() + mask("s3://b/k " + "w\\x ".repeat(1200)) + expect(performance.now() - t0).toBeLessThan(500) + }) +}) From 7d8678e6d5525c3090aaeb0be305ef935bb5188d Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 16:22:31 +0800 Subject: [PATCH 16/46] fix: double spaces in components, current-drive-rooted windows paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - space continuation units accept ' {1,2}' so a double space inside a spaced component keeps masking; 3+ spaces stay a column boundary so aligned log output is never consumed - windowsHome drive prefix is optional: current-drive-rooted homes (\Users\Jane Doe\...) get spaced-username protection - generic windows rule gains a single-backslash rooted opener, proven by two consecutive components of 2+ word characters — regex escapes (\d+\.\d+, \bword\b) and escape chains (\n\r\t) never qualify --- .../opencode/src/altimate/telemetry/index.ts | 14 ++++----- .../test/telemetry/mask-file-paths.test.ts | 29 +++++++++++++++++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 3685ac09a..6cf3401f1 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -83,17 +83,17 @@ const SEP_P = "\\/" const SEP_W = "[\\\\\\/]" const pmR = (sep: string) => (sep === SEP_P ? PM_R_P : PM_R) const pmSpan = (sep: string) => - "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "*" + sep + "|(?: " + pmR(sep) + "+)+" + sep + "|" + pmR(sep) + "*" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" -const pmChunks = (sep: string) => "(?:(?: " + pmR(sep) + "+)+" + sep + pmSpan(sep) + "*)*" + "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "*" + sep + "|(?: {1,2}" + pmR(sep) + "+)+" + sep + "|" + pmR(sep) + "*" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" +const pmChunks = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+)+" + sep + pmSpan(sep) + "*)*" // terminal dotted filename: up to four spaced words that END in an extension -const pmSpFile = (sep: string) => "(?:(?: " + pmR(sep) + "+){1,4}(?<=" + PM_EXT + "))?" -const PM_TERM_COND = "(?:(? "(?:(?: {1,2}" + pmR(sep) + "+){1,4}(?<=" + PM_EXT + "))?" +const PM_TERM_COND = "(?:(? pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), - windowsHome: new RegExp(PM_ANCHOR + "(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), - windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+\\\\))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), + windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:)?" + SEP_W + "Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), + windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_-]{2,}\\\\){2}))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), posix: new RegExp(PM_ANCHOR + "\\.{0,2}\\/(?:" + PM_R_P + "+\\/)+" + pmTail(SEP_P, PM_TERM_COND), "gu"), tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_COND), "gu"), diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index ac2eb6f0d..6dd1a5c41 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -382,3 +382,32 @@ describe("maskString paths — fleet round 6 (backslash components, extended-len expect(performance.now() - t0).toBeLessThan(500) }) }) + +describe("maskString paths — fleet round 7 (double spaces, current-drive-rooted windows)", () => { + it("continues across a double space inside a component", () => { + expect(mask("read /Users/Jane Doe/client/model.sql failed")).toBe("read failed") + }) + + it("three or more spaces stay a column boundary", () => { + expect(mask("read /opt/data/x.sql 404 NotFound")).toBe("read 404 NotFound") + }) + + it("masks current-drive-rooted windows paths, home and generic", () => { + expect(mask(String.raw`read \Users\Jane Doe\models\a.sql failed`)).toBe("read failed") + expect(mask(String.raw`open \inetpub\wwwroot\app\web.config denied`)).toBe("open denied") + }) + + it("rooted opener never eats regex or escape prose", () => { + // proof requires two >=2-word-char components: 1-char escape classes and + // quantifier-bearing runs both fail it + expect(mask(String.raw`expected pattern \d+\.\d+ but got x`)).toBe(String.raw`expected pattern \d+\.\d+ but got x`) + expect(mask(String.raw`escape chain \n\r\t here`)).toBe(String.raw`escape chain \n\r\t here`) + expect(mask(String.raw`match \bword\b boundary`)).toBe(String.raw`match \bword\b boundary`) + }) + + it("double-space continuation stays linear on adversarial input", () => { + const t0 = performance.now() + mask("/Users/x/y" + " zz".repeat(1200)) + expect(performance.now() - t0).toBeLessThan(500) + }) +}) From be0daf960056d6aeb843588ebc0f20513ae3f7e9 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 16:34:02 +0800 Subject: [PATCH 17/46] fix: strip ANSI sequences, drive-relative windows paths, quote delimiters - ANSI CSI sequences are stripped at the head of the chain: colored subprocess stderr no longer hides paths (or splits credentials) from every rule downstream - windows rules accept drive-relative forms (C:dir\file, C:Users\...) proven by a following backslash; colon prose (C:8/10) never matches - unmatched quote characters inside components join the separator- proven delimiter class; paired quoting (`/opt/x`, "/opt/y") keeps its closing quote because the pairing quote can't join the proof --- .../opencode/src/altimate/telemetry/index.ts | 9 +++++--- .../test/telemetry/mask-file-paths.test.ts | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 6cf3401f1..436cf16fa 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -83,7 +83,7 @@ const SEP_P = "\\/" const SEP_W = "[\\\\\\/]" const pmR = (sep: string) => (sep === SEP_P ? PM_R_P : PM_R) const pmSpan = (sep: string) => - "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "*" + sep + "|(?: {1,2}" + pmR(sep) + "+)+" + sep + "|" + pmR(sep) + "*" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" + "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>\"'`](?=" + pmR(sep) + "*" + sep + "|(?: {1,2}" + pmR(sep) + "+)+" + sep + "|" + pmR(sep) + "*" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" const pmChunks = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+)+" + sep + pmSpan(sep) + "*)*" // terminal dotted filename: up to four spaced words that END in an extension const pmSpFile = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+){1,4}(?<=" + PM_EXT + "))?" @@ -92,8 +92,8 @@ const PM_TERM_UNC = "(?:(? pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), - windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:)?" + SEP_W + "Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), - windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_-]{2,}\\\\){2}))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), + windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|" + SEP_W + ")Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), + windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+\\\\)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_-]{2,}\\\\){2}))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), posix: new RegExp(PM_ANCHOR + "\\.{0,2}\\/(?:" + PM_R_P + "+\\/)+" + pmTail(SEP_P, PM_TERM_COND), "gu"), tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_COND), "gu"), @@ -1420,6 +1420,9 @@ export namespace Telemetry { export function maskString(s: string): string { return s + // ANSI CSI sequences (colored subprocess stderr) would otherwise split + // tokens so neither credential nor path rules can see them + .replace(/\x1b\[[0-?]*[ -\/]*[@-~]/g, "") .replace(/sk-(?:ant-)?[A-Za-z0-9_-]{20,}/g, "sk-***") .replace(/Bearer\s+[A-Za-z0-9._-]{20,}/gi, "Bearer ***") // altimate_change start — mask filesystem paths in error text diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 6dd1a5c41..479930bbc 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -411,3 +411,24 @@ describe("maskString paths — fleet round 7 (double spaces, current-drive-roote expect(performance.now() - t0).toBeLessThan(500) }) }) + +describe("maskString paths — fleet round 8 (ANSI, drive-relative, quote delimiters)", () => { + it("strips ANSI CSI sequences before masking", () => { + expect(mask("\x1b[31m/Users/jdoe/client/a.sql\x1b[0m failed")).toBe(" failed") + // an ANSI-split credential must still be caught by the sk- rule + expect(mask("token sk-\x1b[31mabc12345678901234567890\x1b[0m end")).toBe("token sk-*** end") + }) + + it("masks drive-relative windows paths, generic and home", () => { + expect(mask(String.raw`read C:client-repo\models\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`read C:Users\Jane Doe\client\a.sql failed`)).toBe("read failed") + // colon-bearing prose without a backslash chain never matches + expect(mask("score C:8/10 fine")).toBe("score C:8/10 fine") + }) + + it("continues through quote characters a later separator proves", () => { + expect(mask(`read /Users/jdoe/client"repo/models/a.sql failed`)).toBe("read failed") + // paired inline-code backticks stay intact + expect(mask("run `dbt build` in `/opt/proj/app` now")).toBe("run `dbt build` in `` now") + }) +}) From f8939f18cd98cd796e59ce48eef7bcdbf7b045fa Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 16:42:23 +0800 Subject: [PATCH 18/46] fix: quote-pair safety, pipe anchors, OSC escape stripping - quotes split out of the shared proven-delimiter class: they require a non-empty run before the separator, so a closing quote directly before a slash stays a boundary and downstream quote pairing never shifts (an eaten quote could leak the next quoted value entirely) - pipe joins the anchor class: structured-log fields (ENOENT|/path) now mask; plain pipe prose has no path shape and never matches - escape stripping covers OSC sequences (hyperlink-wrapped paths), not just CSI colors --- .../opencode/src/altimate/telemetry/index.ts | 6 +++--- .../test/telemetry/mask-file-paths.test.ts | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 436cf16fa..313e2452b 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -71,7 +71,7 @@ const PM_R = "(?:[^\\s\\/\\\\'\"`]|'(?=[\\p{L}\\p{N}_]))" // slash-delimited variant: backslash is path content, not a separator const PM_R_P = "(?:[^\\s\\/'\"`]|'(?=[\\p{L}\\p{N}_]))" const PM_WORD = "(?:[\\p{L}\\p{M}\\p{N}_‘’-]|'(?=[\\p{L}\\p{N}_]))" -const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<])" +const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<|])" const PM_EXT = "\\.[A-Za-z0-9]{1,8}" // span char: path content incl. delimiters (, ; ) ] } >) that a later // separator — or an attached dotted terminal filename (;draft.sql) — @@ -83,7 +83,7 @@ const SEP_P = "\\/" const SEP_W = "[\\\\\\/]" const pmR = (sep: string) => (sep === SEP_P ? PM_R_P : PM_R) const pmSpan = (sep: string) => - "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>\"'`](?=" + pmR(sep) + "*" + sep + "|(?: {1,2}" + pmR(sep) + "+)+" + sep + "|" + pmR(sep) + "*" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" + "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "*" + sep + "|(?: {1,2}" + pmR(sep) + "+)+" + sep + "|" + pmR(sep) + "*" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?]))|[\"'`](?=" + pmR(sep) + "+" + sep + "|" + pmR(sep) + "+" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" const pmChunks = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+)+" + sep + pmSpan(sep) + "*)*" // terminal dotted filename: up to four spaced words that END in an extension const pmSpFile = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+){1,4}(?<=" + PM_EXT + "))?" @@ -1422,7 +1422,7 @@ export namespace Telemetry { return s // ANSI CSI sequences (colored subprocess stderr) would otherwise split // tokens so neither credential nor path rules can see them - .replace(/\x1b\[[0-?]*[ -\/]*[@-~]/g, "") + .replace(/\x1b(?:\[[0-?]*[ -\/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\))/g, "") .replace(/sk-(?:ant-)?[A-Za-z0-9_-]{20,}/g, "sk-***") .replace(/Bearer\s+[A-Za-z0-9._-]{20,}/gi, "Bearer ***") // altimate_change start — mask filesystem paths in error text diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 479930bbc..4e9d00dcf 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -432,3 +432,23 @@ describe("maskString paths — fleet round 8 (ANSI, drive-relative, quote delimi expect(mask("run `dbt build` in `/opt/proj/app` now")).toBe("run `dbt build` in `` now") }) }) + +describe("maskString paths — fleet round 9 (quote-pair safety, pipes, OSC)", () => { + it("a closing quote directly before a slash stays a boundary (pairing never shifts)", () => { + // the eaten quote used to shift pairing and leak the next quoted value + expect(mask(`read "/a/b"/c "secret data" end`)).toBe("read ?/c ? end") + // mid-component quotes (non-empty run before the separator) still mask + expect(mask(`read /Users/jdoe/client"repo/models/a.sql failed`)).toBe("read failed") + }) + + it("pipe is a path anchor but plain pipe prose never matches", () => { + expect(mask("ENOENT|/Users/jdoe/client/a.sql")).toBe("ENOENT|") + expect(mask("source|s3://customer-bucket/private/a.csv")).toBe("source|") + expect(mask("a|b or c|d plain prose")).toBe("a|b or c|d plain prose") + }) + + it("strips OSC hyperlink escapes so wrapped paths still mask", () => { + expect(mask("\x1b]8;;vscode://file//x\x1b\\/Users/jdoe/client/a.sql\x1b]8;;\x1b\\ failed")) + .toBe(" failed") + }) +}) From c963b712029f8eb82f1b80fb5630151c5a0e52dd Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 16:50:58 +0800 Subject: [PATCH 19/46] fix: spaced first components in dot-relative paths, drive-relative forward slashes - POSIX prefix components accept separator-proven spaced runs, so ./client repo/models/a.sql masks; the windows dot-relative proof gets the same treatment - the drive-relative proof requires a letter-first word run but either separator, so C:client-repo/models works while C:8/10 and e:10/20 ratios keep failing the proof --- .../opencode/src/altimate/telemetry/index.ts | 4 ++-- .../test/telemetry/mask-file-paths.test.ts | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 313e2452b..a87804947 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -93,9 +93,9 @@ const pmTail = (sep: string, term: string) => pmSpan(sep) + "*" + pmChunks(sep) const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|" + SEP_W + ")Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), - windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+\\\\)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_-]{2,}\\\\){2}))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), + windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=[\\p{L}_][\\p{L}\\p{N}_-]*" + SEP_W + ")|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_-]{2,}\\\\){2}))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), - posix: new RegExp(PM_ANCHOR + "\\.{0,2}\\/(?:" + PM_R_P + "+\\/)+" + pmTail(SEP_P, PM_TERM_COND), "gu"), + posix: new RegExp(PM_ANCHOR + "\\.{0,2}\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)+" + pmTail(SEP_P, PM_TERM_COND), "gu"), tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_COND), "gu"), } // altimate_change end diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 4e9d00dcf..4129ddf7c 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -452,3 +452,22 @@ describe("maskString paths — fleet round 9 (quote-pair safety, pipes, OSC)", ( .toBe(" failed") }) }) + +describe("maskString paths — fleet round 10 (spaced first components, drive-relative slashes)", () => { + it("masks spaced first components in dot-relative paths", () => { + expect(mask("read ./client repo/models/private.sql failed")).toBe("read failed") + expect(mask(String.raw`read .\client repo\models\a.sql failed`)).toBe("read failed") + }) + + it("accepts forward slashes in drive-relative paths, digit ratios never match", () => { + expect(mask("read C:client-repo/models/private.sql failed")).toBe("read failed") + expect(mask("score C:8/10 fine")).toBe("score C:8/10 fine") + expect(mask("rated e:10/20 shown")).toBe("rated e:10/20 shown") + }) + + it("spaced prefix components stay linear on adversarial input", () => { + const t0 = performance.now() + mask("./" + "a b ".repeat(1500)) + expect(performance.now() - t0).toBeLessThan(500) + }) +}) From 499e956aea56351fc210e643d12ea81b040e6720 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 16:58:16 +0800 Subject: [PATCH 20/46] fix: home-rule reach through spaced prefixes and UNC shares; drive-relative breadth - posixHome prefix components accept spaced runs, matching the generic rule, so '/mnt disk/c/Users/Jane Doe' gets home treatment - windowsHome accepts UNC and extended \\?\UNC\ prefixes before Users, so share-mounted homes keep spaced-username protection - drive-relative proof split by separator: a backslash proves any first component (spaced, dotted); forward slash keeps the letter-first class so digit ratios (C:8/10) never match --- .../opencode/src/altimate/telemetry/index.ts | 6 +++--- .../test/telemetry/mask-file-paths.test.ts | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index a87804947..48590b0d0 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -92,9 +92,9 @@ const PM_TERM_UNC = "(?:(? pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), - windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|" + SEP_W + ")Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), - windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=[\\p{L}_][\\p{L}\\p{N}_-]*" + SEP_W + ")|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_-]{2,}\\\\){2}))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), - posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), + windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|\\\\\\\\(?:\\?\\\\UNC\\\\)?(?:" + PM_R + "+(?: {1,2}" + PM_R + "+)*" + SEP_W + ")*|" + SEP_W + ")Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), + windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\|[\\p{L}_][\\p{L}\\p{N}_-]*\\/)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_-]{2,}\\\\){2}))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), + posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), posix: new RegExp(PM_ANCHOR + "\\.{0,2}\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)+" + pmTail(SEP_P, PM_TERM_COND), "gu"), tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_COND), "gu"), } diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 4129ddf7c..b311389d8 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -471,3 +471,24 @@ describe("maskString paths — fleet round 10 (spaced first components, drive-re expect(performance.now() - t0).toBeLessThan(500) }) }) + +describe("maskString paths — fleet round 11 (home-rule reach: spaced prefixes, UNC; drive-relative breadth)", () => { + it("home rules reach through spaced prefix components and UNC shares", () => { + expect(mask("stat /mnt disk/c/Users/Jane Doe does not exist")).toBe("stat does not exist") + expect(mask(String.raw`stat \\fileserver\Users\Jane Doe does not exist`)).toBe("stat does not exist") + expect(mask(String.raw`stat \\?\UNC\srv\share\Users\Jane Doe is gone`)).toBe("stat is gone") + }) + + it("drive-relative backslash proof accepts spaced and dotted first components", () => { + expect(mask(String.raw`read C:client repo\models\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`read C:.client\models\private.sql failed`)).toBe("read failed") + // slash-proven form keeps the letter-first guard: ratios never match + expect(mask("score C:8/10 fine")).toBe("score C:8/10 fine") + }) + + it("UNC home opener stays linear on adversarial input", () => { + const t0 = performance.now() + mask("\\\\" + "srv ".repeat(1500) + "x") + expect(performance.now() - t0).toBeLessThan(500) + }) +}) From 7eb83829eba3db3b319c3590ac6d460a3e0f0bd2 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 17:06:19 +0800 Subject: [PATCH 21/46] fix: rooted-proof breadth (spaced/symbol components), tilde home tail - the single-backslash rooted proof accepts spaced components and #/@ symbols (\Program Files\..., \client repo\..., \#client\...) while still rejecting every regex-metacharacter shape - tilde paths are home-rooted: they now use the unconditional home tail, so '~/client repo' and '~jane/Client Secret' mask their full spaced components instead of leaking the second word --- packages/opencode/src/altimate/telemetry/index.ts | 4 ++-- .../test/telemetry/mask-file-paths.test.ts | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 48590b0d0..1effe2b2d 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -93,10 +93,10 @@ const pmTail = (sep: string, term: string) => pmSpan(sep) + "*" + pmChunks(sep) const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|\\\\\\\\(?:\\?\\\\UNC\\\\)?(?:" + PM_R + "+(?: {1,2}" + PM_R + "+)*" + SEP_W + ")*|" + SEP_W + ")Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), - windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\|[\\p{L}_][\\p{L}\\p{N}_-]*\\/)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_-]{2,}\\\\){2}))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), + windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\|[\\p{L}_][\\p{L}\\p{N}_-]*\\/)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_#@-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@-]+)*\\\\){2}))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), posix: new RegExp(PM_ANCHOR + "\\.{0,2}\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)+" + pmTail(SEP_P, PM_TERM_COND), "gu"), - tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_COND), "gu"), + tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_UNC), "gu"), } // altimate_change end diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index b311389d8..5742058e0 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -492,3 +492,18 @@ describe("maskString paths — fleet round 11 (home-rule reach: spaced prefixes, expect(performance.now() - t0).toBeLessThan(500) }) }) + +describe("maskString paths — fleet round 12 (rooted proof breadth, tilde home tail)", () => { + it("rooted proof accepts spaced and symbol-bearing components", () => { + expect(mask(String.raw`open \Program Files\Altimate\secret.sql denied`)).toBe("open denied") + expect(mask(String.raw`read \client repo\models\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`read \#client\models\private.sql failed`)).toBe("read failed") + // regex-prose guards must keep failing the proof + expect(mask(String.raw`expected pattern \d+\.\d+ but got x`)).toBe(String.raw`expected pattern \d+\.\d+ but got x`) + }) + + it("tilde homes use the unconditional home tail", () => { + expect(mask("stat ~/client repo does not exist")).toBe("stat does not exist") + expect(mask("read ~jane/Client Secret now")).toBe("read now") + }) +}) From a5f79d809f18d7ffeb5a684dc9ec34a32eb9627c Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 17:17:00 +0800 Subject: [PATCH 22/46] fix: multi-word PII tails, proof punctuation, bounded delimiter scans - the unconditional home/cloud tail keeps consuming Capitalized words after its first word, so three-word usernames (Mary Jane Smith) and multi-word object keys mask completely; lowercase prose still costs exactly one word - rooted-proof component class accepts parens/dots/apostrophes (\Program Files (x86)\...) while regex escapes keep failing it - every delimiter-proof lookahead is bounded ({0,64} runs, {1,6} words), making per-delimiter work O(1): a 500-delimiter run went from ~380ms to ~1ms; components spanning >64 chars past a delimiter become documented boundaries --- .../opencode/src/altimate/telemetry/index.ts | 6 ++--- .../test/telemetry/mask-file-paths.test.ts | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 1effe2b2d..b7e491b1f 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -83,17 +83,17 @@ const SEP_P = "\\/" const SEP_W = "[\\\\\\/]" const pmR = (sep: string) => (sep === SEP_P ? PM_R_P : PM_R) const pmSpan = (sep: string) => - "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "*" + sep + "|(?: {1,2}" + pmR(sep) + "+)+" + sep + "|" + pmR(sep) + "*" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?]))|[\"'`](?=" + pmR(sep) + "+" + sep + "|" + pmR(sep) + "+" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" + "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "{0,64}" + sep + "|(?: {1,2}" + pmR(sep) + "{1,32}){1,6}" + sep + "|" + pmR(sep) + "{0,64}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?]))|[\"'`](?=" + pmR(sep) + "{1,64}" + sep + "|" + pmR(sep) + "{1,64}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" const pmChunks = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+)+" + sep + pmSpan(sep) + "*)*" // terminal dotted filename: up to four spaced words that END in an extension const pmSpFile = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+){1,4}(?<=" + PM_EXT + "))?" const PM_TERM_COND = "(?:(? pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|\\\\\\\\(?:\\?\\\\UNC\\\\)?(?:" + PM_R + "+(?: {1,2}" + PM_R + "+)*" + SEP_W + ")*|" + SEP_W + ")Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), - windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\|[\\p{L}_][\\p{L}\\p{N}_-]*\\/)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_#@-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@-]+)*\\\\){2}))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), + windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\|[\\p{L}_][\\p{L}\\p{N}_-]*\\/)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\){2}))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), posix: new RegExp(PM_ANCHOR + "\\.{0,2}\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)+" + pmTail(SEP_P, PM_TERM_COND), "gu"), tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_UNC), "gu"), diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 5742058e0..e09288d5a 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -507,3 +507,27 @@ describe("maskString paths — fleet round 12 (rooted proof breadth, tilde home expect(mask("read ~jane/Client Secret now")).toBe("read now") }) }) + +describe("maskString paths — fleet round 13 (multi-word PII tails, proof punctuation, bounded scans)", () => { + it("high-PII tail consumes capitalized word runs (middle names, multi-word keys)", () => { + expect(mask("/Users/Mary Jane Smith does not exist")).toBe(" does not exist") + expect(mask("C:\\Users\\Mary Jane Smith does not exist")).toBe(" does not exist") + expect(mask("s3://bucket/Client Top Secret does not exist")).toBe(" does not exist") + // lowercase prose still costs exactly one word + expect(mask("/Users/jdoe was not found on this system")).toBe(" not found on this system") + }) + + it("rooted proof accepts parenthesized/dotted components", () => { + expect(mask(String.raw`open \Program Files (x86)\Altimate\secret.sql denied`)).toBe("open denied") + expect(mask(String.raw`expected pattern \d+\.\d+ but got x`)).toBe(String.raw`expected pattern \d+\.\d+ but got x`) + }) + + it("delimiter runs are linear: bounded proof scans", () => { + let t0 = performance.now() + mask("C:\\a\\b" + ")".repeat(2000) + "\\c") + expect(performance.now() - t0).toBeLessThan(200) + t0 = performance.now() + mask(")a".repeat(2500) + "/x") + expect(performance.now() - t0).toBeLessThan(200) + }) +}) From 6a0dfd3b17a0ba68855f2f3c9b6c2bed8faefdd6 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 17:27:30 +0800 Subject: [PATCH 23/46] fix: proof bounds match the filesystem component limit; shallow path forms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - delimiter-proof scan bounds raised from 64 to 256 chars — the bound is now the filesystem's own component limit (255 bytes), not an arbitrary cap; per-delimiter work stays O(1) - rooted windows proof accepts one component plus a dotted terminal file (\client-repo\private.sql); word-boundary regex prose still fails it - dot-relative POSIX paths mask with a terminal file only (./secret.sql); extensionless ./x stays out as undecidable --- .../opencode/src/altimate/telemetry/index.ts | 6 ++--- .../test/telemetry/mask-file-paths.test.ts | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index b7e491b1f..ed2696a40 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -83,7 +83,7 @@ const SEP_P = "\\/" const SEP_W = "[\\\\\\/]" const pmR = (sep: string) => (sep === SEP_P ? PM_R_P : PM_R) const pmSpan = (sep: string) => - "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "{0,64}" + sep + "|(?: {1,2}" + pmR(sep) + "{1,32}){1,6}" + sep + "|" + pmR(sep) + "{0,64}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?]))|[\"'`](?=" + pmR(sep) + "{1,64}" + sep + "|" + pmR(sep) + "{1,64}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" + "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "{0,256}" + sep + "|(?: {1,2}" + pmR(sep) + "{1,64}){1,6}" + sep + "|" + pmR(sep) + "{0,256}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?]))|[\"'`](?=" + pmR(sep) + "{1,256}" + sep + "|" + pmR(sep) + "{1,256}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" const pmChunks = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+)+" + sep + pmSpan(sep) + "*)*" // terminal dotted filename: up to four spaced words that END in an extension const pmSpFile = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+){1,4}(?<=" + PM_EXT + "))?" @@ -93,9 +93,9 @@ const pmTail = (sep: string, term: string) => pmSpan(sep) + "*" + pmChunks(sep) const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|\\\\\\\\(?:\\?\\\\UNC\\\\)?(?:" + PM_R + "+(?: {1,2}" + PM_R + "+)*" + SEP_W + ")*|" + SEP_W + ")Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), - windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\|[\\p{L}_][\\p{L}\\p{N}_-]*\\/)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\){2}))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), + windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\|[\\p{L}_][\\p{L}\\p{N}_-]*\\/)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\){2}|[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\[^\\s\\\\]{1,256}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), - posix: new RegExp(PM_ANCHOR + "\\.{0,2}\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)+" + pmTail(SEP_P, PM_TERM_COND), "gu"), + posix: new RegExp(PM_ANCHOR + "(?:\\.{0,2}\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)+" + pmTail(SEP_P, PM_TERM_COND) + "|\\.{1,2}\\/" + pmSpan(SEP_P) + "+(?<=" + PM_EXT + ")(?=$|[\\s.,;:)\\]}!?]))", "gu"), tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_UNC), "gu"), } // altimate_change end diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index e09288d5a..17c72d103 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -531,3 +531,26 @@ describe("maskString paths — fleet round 13 (multi-word PII tails, proof punct expect(performance.now() - t0).toBeLessThan(200) }) }) + +describe("maskString paths — fleet round 14 (fs-limit bounds, shallow forms)", () => { + it("proof scans reach the filesystem component limit (255)", () => { + expect(mask("/Users/jdoe/client)" + "a".repeat(65) + "/models/private.sql leaked")).toBe(" leaked") + // delimiter runs stay linear at the raised bound + const t0 = performance.now() + mask("C:\\a\\b" + ")".repeat(2000) + "\\c") + expect(performance.now() - t0).toBeLessThan(300) + }) + + it("shallow rooted windows paths mask via the dotted-terminal proof", () => { + expect(mask(String.raw`read \client-repo\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`open \Program Files\app.exe denied`)).toBe("open denied") + expect(mask(String.raw`match \bword\b boundary`)).toBe(String.raw`match \bword\b boundary`) + }) + + it("terminal-only dot-relative paths mask when extension-bearing", () => { + expect(mask("read ./customer_secret.sql failed")).toBe("read failed") + expect(mask("read ../customer_secret.sql now")).toBe("read now") + // extensionless ./x stays out — undecidable against prose tokens + expect(mask("see ./x plain token")).toBe("see ./x plain token") + }) +}) From 43b5d6841f9583c2e30d5df90289d10a9452637b Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 17:35:12 +0800 Subject: [PATCH 24/46] fix: spaced shallow terminals, name particles in PII tails, extensionless rooted proof MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - shallow dot-relative branch reuses the spaced-filename fragment, so ./Q4 final report.sql masks whole - PII-tail continuation words may be capitalized, caseless-script (CJK), or lowercase name particles (van/der/de/la/bin/al/...), so 'Mary van der Berg' and CJK names mask completely; ordinary lowercase prose still costs exactly one word - rooted proof accepts a spaced first component with an extensionless terminal (\Program Files\Altimate) — the space is the evidence that separates it from escape prose, which never has one --- .../opencode/src/altimate/telemetry/index.ts | 7 ++++--- .../test/telemetry/mask-file-paths.test.ts | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index ed2696a40..0d7e1e93e 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -88,14 +88,15 @@ const pmChunks = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+)+" + sep + pmSp // terminal dotted filename: up to four spaced words that END in an extension const pmSpFile = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+){1,4}(?<=" + PM_EXT + "))?" const PM_TERM_COND = "(?:(? pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|\\\\\\\\(?:\\?\\\\UNC\\\\)?(?:" + PM_R + "+(?: {1,2}" + PM_R + "+)*" + SEP_W + ")*|" + SEP_W + ")Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), - windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\|[\\p{L}_][\\p{L}\\p{N}_-]*\\/)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\){2}|[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\[^\\s\\\\]{1,256}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), + windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\|[\\p{L}_][\\p{L}\\p{N}_-]*\\/)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\){2}|[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\[^\\s\\\\]{1,256}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])|[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)+\\\\[\\p{L}\\p{N}]))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), - posix: new RegExp(PM_ANCHOR + "(?:\\.{0,2}\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)+" + pmTail(SEP_P, PM_TERM_COND) + "|\\.{1,2}\\/" + pmSpan(SEP_P) + "+(?<=" + PM_EXT + ")(?=$|[\\s.,;:)\\]}!?]))", "gu"), + posix: new RegExp(PM_ANCHOR + "(?:\\.{0,2}\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)+" + pmTail(SEP_P, PM_TERM_COND) + "|\\.{1,2}\\/" + pmSpan(SEP_P) + "+" + pmSpFile(SEP_P) + "(?<=" + PM_EXT + ")(?=$|[\\s.,;:)\\]}!?]))", "gu"), tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_UNC), "gu"), } // altimate_change end diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 17c72d103..a86e3a462 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -554,3 +554,21 @@ describe("maskString paths — fleet round 14 (fs-limit bounds, shallow forms)", expect(mask("see ./x plain token")).toBe("see ./x plain token") }) }) + +describe("maskString paths — fleet round 15 (spaced shallow terminals, name particles, extensionless rooted)", () => { + it("shallow dot-relative accepts spaced dotted terminals", () => { + expect(mask("read ./Q4 final report.sql failed")).toBe("read failed") + }) + + it("PII tails cross lowercase name particles and caseless scripts", () => { + expect(mask("/Users/Mary van der Berg does not exist")).toBe(" does not exist") + expect(mask("/Users/李 小 明 does not exist")).toBe(" does not exist") + // ordinary lowercase prose still costs exactly one word + expect(mask("/Users/jdoe was not found on this system")).toBe(" not found on this system") + }) + + it("rooted proof accepts spaced-first-component extensionless paths", () => { + expect(mask(String.raw`open \Program Files\Altimate denied`)).toBe("open ") + expect(mask(String.raw`match \bword\b boundary`)).toBe(String.raw`match \bword\b boundary`) + }) +}) From 454cb281cf58c0259936ddb0ffb728b7b7c33b16 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 17:42:41 +0800 Subject: [PATCH 25/46] =?UTF-8?q?fix:=20remove=20the=20i=20flag=20from=20h?= =?UTF-8?q?ome/cloud=20rules=20=E2=80=94=20case-fold=20breaks=20the=20tail?= =?UTF-8?q?=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Under /iu, spec-conformant engines case-fold property escapes, so the capitalized-word gate in PM_TERM_UNC would match ANY word and consume entire diagnostic suffixes (V8 folds \p{Lu}; JSC does not — the suite only passed on Bun by grace of that divergence). The i flag is gone; the case-insensitive coverage it provided (users/home roots, scheme names, UNC marker) is hand-expanded per letter via pmCI(), keeping behavior identical on every engine. --- packages/opencode/src/altimate/telemetry/index.ts | 11 ++++++++--- .../opencode/test/telemetry/mask-file-paths.test.ts | 13 +++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 0d7e1e93e..4d9a909c5 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -81,6 +81,11 @@ const PM_EXT = "\\.[A-Za-z0-9]{1,8}" // banned here). const SEP_P = "\\/" const SEP_W = "[\\\\\\/]" +// per-letter case expansion — used instead of the i flag on home/cloud +// rules: under /iu, conformant engines case-fold \p{Lu}, which would turn +// the capitalized-tail gate into "match any word" (V8 folds; JSC does not — +// never rely on the divergence) +const pmCI = (w: string) => w.split("").map((c) => ("[" + c + c.toUpperCase() + "]")).join("") const pmR = (sep: string) => (sep === SEP_P ? PM_R_P : PM_R) const pmSpan = (sep: string) => "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "{0,256}" + sep + "|(?: {1,2}" + pmR(sep) + "{1,64}){1,6}" + sep + "|" + pmR(sep) + "{0,256}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?]))|[\"'`](?=" + pmR(sep) + "{1,256}" + sep + "|" + pmR(sep) + "{1,256}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" @@ -92,10 +97,10 @@ const PM_TERM_UNC = "(?:(? pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term const PATH_RULES = { - cloud: new RegExp(PM_ANCHOR + "(?:(?:gs|s3[an]?|abfss?|wasbs?|adl|dbfs|hdfs):\\/\\/|file:\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "giu"), - windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|\\\\\\\\(?:\\?\\\\UNC\\\\)?(?:" + PM_R + "+(?: {1,2}" + PM_R + "+)*" + SEP_W + ")*|" + SEP_W + ")Users" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "giu"), + cloud: new RegExp(PM_ANCHOR + "(?:(?:" + [pmCI("gs"), pmCI("s3") + "[anAN]?", pmCI("abfs") + "[sS]?", pmCI("wasb") + "[sS]?", pmCI("adl"), pmCI("dbfs"), pmCI("hdfs")].join("|") + "):\\/\\/|" + pmCI("file") + ":\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "gu"), + windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?(?:" + PM_R + "+(?: {1,2}" + PM_R + "+)*" + SEP_W + ")*|" + SEP_W + ")" + pmCI("users") + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "gu"), windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\|[\\p{L}_][\\p{L}\\p{N}_-]*\\/)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\){2}|[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\[^\\s\\\\]{1,256}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])|[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)+\\\\[\\p{L}\\p{N}]))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), - posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)*(?:Users|home)\\/" + pmTail(SEP_P, PM_TERM_UNC), "giu"), + posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)*(?:" + pmCI("users") + "|" + pmCI("home") + ")\\/" + pmTail(SEP_P, PM_TERM_UNC), "gu"), posix: new RegExp(PM_ANCHOR + "(?:\\.{0,2}\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)+" + pmTail(SEP_P, PM_TERM_COND) + "|\\.{1,2}\\/" + pmSpan(SEP_P) + "+" + pmSpFile(SEP_P) + "(?<=" + PM_EXT + ")(?=$|[\\s.,;:)\\]}!?]))", "gu"), tilde: new RegExp(PM_ANCHOR + "~[\\p{L}\\p{M}\\p{N}_.-]*\\/" + pmTail(SEP_P, PM_TERM_UNC), "gu"), } diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index a86e3a462..b9b2f4981 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -572,3 +572,16 @@ describe("maskString paths — fleet round 15 (spaced shallow terminals, name pa expect(mask(String.raw`match \bword\b boundary`)).toBe(String.raw`match \bword\b boundary`) }) }) + +describe("maskString paths — fleet round 16 (case folding out of the tail gate)", () => { + it("home/cloud rules are engine-independent: no i flag, hand-expanded case", () => { + // lowercase prose after a home path costs exactly one word — this is the + // assertion that silently broke under V8's conformant /iu folding + expect(mask("/Users/jdoe was not found on this system")).toBe(" not found on this system") + // the case-insensitive coverage the i flag used to provide still holds + expect(mask("stat c:\\users\\jane doe missing")).toBe("stat missing") + expect(mask("check /users/jane went missing")).toBe("check missing") + expect(mask("read S3://Bucket/Key now")).toBe("read ") + expect(mask(String.raw`stat \\?\unc\srv\share\Users\Jane Doe gone`)).toBe("stat gone") + }) +}) From 78f92a4e683bbd2f7257629a60571c444dbfe938 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 17:48:41 +0800 Subject: [PATCH 26/46] fix: legacy Documents and Settings home roots, 14-char extension window - the legacy Windows profile root joins Users in windowsHome, so 'C:\Documents and Settings\Jane Doe' gets the high-PII tail - PM_EXT window raised 8 -> 14 chars (.properties, .configuration are real extensions); both proof and prose-suppression directions covered by tests --- packages/opencode/src/altimate/telemetry/index.ts | 4 ++-- .../test/telemetry/mask-file-paths.test.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 4d9a909c5..cc41fbe48 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -72,7 +72,7 @@ const PM_R = "(?:[^\\s\\/\\\\'\"`]|'(?=[\\p{L}\\p{N}_]))" const PM_R_P = "(?:[^\\s\\/'\"`]|'(?=[\\p{L}\\p{N}_]))" const PM_WORD = "(?:[\\p{L}\\p{M}\\p{N}_‘’-]|'(?=[\\p{L}\\p{N}_]))" const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<|])" -const PM_EXT = "\\.[A-Za-z0-9]{1,8}" +const PM_EXT = "\\.[A-Za-z0-9]{1,14}" // span char: path content incl. delimiters (, ; ) ] } >) that a later // separator — or an attached dotted terminal filename (;draft.sql) — // proves is path content — multi-word spaced runs allowed, all @@ -98,7 +98,7 @@ const PM_TERM_UNC = const pmTail = (sep: string, term: string) => pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:" + [pmCI("gs"), pmCI("s3") + "[anAN]?", pmCI("abfs") + "[sS]?", pmCI("wasb") + "[sS]?", pmCI("adl"), pmCI("dbfs"), pmCI("hdfs")].join("|") + "):\\/\\/|" + pmCI("file") + ":\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "gu"), - windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?(?:" + PM_R + "+(?: {1,2}" + PM_R + "+)*" + SEP_W + ")*|" + SEP_W + ")" + pmCI("users") + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "gu"), + windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?(?:" + PM_R + "+(?: {1,2}" + PM_R + "+)*" + SEP_W + ")*|" + SEP_W + ")(?:" + pmCI("users") + "|" + pmCI("documents") + " " + pmCI("and") + " " + pmCI("settings") + ")" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "gu"), windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\|[\\p{L}_][\\p{L}\\p{N}_-]*\\/)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\){2}|[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\[^\\s\\\\]{1,256}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])|[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)+\\\\[\\p{L}\\p{N}]))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)*(?:" + pmCI("users") + "|" + pmCI("home") + ")\\/" + pmTail(SEP_P, PM_TERM_UNC), "gu"), posix: new RegExp(PM_ANCHOR + "(?:\\.{0,2}\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)+" + pmTail(SEP_P, PM_TERM_COND) + "|\\.{1,2}\\/" + pmSpan(SEP_P) + "+" + pmSpFile(SEP_P) + "(?<=" + PM_EXT + ")(?=$|[\\s.,;:)\\]}!?]))", "gu"), diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index b9b2f4981..5510eef5c 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -585,3 +585,17 @@ describe("maskString paths — fleet round 16 (case folding out of the tail gate expect(mask(String.raw`stat \\?\unc\srv\share\Users\Jane Doe gone`)).toBe("stat gone") }) }) + +describe("maskString paths — fleet round 17 (legacy home roots, longer extensions)", () => { + it("legacy Documents and Settings roots get home treatment", () => { + expect(mask(String.raw`stat C:\Documents and Settings\Jane Doe does not exist`)) + .toBe("stat does not exist") + }) + + it("extensions up to 14 chars count as proof and as prose guards", () => { + expect(mask("read ./customer_secret.properties failed")).toBe("read failed") + expect(mask("read ../private.configuration now")).toBe("read now") + // the suppression direction: prose after a long-extension file survives + expect(mask("/Users/jdoe/app.properties was deleted")).toBe(" was deleted") + }) +}) From adce06189fb29b0a6d310f7ed6cb44d174e90103 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 17:56:38 +0800 Subject: [PATCH 27/46] fix: delimiter proofs accept mixed unspaced+spaced runs The unspaced and space-first proof branches are unified into one shape (initial run + up to six spaced runs), so ')repo name/' proves like ')repo/' and ') name/' already did. Quote proofs keep their non-empty initial-run requirement, preserving quote-pair safety. --- .../opencode/src/altimate/telemetry/index.ts | 2 +- .../test/telemetry/mask-file-paths.test.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index cc41fbe48..0840a5eb2 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -88,7 +88,7 @@ const SEP_W = "[\\\\\\/]" const pmCI = (w: string) => w.split("").map((c) => ("[" + c + c.toUpperCase() + "]")).join("") const pmR = (sep: string) => (sep === SEP_P ? PM_R_P : PM_R) const pmSpan = (sep: string) => - "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "{0,256}" + sep + "|(?: {1,2}" + pmR(sep) + "{1,64}){1,6}" + sep + "|" + pmR(sep) + "{0,256}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?]))|[\"'`](?=" + pmR(sep) + "{1,256}" + sep + "|" + pmR(sep) + "{1,256}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))" + "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "{0,256}(?: {1,2}" + pmR(sep) + "{1,64}){0,6}(?:" + sep + "|" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))|[\"'`](?=" + pmR(sep) + "{1,256}(?: {1,2}" + pmR(sep) + "{1,64}){0,6}(?:" + sep + "|" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?]))))" const pmChunks = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+)+" + sep + pmSpan(sep) + "*)*" // terminal dotted filename: up to four spaced words that END in an extension const pmSpFile = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+){1,4}(?<=" + PM_EXT + "))?" diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 5510eef5c..451851189 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -599,3 +599,19 @@ describe("maskString paths — fleet round 17 (legacy home roots, longer extensi expect(mask("/Users/jdoe/app.properties was deleted")).toBe(" was deleted") }) }) + +describe("maskString paths — fleet round 18 (mixed spaced runs in delimiter proofs)", () => { + it("a delimiter proof may combine an initial run with spaced runs", () => { + expect(mask("/Users/jdoe/client)repo name/models/private.sql failed")).toBe(" failed") + // all four boundary controls hold + expect(mask("failed (from /opt/dbt/bin/dbt)")).toBe("failed (from )") + expect(mask(`read "/a/b"/c "secret data" end`)).toBe("read ?/c ? end") + expect(mask("cd /Users/jdoe/proj;ls failed")).toBe("cd ;ls failed") + }) + + it("unified proof stays linear", () => { + const t0 = performance.now() + mask("C:\\a\\b" + ")".repeat(2000) + "\\c") + expect(performance.now() - t0).toBeLessThan(300) + }) +}) From 96dbe0e4969df8102c4f2401495b10d99294636d Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 18:03:29 +0800 Subject: [PATCH 28/46] fix: shell redirects anchor paths, single-component absolute files mask - > joins the anchor class, so >/Users/... and 2>/home/... redirect targets mask in bash tool args - the extension-terminal proof extends to bare absolute files (/customer_secret.sql), guarded against URL interiors: scheme colons and protocol-relative // are excluded from the bare form --- .../opencode/src/altimate/telemetry/index.ts | 4 ++-- .../test/telemetry/mask-file-paths.test.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 0840a5eb2..74286dff2 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -71,7 +71,7 @@ const PM_R = "(?:[^\\s\\/\\\\'\"`]|'(?=[\\p{L}\\p{N}_]))" // slash-delimited variant: backslash is path content, not a separator const PM_R_P = "(?:[^\\s\\/'\"`]|'(?=[\\p{L}\\p{N}_]))" const PM_WORD = "(?:[\\p{L}\\p{M}\\p{N}_‘’-]|'(?=[\\p{L}\\p{N}_]))" -const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<|])" +const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<|>])" const PM_EXT = "\\.[A-Za-z0-9]{1,14}" // span char: path content incl. delimiters (, ; ) ] } >) that a later // separator — or an attached dotted terminal filename (;draft.sql) — @@ -101,7 +101,7 @@ const PATH_RULES = { windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?(?:" + PM_R + "+(?: {1,2}" + PM_R + "+)*" + SEP_W + ")*|" + SEP_W + ")(?:" + pmCI("users") + "|" + pmCI("documents") + " " + pmCI("and") + " " + pmCI("settings") + ")" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "gu"), windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\|[\\p{L}_][\\p{L}\\p{N}_-]*\\/)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\){2}|[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\[^\\s\\\\]{1,256}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])|[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)+\\\\[\\p{L}\\p{N}]))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), posixHome: new RegExp(PM_ANCHOR + "\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)*(?:" + pmCI("users") + "|" + pmCI("home") + ")\\/" + pmTail(SEP_P, PM_TERM_UNC), "gu"), - posix: new RegExp(PM_ANCHOR + "(?:\\.{0,2}\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)+" + pmTail(SEP_P, PM_TERM_COND) + "|\\.{1,2}\\/" + pmSpan(SEP_P) + "+" + pmSpFile(SEP_P) + "(?<=" + PM_EXT + ")(?=$|[\\s.,;:)\\]}!?]))", "gu"), + posix: new RegExp(PM_ANCHOR + "(?:\\.{0,2}\\/(?:" + PM_R_P + "+(?: {1,2}" + PM_R_P + "+)*\\/)+" + pmTail(SEP_P, PM_TERM_COND) + "|(?:\\.{1,2}\\/|(? { + it("shell redirection operators anchor paths", () => { + expect(mask("echo x >/Users/jdoe/client/private.log failed")).toBe("echo x > failed") + expect(mask("cmd 2>/home/jdoe/output.log crashed")).toBe("cmd 2> crashed") + expect(mask("if a > b then c")).toBe("if a > b then c") + }) + + it("single-component absolute files mask; URL interiors never do", () => { + expect(mask("read /customer_secret.sql failed")).toBe("read failed") + // scheme colons and protocol-relative slashes are excluded from the + // shallow-absolute form — URL pins elsewhere in this suite stay green + expect(mask("see https://community.snowflake.com/s/ip-not-allowed for details")) + .toBe("see https://community.snowflake.com/s/ip-not-allowed for details") + }) +}) From 7565405b689eda0bbf1b64fb801cb917f12da980 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 18:09:34 +0800 Subject: [PATCH 29/46] fix: closing delimiters anchor paths; forward-slash UNC with dotless servers - ] and ) join the anchor class for structured-stderr shapes like [ENOENT]/path; math and index prose has no path shape - //server/share/... masks via a windows-rule opener guarded two ways: scheme-colon lookbehind (never fires inside https://) and a dotless first component (dotted hosts are protocol-relative URLs, preserved) --- packages/opencode/src/altimate/telemetry/index.ts | 4 ++-- .../test/telemetry/mask-file-paths.test.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 74286dff2..764b1ae5b 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -71,7 +71,7 @@ const PM_R = "(?:[^\\s\\/\\\\'\"`]|'(?=[\\p{L}\\p{N}_]))" // slash-delimited variant: backslash is path content, not a separator const PM_R_P = "(?:[^\\s\\/'\"`]|'(?=[\\p{L}\\p{N}_]))" const PM_WORD = "(?:[\\p{L}\\p{M}\\p{N}_‘’-]|'(?=[\\p{L}\\p{N}_]))" -const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<|>])" +const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<|>)\\]])" const PM_EXT = "\\.[A-Za-z0-9]{1,14}" // span char: path content incl. delimiters (, ; ) ] } >) that a later // separator — or an attached dotted terminal filename (;draft.sql) — @@ -99,7 +99,7 @@ const pmTail = (sep: string, term: string) => pmSpan(sep) + "*" + pmChunks(sep) const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:" + [pmCI("gs"), pmCI("s3") + "[anAN]?", pmCI("abfs") + "[sS]?", pmCI("wasb") + "[sS]?", pmCI("adl"), pmCI("dbfs"), pmCI("hdfs")].join("|") + "):\\/\\/|" + pmCI("file") + ":\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "gu"), windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?(?:" + PM_R + "+(?: {1,2}" + PM_R + "+)*" + SEP_W + ")*|" + SEP_W + ")(?:" + pmCI("users") + "|" + pmCI("documents") + " " + pmCI("and") + " " + pmCI("settings") + ")" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "gu"), - windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|[A-Za-z]:(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\|[\\p{L}_][\\p{L}\\p{N}_-]*\\/)|\\\\\\\\|\\.{1,2}\\\\(?=" + PM_R + "+(?: {1,2}" + PM_R + "+)*\\\\)|\\\\(?=(?:[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\){2}|[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)*\\\\[^\\s\\\\]{1,256}" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])|[\\p{L}\\p{N}_#@().'-]{2,}(?: {1,2}[\\p{L}\\p{N}_#@().'-]+)+\\\\[\\p{L}\\p{N}]))" + pmSpan(SEP_W) + "+" + pmChunks(SEP_W) + pmSpFile(SEP_W) + PM_TERM_COND, "gu"), + windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|(? { + it("closing delimiters anchor paths in structured stderr", () => { + expect(mask("[ENOENT]/Users/jdoe/client/a.sql")).toBe("[ENOENT]") + expect(mask("error)/Users/jdoe/client/a.sql")).toBe("error)") + expect(mask("math (a+b)/(c+d) here")).toBe("math (a+b)/(c+d) here") + }) + + it("forward-slash UNC masks; dotted hosts stay protocol-relative URLs", () => { + expect(mask("open //fileserver/client-share/private.sql failed")).toBe("open failed") + // dotless-server discriminator: dotted first components are URL hosts + expect(mask("fetch //cdn.example.com/lib.js failed")).toBe("fetch //cdn.example.com/lib.js failed") + }) +}) From b75d6188131b9e2db14b2ea6ac5567b7463a776a Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 18:12:51 +0800 Subject: [PATCH 30/46] fix: colon-anchored bare absolute files (ENOENT:/private.sql) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scheme-colon lookbehind on the shallow bare-absolute branch was redundant — the protocol-relative (?!/) guard alone protects every URL pin — and it wrongly blocked errno-style colon anchors. --- packages/opencode/src/altimate/telemetry/index.ts | 2 +- packages/opencode/test/telemetry/mask-file-paths.test.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 764b1ae5b..55af8b9e8 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -101,7 +101,7 @@ const PATH_RULES = { windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?(?:" + PM_R + "+(?: {1,2}" + PM_R + "+)*" + SEP_W + ")*|" + SEP_W + ")(?:" + pmCI("users") + "|" + pmCI("documents") + " " + pmCI("and") + " " + pmCI("settings") + ")" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "gu"), windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|(? { + it("errno-style colon prefixes anchor single-component files", () => { + expect(mask("ENOENT:/private.sql failed")).toBe("ENOENT: failed") + // the protocol-relative guard alone protects URLs — full pin set stays green + expect(mask("fetch //cdn.example.com/lib.js failed")).toBe("fetch //cdn.example.com/lib.js failed") + }) +}) From 77072709f8ed2ee407f4c43837d138d1ed61b8ef Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 18:14:29 +0800 Subject: [PATCH 31/46] fix: forward-slash UNC homes get the high-PII tail The //server/share opener (dotless-server + scheme-colon guarded) joins windowsHome's alternation, so //fileserver/share/Users/Jane Doe gets unconditional home-tail masking instead of generic termination. --- packages/opencode/src/altimate/telemetry/index.ts | 2 +- packages/opencode/test/telemetry/mask-file-paths.test.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 55af8b9e8..dc8ec262a 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -98,7 +98,7 @@ const PM_TERM_UNC = const pmTail = (sep: string, term: string) => pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:" + [pmCI("gs"), pmCI("s3") + "[anAN]?", pmCI("abfs") + "[sS]?", pmCI("wasb") + "[sS]?", pmCI("adl"), pmCI("dbfs"), pmCI("hdfs")].join("|") + "):\\/\\/|" + pmCI("file") + ":\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "gu"), - windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?(?:" + PM_R + "+(?: {1,2}" + PM_R + "+)*" + SEP_W + ")*|" + SEP_W + ")(?:" + pmCI("users") + "|" + pmCI("documents") + " " + pmCI("and") + " " + pmCI("settings") + ")" + SEP_W + pmTail(SEP_W, PM_TERM_UNC), "gu"), + windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|(?:\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?|(? { + it("forward-slash UNC homes get the high-PII tail", () => { + expect(mask("stat //fileserver/share/Users/Jane Doe does not exist")) + .toBe("stat does not exist") + expect(mask("fetch //cdn.example.com/lib.js failed")).toBe("fetch //cdn.example.com/lib.js failed") + }) +}) From af81386f9b133dde2088e102bbfde7195520773c Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 18:21:15 +0800 Subject: [PATCH 32/46] fix: NBSP-bearing components, hyphenated extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - continuation units use PM_SP (any horizontal space incl. NBSP/Zs) so 'Jane Doe' components mask; tabs/newlines remain field boundaries - PM_EXT accepts interior hyphens (.code-workspace), last char must be alphanumeric --- .../opencode/src/altimate/telemetry/index.ts | 21 ++++++++++--------- .../test/telemetry/mask-file-paths.test.ts | 12 +++++++++++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index dc8ec262a..2428dd9b0 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -72,7 +72,8 @@ const PM_R = "(?:[^\\s\\/\\\\'\"`]|'(?=[\\p{L}\\p{N}_]))" const PM_R_P = "(?:[^\\s\\/'\"`]|'(?=[\\p{L}\\p{N}_]))" const PM_WORD = "(?:[\\p{L}\\p{M}\\p{N}_‘’-]|'(?=[\\p{L}\\p{N}_]))" const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<|>)\\]])" -const PM_EXT = "\\.[A-Za-z0-9]{1,14}" +const PM_SP = "[^\\S\\t\\n\\r\\v\\f]" +const PM_EXT = "\\.[A-Za-z0-9-]{0,13}[A-Za-z0-9]" // span char: path content incl. delimiters (, ; ) ] } >) that a later // separator — or an attached dotted terminal filename (;draft.sql) — // proves is path content — multi-word spaced runs allowed, all @@ -88,20 +89,20 @@ const SEP_W = "[\\\\\\/]" const pmCI = (w: string) => w.split("").map((c) => ("[" + c + c.toUpperCase() + "]")).join("") const pmR = (sep: string) => (sep === SEP_P ? PM_R_P : PM_R) const pmSpan = (sep: string) => - "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "{0,256}(?: {1,2}" + pmR(sep) + "{1,64}){0,6}(?:" + sep + "|" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))|[\"'`](?=" + pmR(sep) + "{1,256}(?: {1,2}" + pmR(sep) + "{1,64}){0,6}(?:" + sep + "|" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?]))))" -const pmChunks = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+)+" + sep + pmSpan(sep) + "*)*" + "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "{0,256}(?:" + PM_SP + "{1,2}" + pmR(sep) + "{1,64}){0,6}(?:" + sep + "|" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))|[\"'`](?=" + pmR(sep) + "{1,256}(?:" + PM_SP + "{1,2}" + pmR(sep) + "{1,64}){0,6}(?:" + sep + "|" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?]))))" +const pmChunks = (sep: string) => "(?:(?:" + PM_SP + "{1,2}" + pmR(sep) + "+)+" + sep + pmSpan(sep) + "*)*" // terminal dotted filename: up to four spaced words that END in an extension -const pmSpFile = (sep: string) => "(?:(?: {1,2}" + pmR(sep) + "+){1,4}(?<=" + PM_EXT + "))?" -const PM_TERM_COND = "(?:(? "(?:(?:" + PM_SP + "{1,2}" + pmR(sep) + "+){1,4}(?<=" + PM_EXT + "))?" +const PM_TERM_COND = "(?:(? pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:" + [pmCI("gs"), pmCI("s3") + "[anAN]?", pmCI("abfs") + "[sS]?", pmCI("wasb") + "[sS]?", pmCI("adl"), pmCI("dbfs"), pmCI("hdfs")].join("|") + "):\\/\\/|" + pmCI("file") + ":\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "gu"), - windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|(?:\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?|(? expect(mask("fetch //cdn.example.com/lib.js failed")).toBe("fetch //cdn.example.com/lib.js failed") }) }) + +describe("maskString paths — fleet round 23 (NBSP components, hyphenated extensions)", () => { + it("nonbreaking spaces continue components; tabs stay boundaries", () => { + expect(mask("read /Users/Jane Doe/client/model.sql failed")).toBe("read failed") + expect(mask("/Users/jdoe/client\tsecret/models/a.sql")).toBe(" secret/models/a.sql") + }) + + it("hyphenated extensions prove shallow paths", () => { + expect(mask("read ./client.code-workspace failed")).toBe("read failed") + expect(mask(String.raw`open \client\private.code-workspace denied`)).toBe("open denied") + }) +}) From 1e1e2a32b716e4b7b0a4c39616ba02d4fa196013 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 18:26:53 +0800 Subject: [PATCH 33/46] fix: PowerShell backslash tilde paths The tilde opener accepts a backslash separator guarded by a 2+ word-char component (so regex prose like ~\d+ never matches), and the tail uses the windows separator class for mixed continuation. --- packages/opencode/src/altimate/telemetry/index.ts | 2 +- packages/opencode/test/telemetry/mask-file-paths.test.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 2428dd9b0..ddedd390c 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -103,7 +103,7 @@ const PATH_RULES = { windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|(? denied") }) }) + +describe("maskString paths — fleet round 24 (PowerShell tilde paths)", () => { + it("backslash tilde paths mask; regex tilde prose never does", () => { + expect(mask(String.raw`read ~\client-repo\models\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`read ~jane\Client Secret\a.sql now`)).toBe("read now") + expect(mask(String.raw`regex ~\d+ used here`)).toBe(String.raw`regex ~\d+ used here`) + expect(mask("approx ~5 items left")).toBe("approx ~5 items left") + }) +}) From 6bdbd1b1e15fa2939b03a7daaabd3540f0fadb13 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 18:33:11 +0800 Subject: [PATCH 34/46] fix: dotfile tilde paths, letter-bearing drive proofs, NBSP particles - tilde backslash guard accepts dotfile-shaped first components (~\.config\...); escaped-dot regex prose still fails it - the forward-slash drive-relative proof requires only a letter somewhere in the first component (C:.client/, C:#client/, C:client repo/ mask; C:8/10 and C:8.5/10 ratios never can) - the name-particle lookahead uses the shared horizontal-space fragment, so particles work across NBSP --- .../opencode/src/altimate/telemetry/index.ts | 6 +++--- .../test/telemetry/mask-file-paths.test.ts | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index ddedd390c..4c483ac1a 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -95,15 +95,15 @@ const pmChunks = (sep: string) => "(?:(?:" + PM_SP + "{1,2}" + pmR(sep) + "+)+" const pmSpFile = (sep: string) => "(?:(?:" + PM_SP + "{1,2}" + pmR(sep) + "+){1,4}(?<=" + PM_EXT + "))?" const PM_TERM_COND = "(?:(? pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:" + [pmCI("gs"), pmCI("s3") + "[anAN]?", pmCI("abfs") + "[sS]?", pmCI("wasb") + "[sS]?", pmCI("adl"), pmCI("dbfs"), pmCI("hdfs")].join("|") + "):\\/\\/|" + pmCI("file") + ":\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "gu"), windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|(?:\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?|(? { expect(mask("approx ~5 items left")).toBe("approx ~5 items left") }) }) + +describe("maskString paths — fleet round 25 (dotfile tildes, letter-bearing drive proofs, NBSP particles)", () => { + it("dot-prefixed backslash tilde paths mask; escaped-dot regex prose never does", () => { + expect(mask(String.raw`read ~\.config\altimate\secret.json failed`)).toBe("read failed") + expect(mask(String.raw`sub ~\.\d pattern`)).toBe(String.raw`sub ~\.\d pattern`) + }) + + it("slash drive-relative proof needs only a letter; ratios stay out", () => { + expect(mask("read C:.client/models/private.sql failed")).toBe("read failed") + expect(mask("read C:client repo/models/private.sql failed")).toBe("read failed") + expect(mask("score C:8/10 fine")).toBe("score C:8/10 fine") + expect(mask("avg C:8.5/10 shown")).toBe("avg C:8.5/10 shown") + }) + + it("name particles work across nonbreaking spaces", () => { + expect(mask("/Users/Mary van der Berg does not exist")).toBe(" does not exist") + }) +}) From ddf1dad5d39a5069a4fe9c19f3afdf7eea5203ac Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 18:39:09 +0800 Subject: [PATCH 35/46] fix: one-char backslash tilde components A one-character first component is proven by the NEXT component passing the 2+ word-char or dotfile test, so ~\a\client\private.sql masks while escape chains (~\w\d+, ~\n\r\t) still fail the guard. --- packages/opencode/src/altimate/telemetry/index.ts | 2 +- packages/opencode/test/telemetry/mask-file-paths.test.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 4c483ac1a..bb25c996f 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -103,7 +103,7 @@ const PATH_RULES = { windows: new RegExp(PM_ANCHOR + "(?:[A-Za-z]:" + SEP_W + "|(? does not exist") }) }) + +describe("maskString paths — fleet round 26 (one-char tilde components)", () => { + it("one-char components mask when the next component proves the path", () => { + expect(mask(String.raw`read ~\a\client\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`chain ~\w\d+ used`)).toBe(String.raw`chain ~\w\d+ used`) + expect(mask(String.raw`escapes ~\n\r\t here`)).toBe(String.raw`escapes ~\n\r\t here`) + }) +}) From 0b437165c58748c446cbf61bbb159ece0c1c4845 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 18:48:02 +0800 Subject: [PATCH 36/46] fix: one-char rooted components, brace anchors, unicode extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - the rooted proof gains the one-hop-deferred alternative from the tilde rule, so \a\client\private.sql masks while escape chains fail - } completes the closing-delimiter anchor set ({ENOENT}/path) - PM_EXT accepts unicode letters/numbers (.配置) --- .../opencode/src/altimate/telemetry/index.ts | 6 +++--- .../test/telemetry/mask-file-paths.test.ts | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index bb25c996f..b28d89b29 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -71,9 +71,9 @@ const PM_R = "(?:[^\\s\\/\\\\'\"`]|'(?=[\\p{L}\\p{N}_]))" // slash-delimited variant: backslash is path content, not a separator const PM_R_P = "(?:[^\\s\\/'\"`]|'(?=[\\p{L}\\p{N}_]))" const PM_WORD = "(?:[\\p{L}\\p{M}\\p{N}_‘’-]|'(?=[\\p{L}\\p{N}_]))" -const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<|>)\\]])" +const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<|>)\\]}])" const PM_SP = "[^\\S\\t\\n\\r\\v\\f]" -const PM_EXT = "\\.[A-Za-z0-9-]{0,13}[A-Za-z0-9]" +const PM_EXT = "\\.[\\p{L}\\p{N}-]{0,13}[\\p{L}\\p{N}]" // span char: path content incl. delimiters (, ; ) ] } >) that a later // separator — or an attached dotted terminal filename (;draft.sql) — // proves is path content — multi-word spaced runs allowed, all @@ -100,7 +100,7 @@ const pmTail = (sep: string, term: string) => pmSpan(sep) + "*" + pmChunks(sep) const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:" + [pmCI("gs"), pmCI("s3") + "[anAN]?", pmCI("abfs") + "[sS]?", pmCI("wasb") + "[sS]?", pmCI("adl"), pmCI("dbfs"), pmCI("hdfs")].join("|") + "):\\/\\/|" + pmCI("file") + ":\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "gu"), windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|(?:\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?|(? { + it("one-char rooted components defer proof to the next component", () => { + expect(mask(String.raw`read \a\client\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`escape chain \n\r\t here`)).toBe(String.raw`escape chain \n\r\t here`) + }) + + it("closing braces anchor paths", () => { + expect(mask("{ENOENT}/Users/jdoe/client/a.sql")).toBe("{ENOENT}") + expect(mask("json {a}/{b} template")).toBe("json {a}/{b} template") + }) + + it("unicode extensions prove explicit paths", () => { + expect(mask("read ./customer.配置 failed")).toBe("read failed") + expect(mask("read /秘密.配置 now")).toBe("read now") + }) +}) From f902eea917be2007b4e6224f21b17ef65e16680d Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 18:55:58 +0800 Subject: [PATCH 37/46] fix: ampersand anchors, component-standard bounds, mark-bearing extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - & joins the anchor class (cmd&&/path shell syntax) - the drive-relative letter scan uses the 256 component bound instead of an arbitrary 64 - explicit shallow paths (./ prefix) use a length-bounded spaced- filename variant (12 words x 64 chars); deep tails keep the {1,4} prose cap deliberately — everything after ./ is filename, prose after a deep path is not - PM_EXT accepts combining marks (decomposed accents) --- .../opencode/src/altimate/telemetry/index.ts | 9 ++++---- .../test/telemetry/mask-file-paths.test.ts | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index b28d89b29..562e9f86b 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -71,9 +71,9 @@ const PM_R = "(?:[^\\s\\/\\\\'\"`]|'(?=[\\p{L}\\p{N}_]))" // slash-delimited variant: backslash is path content, not a separator const PM_R_P = "(?:[^\\s\\/'\"`]|'(?=[\\p{L}\\p{N}_]))" const PM_WORD = "(?:[\\p{L}\\p{M}\\p{N}_‘’-]|'(?=[\\p{L}\\p{N}_]))" -const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<|>)\\]}])" +const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<|>)\\]}&])" const PM_SP = "[^\\S\\t\\n\\r\\v\\f]" -const PM_EXT = "\\.[\\p{L}\\p{N}-]{0,13}[\\p{L}\\p{N}]" +const PM_EXT = "\\.[\\p{L}\\p{M}\\p{N}-]{0,13}[\\p{L}\\p{M}\\p{N}]" // span char: path content incl. delimiters (, ; ) ] } >) that a later // separator — or an attached dotted terminal filename (;draft.sql) — // proves is path content — multi-word spaced runs allowed, all @@ -96,13 +96,14 @@ const pmSpFile = (sep: string) => "(?:(?:" + PM_SP + "{1,2}" + pmR(sep) + "+){1, const PM_TERM_COND = "(?:(? "(?:(?:" + PM_SP + "{1,2}" + pmR(sep) + "{1,64}){1,12}(?<=" + PM_EXT + "))?" const pmTail = (sep: string, term: string) => pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:" + [pmCI("gs"), pmCI("s3") + "[anAN]?", pmCI("abfs") + "[sS]?", pmCI("wasb") + "[sS]?", pmCI("adl"), pmCI("dbfs"), pmCI("hdfs")].join("|") + "):\\/\\/|" + pmCI("file") + ":\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "gu"), windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|(?:\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?|(? now") }) }) + +describe("maskString paths — fleet round 28 (ampersands, bounds, marks)", () => { + it("shell ampersands anchor paths", () => { + expect(mask("cmd&&/Users/jdoe/client/private.sh failed")).toBe("cmd&& failed") + expect(mask("AT&T report ready")).toBe("AT&T report ready") + }) + + it("drive-relative letter scan reaches the component limit", () => { + expect(mask("read C:" + "1".repeat(65) + "a/models/private.sql failed")).toBe("read failed") + }) + + it("explicit shallow paths take many spaced words; deep tails keep the prose cap", () => { + expect(mask("read ./Q1 final audited customer revenue report.sql failed")).toBe("read failed") + // prose after a deep extensionless path must NOT be chased to a dotted token + expect(mask("read /opt/x error reading the project config.yml here")) + .toBe("read error reading the project config.yml here") + }) + + it("combining marks count in extensions", () => { + expect(mask("read ./customer.é failed")).toBe("read failed") + }) +}) From a811a232583f1e4704376c04c13ba1cc46746170 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 19:04:30 +0800 Subject: [PATCH 38/46] fix: +& in rooted components (len 3+), 30-char extension window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rooted-proof components accept + and & at length >=3 (\C++ Projects\, \R&D\) — the length floor keeps quantified escapes (\n+, \d+) out - PM_EXT window raised to 30 (.sublime-workspace); suppression direction stays conservative --- packages/opencode/src/altimate/telemetry/index.ts | 6 ++++-- .../opencode/test/telemetry/mask-file-paths.test.ts | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 562e9f86b..2fb9f3edb 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -73,7 +73,7 @@ const PM_R_P = "(?:[^\\s\\/'\"`]|'(?=[\\p{L}\\p{N}_]))" const PM_WORD = "(?:[\\p{L}\\p{M}\\p{N}_‘’-]|'(?=[\\p{L}\\p{N}_]))" const PM_ANCHOR = "(^|[\\s\"'`=(,[{:;<|>)\\]}&])" const PM_SP = "[^\\S\\t\\n\\r\\v\\f]" -const PM_EXT = "\\.[\\p{L}\\p{M}\\p{N}-]{0,13}[\\p{L}\\p{M}\\p{N}]" +const PM_EXT = "\\.[\\p{L}\\p{M}\\p{N}-]{0,29}[\\p{L}\\p{M}\\p{N}]" // span char: path content incl. delimiters (, ; ) ] } >) that a later // separator — or an attached dotted terminal filename (;draft.sql) — // proves is path content — multi-word spaced runs allowed, all @@ -96,12 +96,14 @@ const pmSpFile = (sep: string) => "(?:(?:" + PM_SP + "{1,2}" + pmR(sep) + "+){1, const PM_TERM_COND = "(?:(? "(?:(?:" + PM_SP + "{1,2}" + pmR(sep) + "{1,64}){1,12}(?<=" + PM_EXT + "))?" const pmTail = (sep: string, term: string) => pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:" + [pmCI("gs"), pmCI("s3") + "[anAN]?", pmCI("abfs") + "[sS]?", pmCI("wasb") + "[sS]?", pmCI("adl"), pmCI("dbfs"), pmCI("hdfs")].join("|") + "):\\/\\/|" + pmCI("file") + ":\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "gu"), windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|(?:\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?|(? failed") }) }) + +describe("maskString paths — fleet round 29 (symbol components, longer extensions)", () => { + it("rooted components carry + and & at length 3+; quantified escapes never do", () => { + expect(mask(String.raw`read \C++ Projects\client\secret.sql failed`)).toBe("read failed") + expect(mask(String.raw`read \R&D\client\secret.sql failed`)).toBe("read failed") + expect(mask(String.raw`pattern \n+\r+ found`)).toBe(String.raw`pattern \n+\r+ found`) + }) + + it("extensions up to 30 chars prove explicit paths", () => { + expect(mask("read ./customer.sublime-workspace failed")).toBe("read failed") + expect(mask("/Users/jdoe/app.properties was deleted")).toBe(" was deleted") + }) +}) From 2e1275ffa015a9ea94599b8b542b1bcbdfee1b34 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 19:21:27 +0800 Subject: [PATCH 39/46] fix: collapse pure separator runs before the composed rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A run of 4+ separators is never a real path shape, but the composed windows rule paid ~10µs/char consuming it in the regex interpreter — 20k backslashes cost ~200ms and tripped the upstream DoS-budget suite (v140-merge-chaos). One cheap anchored pre-rule collapses such runs to before the heavy rules run; the chaos suite passes with margin. --- packages/opencode/src/altimate/telemetry/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 2fb9f3edb..f0332fde4 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -1454,6 +1454,7 @@ export namespace Telemetry { // word mid-sentence (no personal names in that class); a delimiter // followed by neither a further separator nor a dotted terminal // filename is a permanent boundary. + .replace(/(^|[\s"'`=(,[{:;<|>)\]}&])[\\/]{4,}(?=$|[\s"'`,;)\]}<>|&])/g, "$1") .replace(PATH_RULES.cloud, "$1") .replace(PATH_RULES.windowsHome, "$1") .replace(PATH_RULES.windows, "$1") From abd0ff46369cd910049355a4a0e249654cd62f9b Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 19:44:19 +0800 Subject: [PATCH 40/46] feat: known-prefix literal masking layer, fast-path gates, shallow windows terminals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - layer zero: paths under the local user's home and cwd are replaced by EXACT literal match before any structural rule runs (the approach used by Salesforce's telemetry GDPR scrub and gatsby-telemetry's cleanPaths) — every username shape masks with zero false positives; variants cover JSON-doubled backslashes and swapped separators - fast path: strings without a separator skip the whole path stack; the two windows rules (widest opener alternation) additionally skip unless a backslash, boundary drive-colon, or non-scheme // exists — coverage-neutral, mean corpus cost 252µs -> 213µs - shallow explicit windows terminals mask (.\secret.sql, \secret.sql) via the dotted-terminal proof; regex escape prose still fails it --- .../opencode/src/altimate/telemetry/index.ts | 40 ++++++++++++++++--- .../test/telemetry/mask-file-paths.test.ts | 24 +++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index f0332fde4..3905f6711 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -100,10 +100,26 @@ const PM_WC = "(?:[\\p{L}\\p{N}_#@().'-]{2,}|[\\p{L}\\p{N}_#@().'+&-]{3,})" const PM_WCC = "[\\p{L}\\p{N}_#@().'+&-]+" const pmSpFileX = (sep: string) => "(?:(?:" + PM_SP + "{1,2}" + pmR(sep) + "{1,64}){1,12}(?<=" + PM_EXT + "))?" const pmTail = (sep: string, term: string) => pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term +// Layer zero — the local user's home and cwd are KNOWN literal values, so +// paths under them are replaced by exact match before any structural rule +// runs. Exact matching handles every username shape (spaces, NBSP, unicode) +// with zero false positives; the structural rules below remain for paths the +// literals cannot know (other drives, UNC shares, cloud URIs, relative +// forms, WSL-mounted homes). Variants cover JSON-doubled backslashes and +// swapped separators. Same approach as Salesforce's telemetry GDPR scrub +// (os.homedir() literal) and gatsby-telemetry's cleanPaths (cwd prefixes). +const pmEscape = (v: string) => v.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&") +const PM_KNOWN_PREFIXES: RegExp[] = [] +for (const root of [process.cwd(), os.homedir()]) { + if (!root || root.length < 4) continue + for (const v of new Set([root, root.replace(/\\/g, "\\\\"), root.replace(/\\/g, "/")])) { + PM_KNOWN_PREFIXES.push(new RegExp(pmEscape(v), "gi")) + } +} const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:" + [pmCI("gs"), pmCI("s3") + "[anAN]?", pmCI("abfs") + "[sS]?", pmCI("wasb") + "[sS]?", pmCI("adl"), pmCI("dbfs"), pmCI("hdfs")].join("|") + "):\\/\\/|" + pmCI("file") + ":\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "gu"), windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|(?:\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?|(?") + out = out // altimate_change start — mask filesystem paths in error text // Six masking rules (cloud URIs, Windows home, Windows/UNC incl. .\ and // ..\, POSIX home, POSIX incl. ./ and ../, ~ incl. ~username), composed from shared @@ -1456,11 +1477,20 @@ export namespace Telemetry { // filename is a permanent boundary. .replace(/(^|[\s"'`=(,[{:;<|>)\]}&])[\\/]{4,}(?=$|[\s"'`,;)\]}<>|&])/g, "$1") .replace(PATH_RULES.cloud, "$1") - .replace(PATH_RULES.windowsHome, "$1") - .replace(PATH_RULES.windows, "$1") + // the windows rules carry the widest opener alternation — they cannot + // match without a backslash, a boundary drive-colon, or a non-scheme // + if (out.includes("\\") || /(?").replace(PATH_RULES.windows, "$1") + } + out = out .replace(PATH_RULES.posixHome, "$1") .replace(PATH_RULES.posix, "$1") - .replace(PATH_RULES.tilde, "$1") + .replace(PATH_RULES.tilde, "$1") + // a literal-prefix mask followed by a structurally-masked remainder + // collapses to one marker + .replace(/(?:[\\/]?)+/g, "") + } + return out // altimate_change end // Email addresses — providers occasionally echo caller identity in error text. .replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, "") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 1c9bb7365..dc7c0e834 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -760,3 +760,27 @@ describe("maskString paths — fleet round 29 (symbol components, longer extensi expect(mask("/Users/jdoe/app.properties was deleted")).toBe(" was deleted") }) }) + +describe("maskString paths — layer zero (known-prefix literals) + fast path", () => { + const os = require("os") + it("paths under the local home mask by exact literal — any username shape", () => { + const home = os.homedir() + expect(mask(`ENOENT: no such file ${home}/client repo/秘密 file.sql retry`)).toBe("ENOENT: no such file retry") + expect(mask(`stat ${home} does not exist`)).toBe("stat does not exist") + }) + + it("paths under cwd mask by exact literal", () => { + expect(mask(`failed in ${process.cwd()}/models/private.sql now`)).toBe("failed in now") + }) + + it("shallow explicit windows terminals mask (dual-flagged symmetry gap)", () => { + expect(mask(String.raw`read .\customer_secret.sql failed`)).toBe("read failed") + expect(mask(String.raw`read \customer_secret.sql failed`)).toBe("read failed") + expect(mask(String.raw`expected \d+\.\d+ but got x`)).toBe(String.raw`expected \d+\.\d+ but got x`) + }) + + it("separator-free strings take the fast path unchanged", () => { + expect(mask("invalid_token: authentication expired, retry later")) + .toBe("invalid_token: authentication expired, retry later") + }) +}) From aa5d6d1afed0af8ff4d83553ef8c1399869052c1 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 19:54:38 +0800 Subject: [PATCH 41/46] test: restructure path-masking suite by subject MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The suite had accumulated as one describe per review round — a changelog, not a spec. Reorganized into thirteen subject sections (preprocessing, home roots, tails, windows/posix/tilde/cloud families, anchors, in-component delimiters, extensions, must-not-mask, composition, performance) with every test body preserved verbatim: same 115 tests, same 241 assertions. --- .../test/telemetry/mask-file-paths.test.ts | 992 +++++++++--------- 1 file changed, 487 insertions(+), 505 deletions(-) diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index dc7c0e834..738546f43 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -7,13 +7,57 @@ * core_failure/file_not_found cluster carried full /Users//… paths: * OS username + client repo structure). Quoted paths were coincidentally * destroyed by the quote rule, which is why the gap went unnoticed. + * + * Structure: one describe per subject — preprocessing, each path family, + * anchors, in-component delimiters, extensions, the must-not-mask set, and + * performance budgets. Doctrine throughout: over-masking is the correct + * failure mode; "path content vs prose" past an unproven delimiter is + * undecidable and stays a documented boundary. */ import { describe, expect, it } from "bun:test" +import os from "os" import { Telemetry } from "../../src/altimate/telemetry" const mask = Telemetry.maskString -describe("maskString file paths", () => { + +// ANSI/OSC sequences are stripped before any rule; paths under the local +// user's home/cwd are replaced by exact literal match; separator-free +// strings skip the path stack entirely. +describe("maskString paths — preprocessing — escape stripping, known-prefix literals, fast path", () => { + it("strips ANSI CSI sequences before masking", () => { + expect(mask("\x1b[31m/Users/jdoe/client/a.sql\x1b[0m failed")).toBe(" failed") + // an ANSI-split credential must still be caught by the sk- rule + expect(mask("token sk-\x1b[31mabc12345678901234567890\x1b[0m end")).toBe("token sk-*** end") + }) + + it("strips OSC hyperlink escapes so wrapped paths still mask", () => { + expect(mask("\x1b]8;;vscode://file//x\x1b\\/Users/jdoe/client/a.sql\x1b]8;;\x1b\\ failed")) + .toBe(" failed") + }) + + it("paths under the local home mask by exact literal — any username shape", () => { + const home = os.homedir() + expect(mask(`ENOENT: no such file ${home}/client repo/秘密 file.sql retry`)).toBe("ENOENT: no such file retry") + expect(mask(`stat ${home} does not exist`)).toBe("stat does not exist") + }) + + it("paths under cwd mask by exact literal", () => { + expect(mask(`failed in ${process.cwd()}/models/private.sql now`)).toBe("failed in now") + }) + + it("separator-free strings take the fast path unchanged", () => { + expect(mask("invalid_token: authentication expired, retry later")) + .toBe("invalid_token: authentication expired, retry later") + }) +}) + +// The high-PII class. Home rules must reach every root spelling: POSIX and +// Windows homes, nested mounts (WSL), UNC shares, extended-length and +// legacy prefixes — matched case-insensitively without the i flag +// (conformant engines fold \p{Lu} under /iu, which would break the +// capitalized-tail gate; JSC does not fold — never rely on the divergence). +describe("maskString paths — home-rooted paths — roots and reach", () => { it("masks home-directory paths (the PII case: username + repo layout)", () => { expect(mask("File not found: /Users/jdoe/Documents/client-repos/Platform_Project/models/05_da.sql")) .toBe("File not found: ") @@ -22,48 +66,56 @@ describe("maskString file paths", () => { expect(mask("could not read ~/dbt/profiles.yml")).toBe("could not read ") }) - it("masks Windows drive and UNC paths", () => { - expect(mask("No such file: C:\\Users\\jdoe\\project\\models\\a.sql")).toBe("No such file: ") - expect(mask("read failed for \\\\fileserver\\share\\models\\x.sql")).toBe("read failed for ") + it("matches Windows home roots case-insensitively", () => { + expect(mask("stat C:\\users\\Jane Doe does not exist")) + .toBe("stat does not exist") }) - it("masks project-rooted absolute paths (client repo structure)", () => { - expect(mask("File not found: /app/models/stg_quickbooks__estimate.sql")) - .toBe("File not found: ") - expect(mask("Manifest path for deferral does not exist: /data/warehouse/target/manifest.json")) - .toBe("Manifest path for deferral does not exist: ") + it("nested home roots get home-rule masking (WSL, Silverblue)", () => { + expect(mask("stat /mnt/c/Users/Jane Doe does not exist")) + .toBe("stat does not exist") + expect(mask("check /var/home/jane went missing")).toBe("check missing") }) - it("masks paths at string start and inside punctuation", () => { - expect(mask("/Users/jdoe/x/y.sql was deleted")).toBe(" was deleted") - expect(mask("failed (from /opt/dbt/bin/dbt)")).toBe("failed (from )") - expect(mask("--project-dir=/srv/analytics/dbt")).toBe("--project-dir=") + it("recognizes extended-length Windows home paths", () => { + expect(mask(String.raw`\\?\C:\Users\Jane Doe does not exist`)).toBe(" does not exist") + expect(mask(String.raw`stat \\?\C:\Users\Jane Doe\models\a.sql failed`)).toBe("stat failed") }) - it("does NOT mask non-path slashes", () => { - expect(mask("content-type application/json rejected")).toBe("content-type application/json rejected") - expect(mask("requirement dbt-core~=1.11.0 not satisfied")).toBe("requirement dbt-core~=1.11.0 not satisfied") - expect(mask("on 8/17/2026 at 3:2 ratio 1/2")).toBe("on 8/17/2026 at 3:2 ratio 1/2") - expect(mask("endpoint /mcp returned 404")).toBe("endpoint /mcp returned 404") + it("home rules reach through spaced prefix components and UNC shares", () => { + expect(mask("stat /mnt disk/c/Users/Jane Doe does not exist")).toBe("stat does not exist") + expect(mask(String.raw`stat \\fileserver\Users\Jane Doe does not exist`)).toBe("stat does not exist") + expect(mask(String.raw`stat \\?\UNC\srv\share\Users\Jane Doe is gone`)).toBe("stat is gone") }) - it("leaves public URL paths to the URL rules (never word-anchored)", () => { - expect(mask("POST https://api.openai.com/v1/chat/completions failed")) - .toBe("POST https://api.openai.com/v1/chat/completions failed") - // internal hosts keep their existing, stronger treatment - expect(mask("GET http://localhost:8080/api/x timed out")).toContain("") + it("legacy Documents and Settings roots get home treatment", () => { + expect(mask(String.raw`stat C:\Documents and Settings\Jane Doe does not exist`)) + .toBe("stat does not exist") }) - it("composes with the earlier rules in the chain", () => { - // credential first, then the path - expect(mask("sk-abcdefghijklmnopqrstuvwx leaked into /Users/jdoe/log.txt")) - .toBe("sk-*** leaked into ") - // quoted path still collapses via the quote rule — no leak either way - expect(mask("open '/Users/jdoe/x.sql' failed")).not.toContain("jdoe") + it("forward-slash UNC homes get the high-PII tail", () => { + expect(mask("stat //fileserver/share/Users/Jane Doe does not exist")) + .toBe("stat does not exist") + expect(mask("fetch //cdn.example.com/lib.js failed")).toBe("fetch //cdn.example.com/lib.js failed") + }) + + it("home/cloud rules are engine-independent: no i flag, hand-expanded case", () => { + // lowercase prose after a home path costs exactly one word — this is the + // assertion that silently broke under V8's conformant /iu folding + expect(mask("/Users/jdoe was not found on this system")).toBe(" not found on this system") + // the case-insensitive coverage the i flag used to provide still holds + expect(mask("stat c:\\users\\jane doe missing")).toBe("stat missing") + expect(mask("check /users/jane went missing")).toBe("check missing") + expect(mask("read S3://Bucket/Key now")).toBe("read ") + expect(mask(String.raw`stat \\?\unc\srv\share\Users\Jane Doe gone`)).toBe("stat gone") }) }) -describe("maskString paths — review + live-data hardening", () => { +// Home paths and cloud keys consume one unconditional trailing word plus +// capitalized/caseless/particle continuations, so full names mask; other +// rules take a terminal word only at end-of-string/punctuation; a dotted +// extension suppresses the tail so following prose survives. +describe("maskString paths — terminal components and tails — spaced, unicode, multi-word names", () => { it("masks paths with embedded spaces (macOS home dirs, spaced repo names)", () => { expect(mask("File not found: /Users/Jane Doe/client repo/models/a.sql")) .toBe("File not found: ") @@ -83,49 +135,12 @@ describe("maskString paths — review + live-data hardening", () => { .toBe("File not found: was deleted upstream") }) - it("masks cloud-storage URIs (live leak: client GCS bucket + data layout)", () => { - expect(mask("CSV table references gs://client-finance-import/inventory/users/file_date=2026-07-16/users.csv")) - .toBe("CSV table references ") - expect(mask("read failed: s3://acme-prod-lake/raw/orders/part-0001.parquet")) - .toBe("read failed: ") - // trailing single word at end-of-string is consumed (see doctrine note) - expect(mask("abfss://container@account.dfs.core.windows.net/data/x failed")) - .toBe("") - }) - - it("still leaves public https doc-links alone (provider help URLs)", () => { - expect(mask("see https://community.snowflake.com/s/ip-not-allowed for details")) - .toBe("see https://community.snowflake.com/s/ip-not-allowed for details") - }) -}) - -describe("maskString paths — codex re-review round", () => { - it("masks paths enclosed in brackets/braces", () => { - expect(mask("failed [/Users/jdoe/client/model.sql]")).toBe("failed []") - expect(mask("ctx {/home/jdoe/proj/x.yml} missing")).toBe("ctx {} missing") - }) - - it("continues cloud object keys across spaces", () => { - expect(mask("read s3://client-bucket/client data/raw file.csv")) - .toBe("read ") - expect(mask("gs://acme-lake/dir with spaces/part 0001.parquet not found")) - .toBe(" not found") - }) - it("masks a terminal spaced component at end-of-string or before punctuation", () => { expect(mask("Directory not found: /Users/jdoe/client repo")).toBe("Directory not found: ") expect(mask("could not open /Users/jdoe/client repo.")).toBe("could not open .") expect(mask("bad dir [C:\\Users\\jdoe\\client repo]")).toBe("bad dir []") }) - it("still never eats mid-sentence prose after a path", () => { - expect(mask("open /app/x.sql failed with error")).toBe("open failed with error") - expect(mask("File not found: /app/models/a.sql was deleted upstream")) - .toBe("File not found: was deleted upstream") - }) -}) - -describe("maskString paths — round 4 (spaced usernames, windows delimiters)", () => { it("masks the tail of a spaced username mid-sentence (home-rooted paths)", () => { expect(mask("No such file: /Users/Jane Doe does not exist")) .toBe("No such file: does not exist") // surname consumed, prose kept @@ -137,39 +152,6 @@ describe("maskString paths — round 4 (spaced usernames, windows delimiters)", .toBe("File not found: was deleted upstream") }) - it("windows paths keep their closing delimiters", () => { - expect(mask("failed (C:\\Users\\jdoe\\x.sql)")).toBe("failed ()") - expect(mask("in [D:\\proj\\data\\y.csv], aborted")).toBe("in [], aborted") - }) - - it("spaced Windows dirs still continue (Program Files shape)", () => { - // dotted extension -> the trailing word is clearly prose and survives - expect(mask("spawn C:\\Program Files (x86)\\dbt\\dbt.exe ENOENT")) - .toBe("spawn ENOENT") - }) -}) - -describe("maskString paths — round 5", () => { - it("masks single-slash file: URIs (RFC 8089)", () => { - expect(mask("read file:/Users/jdoe/client/model.sql failed")) - .toBe("read failed") - expect(mask("open file:///home/jdoe/x.yml then retry")).toBe("open then retry") - }) - - it("matches Windows home roots case-insensitively", () => { - expect(mask("stat C:\\users\\Jane Doe does not exist")) - .toBe("stat does not exist") - }) - - it("recognizes long extensions so following prose survives", () => { - expect(mask("/Users/jdoe/data.parquet was deleted")) - .toBe(" was deleted") - expect(mask("wrote /home/jdoe/out/events.jsonl then stopped")) - .toBe("wrote then stopped") - }) -}) - -describe("maskString paths — round 6 (unicode, colon anchors)", () => { it("masks unicode path components across spaces", () => { expect(mask("read /Users/Jane García/client/model.sql failed")) .toBe("read failed") @@ -179,23 +161,6 @@ describe("maskString paths — round 6 (unicode, colon anchors)", () => { expect(mask("read /Users/José/client/model.sql failed")).toBe("read failed") }) - it("recognizes paths directly after a colon", () => { - expect(mask("ENOENT:/Users/jdoe/client/a.sql")).toBe("ENOENT:") - expect(mask("source:s3://customer-bucket/key")).toBe("source:") - expect(mask("at path:C:\\Users\\jdoe\\x.sql end")).toBe("at path: end") - }) - - it("colon anchors never bite into URLs", () => { - expect(mask("POST https://api.openai.com/v1/chat/completions failed")) - .toBe("POST https://api.openai.com/v1/chat/completions failed") - expect(mask("fetch //cdn.example.com/lib.js failed")) - .toBe("fetch //cdn.example.com/lib.js failed") - expect(mask("connect db-host:5432/postgres refused")) - .toBe("connect db-host:5432/postgres refused") - }) -}) - -describe("maskString paths — round 7 (combining marks, punctuation boundaries)", () => { it("masks NFD-decomposed names (macOS filename normalization)", () => { const nfd = "/Users/Jane García/client/model.sql".normalize("NFD") expect(nfd).not.toBe("/Users/Jane García/client/model.sql".normalize("NFC")) // truly decomposed @@ -207,400 +172,417 @@ describe("maskString paths — round 7 (combining marks, punctuation boundaries) .toBe("read failed") }) - it("recognizes ; and < as path boundaries, preserving closers", () => { - expect(mask("ENOENT;/Users/jdoe/client/a.sql")).toBe("ENOENT;") - expect(mask("failed ")).toBe("failed <>") - expect(mask("cfg=a.yml;/etc/dbt/profiles.yml;done")).toBe("cfg=a.yml;;done") + it("apostrophes survive inside spaced components", () => { + expect(mask("stat /Users/Jane Doe's client/models/a.sql now")).toBe("stat now") }) -}) -describe("maskString paths — round 8 (ASCII apostrophes in components)", () => { - it("treats an apostrophe followed by a word char as path content", () => { - expect(mask("read /Users/Jane/O'Connor/client/model.sql failed")) - .toBe("read failed") - expect(mask("stat /Users/Jane O'Connor went missing")).toBe("stat went missing") + it("continues across a double space inside a component", () => { + expect(mask("read /Users/Jane Doe/client/model.sql failed")).toBe("read failed") }) - it("still treats a closing apostrophe as a quote delimiter", () => { - // path masks inside the quotes, closing quote survives for the quote rule - expect(mask("open '/Users/jdoe/x.sql' failed")).toBe("open ? failed") - expect(mask("open '/Users/jdoe/x.sql' failed")).not.toContain("jdoe") + it("high-PII tail consumes capitalized word runs (middle names, multi-word keys)", () => { + expect(mask("/Users/Mary Jane Smith does not exist")).toBe(" does not exist") + expect(mask("C:\\Users\\Mary Jane Smith does not exist")).toBe(" does not exist") + expect(mask("s3://bucket/Client Top Secret does not exist")).toBe(" does not exist") + // lowercase prose still costs exactly one word + expect(mask("/Users/jdoe was not found on this system")).toBe(" not found on this system") }) -}) -describe("maskString paths — closing round (delimiters inside components)", () => { - it("continues through delimiters that a later separator proves are path content", () => { - expect(mask("read /Users/Jane Doe/client, repo/models/a.sql failed")) - .toBe("read failed") - expect(mask("open ~/reports,final/q3.csv now")).toBe("open now") + it("PII tails cross lowercase name particles and caseless scripts", () => { + expect(mask("/Users/Mary van der Berg does not exist")).toBe(" does not exist") + expect(mask("/Users/李 小 明 does not exist")).toBe(" does not exist") + // ordinary lowercase prose still costs exactly one word + expect(mask("/Users/jdoe was not found on this system")).toBe(" not found on this system") }) - it("a delimiter without a following separator stays a boundary", () => { - expect(mask("read /app/data/x.csv, then /var/log/y.log failed")) - .toBe("read , then failed") - expect(mask("saw [/opt/dbt/a.yml], aborted")).toBe("saw [], aborted") + it("nonbreaking spaces continue components; tabs stay boundaries", () => { + expect(mask("read /Users/Jane Doe/client/model.sql failed")).toBe("read failed") + expect(mask("/Users/jdoe/client\tsecret/models/a.sql")).toBe(" secret/models/a.sql") + }) + + it("name particles work across nonbreaking spaces", () => { + expect(mask("/Users/Mary van der Berg does not exist")).toBe(" does not exist") }) }) -describe("maskString paths — fleet review round (ReDoS + separators)", () => { - it("adversarial delimiter + slash-free run completes in linear time", () => { - const t0 = performance.now() - mask("/a/b, " + "a".repeat(5000)) - expect(performance.now() - t0).toBeLessThan(500) // was exponential: ~2s at 28 chars +describe("maskString paths — windows paths — drive, UNC, relative, and rooted forms", () => { + it("masks Windows drive and UNC paths", () => { + expect(mask("No such file: C:\\Users\\jdoe\\project\\models\\a.sql")).toBe("No such file: ") + expect(mask("read failed for \\\\fileserver\\share\\models\\x.sql")).toBe("read failed for ") }) - it("delimiters attached directly to a separator continue the path", () => { - expect(mask("read /data/a[b]/c.txt failed")).toBe("read failed") + it("windows paths keep their closing delimiters", () => { + expect(mask("failed (C:\\Users\\jdoe\\x.sql)")).toBe("failed ()") + expect(mask("in [D:\\proj\\data\\y.csv], aborted")).toBe("in [], aborted") + }) + + it("spaced Windows dirs still continue (Program Files shape)", () => { + // dotted extension -> the trailing word is clearly prose and survives + expect(mask("spawn C:\\Program Files (x86)\\dbt\\dbt.exe ENOENT")) + .toBe("spawn ENOENT") }) it("windows delimiter continuation accepts backslash separators", () => { expect(mask("del C:\\Users\\jdoe\\client, repo\\x.sql now")).toBe("del now") }) -}) -describe("maskString paths — fleet round 2 (relative, symbols, cloud tails)", () => { - it("masks dot-relative paths (unambiguous ./ and ../ prefixes)", () => { - expect(mask("read failed: ./customers/acme/models/private.sql")).toBe("read failed: ") - expect(mask("open ../client-repo/profiles.yml now")).toBe("open now") + it("masks Windows dot-relative paths", () => { + expect(mask(String.raw`read .\customers\acme\models\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`open ..\client-repo\profiles.yml now`)).toBe("open now") }) - it("continues spaced components that start with symbols", () => { - expect(mask("read /Users/Jane Doe/client #1/models/a.sql failed")) - .toBe("read failed") + it("dot-relative prefix never fires on dotted prose or printed escapes", () => { + expect(mask("wait...\\ hmm that failed")).toBe("wait...\\ hmm that failed") + expect(mask("unexpected token .\\n at position 5")).toBe("unexpected token .\\n at position 5") }) - it("cloud object keys consume a terminal spaced component like home paths", () => { - expect(mask("read s3://customer-bucket/client repo failed")).toBe("read failed") - // dotted extension still protects following prose - expect(mask("read s3://customer-bucket/data.csv failed to download")) - .toBe("read failed to download") + it("masks current-drive-rooted windows paths, home and generic", () => { + expect(mask(String.raw`read \Users\Jane Doe\models\a.sql failed`)).toBe("read failed") + expect(mask(String.raw`open \inetpub\wwwroot\app\web.config denied`)).toBe("open denied") }) -}) -describe("maskString paths — fleet round 3 (composed rules)", () => { - it("delimiter lookahead accepts multi-word components", () => { - expect(mask("read /data/x, big client repo/models/a.sql done")).toBe("read done") + it("rooted opener never eats regex or escape prose", () => { + // proof requires two >=2-word-char components: 1-char escape classes and + // quantifier-bearing runs both fail it + expect(mask(String.raw`expected pattern \d+\.\d+ but got x`)).toBe(String.raw`expected pattern \d+\.\d+ but got x`) + expect(mask(String.raw`escape chain \n\r\t here`)).toBe(String.raw`escape chain \n\r\t here`) + expect(mask(String.raw`match \bword\b boundary`)).toBe(String.raw`match \bword\b boundary`) }) - it("terminal dotted filename may span multiple words", () => { - expect(mask("read /Users/jdoe/reports/Q4 final reviewed version.sql failed")) - .toBe("read failed") + it("masks drive-relative windows paths, generic and home", () => { + expect(mask(String.raw`read C:client-repo\models\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`read C:Users\Jane Doe\client\a.sql failed`)).toBe("read failed") + // colon-bearing prose without a backslash chain never matches + expect(mask("score C:8/10 fine")).toBe("score C:8/10 fine") }) - it("first POSIX segment may contain symbols or emoji", () => { - expect(mask("read /#clients/acme/private.sql failed")).toBe("read failed") - expect(mask("read /💾/acme/private.sql failed")).toBe("read failed") + it("accepts forward slashes in drive-relative paths, digit ratios never match", () => { + expect(mask("read C:client-repo/models/private.sql failed")).toBe("read failed") + expect(mask("score C:8/10 fine")).toBe("score C:8/10 fine") + expect(mask("rated e:10/20 shown")).toBe("rated e:10/20 shown") }) - it("named-user tilde homes mask", () => { - expect(mask("could not read ~jane/client-repo/profiles.yml")).toBe("could not read ") + it("drive-relative backslash proof accepts spaced and dotted first components", () => { + expect(mask(String.raw`read C:client repo\models\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`read C:.client\models\private.sql failed`)).toBe("read failed") + // slash-proven form keeps the letter-first guard: ratios never match + expect(mask("score C:8/10 fine")).toBe("score C:8/10 fine") }) - it("apostrophes survive inside spaced components", () => { - expect(mask("stat /Users/Jane Doe's client/models/a.sql now")).toBe("stat now") + it("rooted proof accepts spaced and symbol-bearing components", () => { + expect(mask(String.raw`open \Program Files\Altimate\secret.sql denied`)).toBe("open denied") + expect(mask(String.raw`read \client repo\models\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`read \#client\models\private.sql failed`)).toBe("read failed") + // regex-prose guards must keep failing the proof + expect(mask(String.raw`expected pattern \d+\.\d+ but got x`)).toBe(String.raw`expected pattern \d+\.\d+ but got x`) }) - it("multi-word lookahead stays linear on adversarial input", () => { - const t0 = performance.now() - mask("/a/b, " + "word ".repeat(1000)) - expect(performance.now() - t0).toBeLessThan(500) + it("rooted proof accepts parenthesized/dotted components", () => { + expect(mask(String.raw`open \Program Files (x86)\Altimate\secret.sql denied`)).toBe("open denied") + expect(mask(String.raw`expected pattern \d+\.\d+ but got x`)).toBe(String.raw`expected pattern \d+\.\d+ but got x`) }) -}) -describe("maskString paths — fleet round 4 (parens in components, nested homes)", () => { - it("continues through ) when a later separator proves it is path content", () => { - expect(mask("read /Users/jdoe/client)repo/models/a.sql failed")).toBe("read failed") - // closing paren without a following separator stays preserved - expect(mask("failed (from /opt/dbt/bin/dbt)")).toBe("failed (from )") + it("shallow rooted windows paths mask via the dotted-terminal proof", () => { + expect(mask(String.raw`read \client-repo\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`open \Program Files\app.exe denied`)).toBe("open denied") + expect(mask(String.raw`match \bword\b boundary`)).toBe(String.raw`match \bword\b boundary`) }) - it("nested home roots get home-rule masking (WSL, Silverblue)", () => { - expect(mask("stat /mnt/c/Users/Jane Doe does not exist")) - .toBe("stat does not exist") - expect(mask("check /var/home/jane went missing")).toBe("check missing") + it("rooted proof accepts spaced-first-component extensionless paths", () => { + expect(mask(String.raw`open \Program Files\Altimate denied`)).toBe("open ") + expect(mask(String.raw`match \bword\b boundary`)).toBe(String.raw`match \bword\b boundary`) }) -}) -describe("maskString paths — fleet round 5 (dot-relative windows, attached filenames)", () => { - it("masks Windows dot-relative paths", () => { - expect(mask(String.raw`read .\customers\acme\models\private.sql failed`)).toBe("read failed") - expect(mask(String.raw`open ..\client-repo\profiles.yml now`)).toBe("open now") + it("forward-slash UNC masks; dotted hosts stay protocol-relative URLs", () => { + expect(mask("open //fileserver/client-share/private.sql failed")).toBe("open failed") + // dotless-server discriminator: dotted first components are URL hosts + expect(mask("fetch //cdn.example.com/lib.js failed")).toBe("fetch //cdn.example.com/lib.js failed") }) - it("dot-relative prefix never fires on dotted prose or printed escapes", () => { - expect(mask("wait...\\ hmm that failed")).toBe("wait...\\ hmm that failed") - expect(mask("unexpected token .\\n at position 5")).toBe("unexpected token .\\n at position 5") + it("slash drive-relative proof needs only a letter; ratios stay out", () => { + expect(mask("read C:.client/models/private.sql failed")).toBe("read failed") + expect(mask("read C:client repo/models/private.sql failed")).toBe("read failed") + expect(mask("score C:8/10 fine")).toBe("score C:8/10 fine") + expect(mask("avg C:8.5/10 shown")).toBe("avg C:8.5/10 shown") }) - it("attached dotted terminal filename proves a delimiter (all rule families)", () => { - expect(mask("read /Users/jdoe/project;draft.sql")).toBe("read ") - expect(mask("C:\\Users\\jdoe\\proj;draft.sql gone")).toBe(" gone") - expect(mask("s3://bucket/dir;file.csv loaded")).toBe(" loaded") + it("one-char rooted components defer proof to the next component", () => { + expect(mask(String.raw`read \a\client\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`escape chain \n\r\t here`)).toBe(String.raw`escape chain \n\r\t here`) }) - it("attached delimiter without separator or dotted filename stays a boundary", () => { - // shell-style `;cmd` must not be eaten — documented residue - expect(mask("cd /Users/jdoe/proj;ls failed")).toBe("cd ;ls failed") - expect(mask("shell: cd /opt/app; rm -rf tmp")).toBe("shell: cd ; rm -rf tmp") + it("drive-relative letter scan reaches the component limit", () => { + expect(mask("read C:" + "1".repeat(65) + "a/models/private.sql failed")).toBe("read failed") }) - it("tab is a field boundary, not path continuation (documented residue)", () => { - // tab-delimited log columns after a path must survive; the bare relative - // fragment after the tab is the documented undecidable-fragment boundary - expect(mask("/Users/jdoe/client\tsecret/models/a.sql")).toBe(" secret/models/a.sql") + it("rooted components carry + and & at length 3+; quantified escapes never do", () => { + expect(mask(String.raw`read \C++ Projects\client\secret.sql failed`)).toBe("read failed") + expect(mask(String.raw`read \R&D\client\secret.sql failed`)).toBe("read failed") + expect(mask(String.raw`pattern \n+\r+ found`)).toBe(String.raw`pattern \n+\r+ found`) }) - it("extension-proof delimiter branch stays linear on adversarial input", () => { - let t0 = performance.now() - mask("/a/b/x;" + "a".repeat(5000)) - expect(performance.now() - t0).toBeLessThan(500) - t0 = performance.now() - mask("/a/b/x;" + "a.".repeat(2500)) - expect(performance.now() - t0).toBeLessThan(500) + it("shallow explicit windows terminals mask (POSIX symmetry)", () => { + expect(mask(String.raw`read .\customer_secret.sql failed`)).toBe("read failed") + expect(mask(String.raw`read \customer_secret.sql failed`)).toBe("read failed") + expect(mask(String.raw`expected \d+\.\d+ but got x`)).toBe(String.raw`expected \d+\.\d+ but got x`) }) }) -describe("maskString paths — fleet round 6 (backslash components, extended-length homes)", () => { - it("keeps literal backslashes as path content in slash-delimited rules", () => { - expect(mask(String.raw`read s3://bucket/customer data\raw/models/a.sql failed`)).toBe("read failed") - expect(mask(String.raw`stat /opt/a\b/c/d.sql failed`)).toBe("stat failed") +describe("maskString paths — posix paths — absolute, dot-relative, and shallow forms", () => { + it("masks project-rooted absolute paths (client repo structure)", () => { + expect(mask("File not found: /app/models/stg_quickbooks__estimate.sql")) + .toBe("File not found: ") + expect(mask("Manifest path for deferral does not exist: /data/warehouse/target/manifest.json")) + .toBe("Manifest path for deferral does not exist: ") }) - it("recognizes extended-length Windows home paths", () => { - expect(mask(String.raw`\\?\C:\Users\Jane Doe does not exist`)).toBe(" does not exist") - expect(mask(String.raw`stat \\?\C:\Users\Jane Doe\models\a.sql failed`)).toBe("stat failed") + it("masks dot-relative paths (unambiguous ./ and ../ prefixes)", () => { + expect(mask("read failed: ./customers/acme/models/private.sql")).toBe("read failed: ") + expect(mask("open ../client-repo/profiles.yml now")).toBe("open now") }) - it("backslash-permissive run class never bleeds into following prose", () => { - expect(mask(String.raw`use /pattern/ with \d+ tokens`)).toBe(String.raw`use with \d+ tokens`) - expect(mask(String.raw`glob /opt/data/*.sql \n escaped`)).toBe(String.raw`glob \n escaped`) + it("first POSIX segment may contain symbols or emoji", () => { + expect(mask("read /#clients/acme/private.sql failed")).toBe("read failed") + expect(mask("read /💾/acme/private.sql failed")).toBe("read failed") }) - it("widened run class stays linear on adversarial input", () => { - const t0 = performance.now() - mask("s3://b/k " + "w\\x ".repeat(1200)) - expect(performance.now() - t0).toBeLessThan(500) + it("masks spaced first components in dot-relative paths", () => { + expect(mask("read ./client repo/models/private.sql failed")).toBe("read failed") + expect(mask(String.raw`read .\client repo\models\a.sql failed`)).toBe("read failed") }) -}) -describe("maskString paths — fleet round 7 (double spaces, current-drive-rooted windows)", () => { - it("continues across a double space inside a component", () => { - expect(mask("read /Users/Jane Doe/client/model.sql failed")).toBe("read failed") + it("terminal-only dot-relative paths mask when extension-bearing", () => { + expect(mask("read ./customer_secret.sql failed")).toBe("read failed") + expect(mask("read ../customer_secret.sql now")).toBe("read now") + // extensionless ./x stays out — undecidable against prose tokens + expect(mask("see ./x plain token")).toBe("see ./x plain token") }) - it("three or more spaces stay a column boundary", () => { - expect(mask("read /opt/data/x.sql 404 NotFound")).toBe("read 404 NotFound") + it("shallow dot-relative accepts spaced dotted terminals", () => { + expect(mask("read ./Q4 final report.sql failed")).toBe("read failed") }) - it("masks current-drive-rooted windows paths, home and generic", () => { - expect(mask(String.raw`read \Users\Jane Doe\models\a.sql failed`)).toBe("read failed") - expect(mask(String.raw`open \inetpub\wwwroot\app\web.config denied`)).toBe("open denied") + it("single-component absolute files mask; URL interiors never do", () => { + expect(mask("read /customer_secret.sql failed")).toBe("read failed") + // scheme colons and protocol-relative slashes are excluded from the + // shallow-absolute form — URL pins elsewhere in this suite stay green + expect(mask("see https://community.snowflake.com/s/ip-not-allowed for details")) + .toBe("see https://community.snowflake.com/s/ip-not-allowed for details") }) - it("rooted opener never eats regex or escape prose", () => { - // proof requires two >=2-word-char components: 1-char escape classes and - // quantifier-bearing runs both fail it - expect(mask(String.raw`expected pattern \d+\.\d+ but got x`)).toBe(String.raw`expected pattern \d+\.\d+ but got x`) - expect(mask(String.raw`escape chain \n\r\t here`)).toBe(String.raw`escape chain \n\r\t here`) - expect(mask(String.raw`match \bword\b boundary`)).toBe(String.raw`match \bword\b boundary`) + it("errno-style colon prefixes anchor single-component files", () => { + expect(mask("ENOENT:/private.sql failed")).toBe("ENOENT: failed") + // the protocol-relative guard alone protects URLs — full pin set stays green + expect(mask("fetch //cdn.example.com/lib.js failed")).toBe("fetch //cdn.example.com/lib.js failed") }) - it("double-space continuation stays linear on adversarial input", () => { - const t0 = performance.now() - mask("/Users/x/y" + " zz".repeat(1200)) - expect(performance.now() - t0).toBeLessThan(500) + it("explicit shallow paths take many spaced words; deep tails keep the prose cap", () => { + expect(mask("read ./Q1 final audited customer revenue report.sql failed")).toBe("read failed") + // prose after a deep extensionless path must NOT be chased to a dotted token + expect(mask("read /opt/x error reading the project config.yml here")) + .toBe("read error reading the project config.yml here") }) }) -describe("maskString paths — fleet round 8 (ANSI, drive-relative, quote delimiters)", () => { - it("strips ANSI CSI sequences before masking", () => { - expect(mask("\x1b[31m/Users/jdoe/client/a.sql\x1b[0m failed")).toBe(" failed") - // an ANSI-split credential must still be caught by the sk- rule - expect(mask("token sk-\x1b[31mabc12345678901234567890\x1b[0m end")).toBe("token sk-*** end") +describe("maskString paths — tilde paths", () => { + it("named-user tilde homes mask", () => { + expect(mask("could not read ~jane/client-repo/profiles.yml")).toBe("could not read ") }) - it("masks drive-relative windows paths, generic and home", () => { - expect(mask(String.raw`read C:client-repo\models\private.sql failed`)).toBe("read failed") - expect(mask(String.raw`read C:Users\Jane Doe\client\a.sql failed`)).toBe("read failed") - // colon-bearing prose without a backslash chain never matches - expect(mask("score C:8/10 fine")).toBe("score C:8/10 fine") + it("tilde homes use the unconditional home tail", () => { + expect(mask("stat ~/client repo does not exist")).toBe("stat does not exist") + expect(mask("read ~jane/Client Secret now")).toBe("read now") }) - it("continues through quote characters a later separator proves", () => { - expect(mask(`read /Users/jdoe/client"repo/models/a.sql failed`)).toBe("read failed") - // paired inline-code backticks stay intact - expect(mask("run `dbt build` in `/opt/proj/app` now")).toBe("run `dbt build` in `` now") + it("backslash tilde paths mask; regex tilde prose never does", () => { + expect(mask(String.raw`read ~\client-repo\models\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`read ~jane\Client Secret\a.sql now`)).toBe("read now") + expect(mask(String.raw`regex ~\d+ used here`)).toBe(String.raw`regex ~\d+ used here`) + expect(mask("approx ~5 items left")).toBe("approx ~5 items left") + }) + + it("dot-prefixed backslash tilde paths mask; escaped-dot regex prose never does", () => { + expect(mask(String.raw`read ~\.config\altimate\secret.json failed`)).toBe("read failed") + expect(mask(String.raw`sub ~\.\d pattern`)).toBe(String.raw`sub ~\.\d pattern`) + }) + + it("one-char components mask when the next component proves the path", () => { + expect(mask(String.raw`read ~\a\client\private.sql failed`)).toBe("read failed") + expect(mask(String.raw`chain ~\w\d+ used`)).toBe(String.raw`chain ~\w\d+ used`) + expect(mask(String.raw`escapes ~\n\r\t here`)).toBe(String.raw`escapes ~\n\r\t here`) }) }) -describe("maskString paths — fleet round 9 (quote-pair safety, pipes, OSC)", () => { - it("a closing quote directly before a slash stays a boundary (pairing never shifts)", () => { - // the eaten quote used to shift pairing and leak the next quoted value - expect(mask(`read "/a/b"/c "secret data" end`)).toBe("read ?/c ? end") - // mid-component quotes (non-empty run before the separator) still mask - expect(mask(`read /Users/jdoe/client"repo/models/a.sql failed`)).toBe("read failed") +describe("maskString paths — cloud storage URIs", () => { + it("masks cloud-storage URIs (live leak: client GCS bucket + data layout)", () => { + expect(mask("CSV table references gs://client-finance-import/inventory/users/file_date=2026-07-16/users.csv")) + .toBe("CSV table references ") + expect(mask("read failed: s3://acme-prod-lake/raw/orders/part-0001.parquet")) + .toBe("read failed: ") + // trailing single word at end-of-string is consumed (see doctrine note) + expect(mask("abfss://container@account.dfs.core.windows.net/data/x failed")) + .toBe("") }) - it("pipe is a path anchor but plain pipe prose never matches", () => { - expect(mask("ENOENT|/Users/jdoe/client/a.sql")).toBe("ENOENT|") - expect(mask("source|s3://customer-bucket/private/a.csv")).toBe("source|") - expect(mask("a|b or c|d plain prose")).toBe("a|b or c|d plain prose") + it("continues cloud object keys across spaces", () => { + expect(mask("read s3://client-bucket/client data/raw file.csv")) + .toBe("read ") + expect(mask("gs://acme-lake/dir with spaces/part 0001.parquet not found")) + .toBe(" not found") }) - it("strips OSC hyperlink escapes so wrapped paths still mask", () => { - expect(mask("\x1b]8;;vscode://file//x\x1b\\/Users/jdoe/client/a.sql\x1b]8;;\x1b\\ failed")) - .toBe(" failed") + it("masks single-slash file: URIs (RFC 8089)", () => { + expect(mask("read file:/Users/jdoe/client/model.sql failed")) + .toBe("read failed") + expect(mask("open file:///home/jdoe/x.yml then retry")).toBe("open then retry") + }) + + it("cloud object keys consume a terminal spaced component like home paths", () => { + expect(mask("read s3://customer-bucket/client repo failed")).toBe("read failed") + // dotted extension still protects following prose + expect(mask("read s3://customer-bucket/data.csv failed to download")) + .toBe("read failed to download") }) }) -describe("maskString paths — fleet round 10 (spaced first components, drive-relative slashes)", () => { - it("masks spaced first components in dot-relative paths", () => { - expect(mask("read ./client repo/models/private.sql failed")).toBe("read failed") - expect(mask(String.raw`read .\client repo\models\a.sql failed`)).toBe("read failed") +// The anchor class: whitespace, quotes, brackets, comparison/shell +// operators, pipes, redirects, colons, and closing delimiters. +describe("maskString paths — anchors — where a path may begin", () => { + it("masks paths at string start and inside punctuation", () => { + expect(mask("/Users/jdoe/x/y.sql was deleted")).toBe(" was deleted") + expect(mask("failed (from /opt/dbt/bin/dbt)")).toBe("failed (from )") + expect(mask("--project-dir=/srv/analytics/dbt")).toBe("--project-dir=") }) - it("accepts forward slashes in drive-relative paths, digit ratios never match", () => { - expect(mask("read C:client-repo/models/private.sql failed")).toBe("read failed") - expect(mask("score C:8/10 fine")).toBe("score C:8/10 fine") - expect(mask("rated e:10/20 shown")).toBe("rated e:10/20 shown") + it("masks paths enclosed in brackets/braces", () => { + expect(mask("failed [/Users/jdoe/client/model.sql]")).toBe("failed []") + expect(mask("ctx {/home/jdoe/proj/x.yml} missing")).toBe("ctx {} missing") }) - it("spaced prefix components stay linear on adversarial input", () => { - const t0 = performance.now() - mask("./" + "a b ".repeat(1500)) - expect(performance.now() - t0).toBeLessThan(500) + it("recognizes paths directly after a colon", () => { + expect(mask("ENOENT:/Users/jdoe/client/a.sql")).toBe("ENOENT:") + expect(mask("source:s3://customer-bucket/key")).toBe("source:") + expect(mask("at path:C:\\Users\\jdoe\\x.sql end")).toBe("at path: end") }) -}) -describe("maskString paths — fleet round 11 (home-rule reach: spaced prefixes, UNC; drive-relative breadth)", () => { - it("home rules reach through spaced prefix components and UNC shares", () => { - expect(mask("stat /mnt disk/c/Users/Jane Doe does not exist")).toBe("stat does not exist") - expect(mask(String.raw`stat \\fileserver\Users\Jane Doe does not exist`)).toBe("stat does not exist") - expect(mask(String.raw`stat \\?\UNC\srv\share\Users\Jane Doe is gone`)).toBe("stat is gone") + it("recognizes ; and < as path boundaries, preserving closers", () => { + expect(mask("ENOENT;/Users/jdoe/client/a.sql")).toBe("ENOENT;") + expect(mask("failed ")).toBe("failed <>") + expect(mask("cfg=a.yml;/etc/dbt/profiles.yml;done")).toBe("cfg=a.yml;;done") }) - it("drive-relative backslash proof accepts spaced and dotted first components", () => { - expect(mask(String.raw`read C:client repo\models\private.sql failed`)).toBe("read failed") - expect(mask(String.raw`read C:.client\models\private.sql failed`)).toBe("read failed") - // slash-proven form keeps the letter-first guard: ratios never match - expect(mask("score C:8/10 fine")).toBe("score C:8/10 fine") + it("pipe is a path anchor but plain pipe prose never matches", () => { + expect(mask("ENOENT|/Users/jdoe/client/a.sql")).toBe("ENOENT|") + expect(mask("source|s3://customer-bucket/private/a.csv")).toBe("source|") + expect(mask("a|b or c|d plain prose")).toBe("a|b or c|d plain prose") }) - it("UNC home opener stays linear on adversarial input", () => { - const t0 = performance.now() - mask("\\\\" + "srv ".repeat(1500) + "x") - expect(performance.now() - t0).toBeLessThan(500) + it("shell redirection operators anchor paths", () => { + expect(mask("echo x >/Users/jdoe/client/private.log failed")).toBe("echo x > failed") + expect(mask("cmd 2>/home/jdoe/output.log crashed")).toBe("cmd 2> crashed") + expect(mask("if a > b then c")).toBe("if a > b then c") }) -}) -describe("maskString paths — fleet round 12 (rooted proof breadth, tilde home tail)", () => { - it("rooted proof accepts spaced and symbol-bearing components", () => { - expect(mask(String.raw`open \Program Files\Altimate\secret.sql denied`)).toBe("open denied") - expect(mask(String.raw`read \client repo\models\private.sql failed`)).toBe("read failed") - expect(mask(String.raw`read \#client\models\private.sql failed`)).toBe("read failed") - // regex-prose guards must keep failing the proof - expect(mask(String.raw`expected pattern \d+\.\d+ but got x`)).toBe(String.raw`expected pattern \d+\.\d+ but got x`) + it("closing delimiters anchor paths in structured stderr", () => { + expect(mask("[ENOENT]/Users/jdoe/client/a.sql")).toBe("[ENOENT]") + expect(mask("error)/Users/jdoe/client/a.sql")).toBe("error)") + expect(mask("math (a+b)/(c+d) here")).toBe("math (a+b)/(c+d) here") }) - it("tilde homes use the unconditional home tail", () => { - expect(mask("stat ~/client repo does not exist")).toBe("stat does not exist") - expect(mask("read ~jane/Client Secret now")).toBe("read now") + it("closing braces anchor paths", () => { + expect(mask("{ENOENT}/Users/jdoe/client/a.sql")).toBe("{ENOENT}") + expect(mask("json {a}/{b} template")).toBe("json {a}/{b} template") + }) + + it("shell ampersands anchor paths", () => { + expect(mask("cmd&&/Users/jdoe/client/private.sh failed")).toBe("cmd&& failed") + expect(mask("AT&T report ready")).toBe("AT&T report ready") }) }) -describe("maskString paths — fleet round 13 (multi-word PII tails, proof punctuation, bounded scans)", () => { - it("high-PII tail consumes capitalized word runs (middle names, multi-word keys)", () => { - expect(mask("/Users/Mary Jane Smith does not exist")).toBe(" does not exist") - expect(mask("C:\\Users\\Mary Jane Smith does not exist")).toBe(" does not exist") - expect(mask("s3://bucket/Client Top Secret does not exist")).toBe(" does not exist") - // lowercase prose still costs exactly one word - expect(mask("/Users/jdoe was not found on this system")).toBe(" not found on this system") +// A delimiter or quote inside a component is path content only when a +// later separator or attached dotted filename proves it; otherwise it is +// a permanent boundary (shell ;cmd, closing quotes/parens are preserved, +// and quote pairing can never shift). +describe("maskString paths — delimiters and quotes inside components — separator-proven continuation", () => { + it("treats an apostrophe followed by a word char as path content", () => { + expect(mask("read /Users/Jane/O'Connor/client/model.sql failed")) + .toBe("read failed") + expect(mask("stat /Users/Jane O'Connor went missing")).toBe("stat went missing") }) - it("rooted proof accepts parenthesized/dotted components", () => { - expect(mask(String.raw`open \Program Files (x86)\Altimate\secret.sql denied`)).toBe("open denied") - expect(mask(String.raw`expected pattern \d+\.\d+ but got x`)).toBe(String.raw`expected pattern \d+\.\d+ but got x`) + it("still treats a closing apostrophe as a quote delimiter", () => { + // path masks inside the quotes, closing quote survives for the quote rule + expect(mask("open '/Users/jdoe/x.sql' failed")).toBe("open ? failed") + expect(mask("open '/Users/jdoe/x.sql' failed")).not.toContain("jdoe") }) - it("delimiter runs are linear: bounded proof scans", () => { - let t0 = performance.now() - mask("C:\\a\\b" + ")".repeat(2000) + "\\c") - expect(performance.now() - t0).toBeLessThan(200) - t0 = performance.now() - mask(")a".repeat(2500) + "/x") - expect(performance.now() - t0).toBeLessThan(200) + it("continues through delimiters that a later separator proves are path content", () => { + expect(mask("read /Users/Jane Doe/client, repo/models/a.sql failed")) + .toBe("read failed") + expect(mask("open ~/reports,final/q3.csv now")).toBe("open now") }) -}) -describe("maskString paths — fleet round 14 (fs-limit bounds, shallow forms)", () => { - it("proof scans reach the filesystem component limit (255)", () => { - expect(mask("/Users/jdoe/client)" + "a".repeat(65) + "/models/private.sql leaked")).toBe(" leaked") - // delimiter runs stay linear at the raised bound - const t0 = performance.now() - mask("C:\\a\\b" + ")".repeat(2000) + "\\c") - expect(performance.now() - t0).toBeLessThan(300) + it("a delimiter without a following separator stays a boundary", () => { + expect(mask("read /app/data/x.csv, then /var/log/y.log failed")) + .toBe("read , then failed") + expect(mask("saw [/opt/dbt/a.yml], aborted")).toBe("saw [], aborted") }) - it("shallow rooted windows paths mask via the dotted-terminal proof", () => { - expect(mask(String.raw`read \client-repo\private.sql failed`)).toBe("read failed") - expect(mask(String.raw`open \Program Files\app.exe denied`)).toBe("open denied") - expect(mask(String.raw`match \bword\b boundary`)).toBe(String.raw`match \bword\b boundary`) + it("delimiters attached directly to a separator continue the path", () => { + expect(mask("read /data/a[b]/c.txt failed")).toBe("read failed") }) - it("terminal-only dot-relative paths mask when extension-bearing", () => { - expect(mask("read ./customer_secret.sql failed")).toBe("read failed") - expect(mask("read ../customer_secret.sql now")).toBe("read now") - // extensionless ./x stays out — undecidable against prose tokens - expect(mask("see ./x plain token")).toBe("see ./x plain token") + it("continues spaced components that start with symbols", () => { + expect(mask("read /Users/Jane Doe/client #1/models/a.sql failed")) + .toBe("read failed") }) -}) -describe("maskString paths — fleet round 15 (spaced shallow terminals, name particles, extensionless rooted)", () => { - it("shallow dot-relative accepts spaced dotted terminals", () => { - expect(mask("read ./Q4 final report.sql failed")).toBe("read failed") + it("delimiter lookahead accepts multi-word components", () => { + expect(mask("read /data/x, big client repo/models/a.sql done")).toBe("read done") }) - it("PII tails cross lowercase name particles and caseless scripts", () => { - expect(mask("/Users/Mary van der Berg does not exist")).toBe(" does not exist") - expect(mask("/Users/李 小 明 does not exist")).toBe(" does not exist") - // ordinary lowercase prose still costs exactly one word - expect(mask("/Users/jdoe was not found on this system")).toBe(" not found on this system") + it("continues through ) when a later separator proves it is path content", () => { + expect(mask("read /Users/jdoe/client)repo/models/a.sql failed")).toBe("read failed") + // closing paren without a following separator stays preserved + expect(mask("failed (from /opt/dbt/bin/dbt)")).toBe("failed (from )") + }) + + it("attached dotted terminal filename proves a delimiter (all rule families)", () => { + expect(mask("read /Users/jdoe/project;draft.sql")).toBe("read ") + expect(mask("C:\\Users\\jdoe\\proj;draft.sql gone")).toBe(" gone") + expect(mask("s3://bucket/dir;file.csv loaded")).toBe(" loaded") + }) + + it("attached delimiter without separator or dotted filename stays a boundary", () => { + // shell-style `;cmd` must not be eaten — documented residue + expect(mask("cd /Users/jdoe/proj;ls failed")).toBe("cd ;ls failed") + expect(mask("shell: cd /opt/app; rm -rf tmp")).toBe("shell: cd ; rm -rf tmp") }) - it("rooted proof accepts spaced-first-component extensionless paths", () => { - expect(mask(String.raw`open \Program Files\Altimate denied`)).toBe("open ") - expect(mask(String.raw`match \bword\b boundary`)).toBe(String.raw`match \bword\b boundary`) + it("keeps literal backslashes as path content in slash-delimited rules", () => { + expect(mask(String.raw`read s3://bucket/customer data\raw/models/a.sql failed`)).toBe("read failed") + expect(mask(String.raw`stat /opt/a\b/c/d.sql failed`)).toBe("stat failed") }) -}) -describe("maskString paths — fleet round 16 (case folding out of the tail gate)", () => { - it("home/cloud rules are engine-independent: no i flag, hand-expanded case", () => { - // lowercase prose after a home path costs exactly one word — this is the - // assertion that silently broke under V8's conformant /iu folding - expect(mask("/Users/jdoe was not found on this system")).toBe(" not found on this system") - // the case-insensitive coverage the i flag used to provide still holds - expect(mask("stat c:\\users\\jane doe missing")).toBe("stat missing") - expect(mask("check /users/jane went missing")).toBe("check missing") - expect(mask("read S3://Bucket/Key now")).toBe("read ") - expect(mask(String.raw`stat \\?\unc\srv\share\Users\Jane Doe gone`)).toBe("stat gone") + it("backslash-permissive run class never bleeds into following prose", () => { + expect(mask(String.raw`use /pattern/ with \d+ tokens`)).toBe(String.raw`use with \d+ tokens`) + expect(mask(String.raw`glob /opt/data/*.sql \n escaped`)).toBe(String.raw`glob \n escaped`) }) -}) -describe("maskString paths — fleet round 17 (legacy home roots, longer extensions)", () => { - it("legacy Documents and Settings roots get home treatment", () => { - expect(mask(String.raw`stat C:\Documents and Settings\Jane Doe does not exist`)) - .toBe("stat does not exist") + it("continues through quote characters a later separator proves", () => { + expect(mask(`read /Users/jdoe/client"repo/models/a.sql failed`)).toBe("read failed") + // paired inline-code backticks stay intact + expect(mask("run `dbt build` in `/opt/proj/app` now")).toBe("run `dbt build` in `` now") }) - it("extensions up to 14 chars count as proof and as prose guards", () => { - expect(mask("read ./customer_secret.properties failed")).toBe("read failed") - expect(mask("read ../private.configuration now")).toBe("read now") - // the suppression direction: prose after a long-extension file survives - expect(mask("/Users/jdoe/app.properties was deleted")).toBe(" was deleted") + it("a closing quote directly before a slash stays a boundary (pairing never shifts)", () => { + // the eaten quote used to shift pairing and leak the next quoted value + expect(mask(`read "/a/b"/c "secret data" end`)).toBe("read ?/c ? end") + // mid-component quotes (non-empty run before the separator) still mask + expect(mask(`read /Users/jdoe/client"repo/models/a.sql failed`)).toBe("read failed") }) -}) -describe("maskString paths — fleet round 18 (mixed spaced runs in delimiter proofs)", () => { it("a delimiter proof may combine an initial run with spaced runs", () => { expect(mask("/Users/jdoe/client)repo name/models/private.sql failed")).toBe(" failed") // all four boundary controls hold @@ -608,179 +590,179 @@ describe("maskString paths — fleet round 18 (mixed spaced runs in delimiter pr expect(mask(`read "/a/b"/c "secret data" end`)).toBe("read ?/c ? end") expect(mask("cd /Users/jdoe/proj;ls failed")).toBe("cd ;ls failed") }) - - it("unified proof stays linear", () => { - const t0 = performance.now() - mask("C:\\a\\b" + ")".repeat(2000) + "\\c") - expect(performance.now() - t0).toBeLessThan(300) - }) }) -describe("maskString paths — fleet round 19 (redirects, single-component absolutes)", () => { - it("shell redirection operators anchor paths", () => { - expect(mask("echo x >/Users/jdoe/client/private.log failed")).toBe("echo x > failed") - expect(mask("cmd 2>/home/jdoe/output.log crashed")).toBe("cmd 2> crashed") - expect(mask("if a > b then c")).toBe("if a > b then c") - }) - - it("single-component absolute files mask; URL interiors never do", () => { - expect(mask("read /customer_secret.sql failed")).toBe("read failed") - // scheme colons and protocol-relative slashes are excluded from the - // shallow-absolute form — URL pins elsewhere in this suite stay green - expect(mask("see https://community.snowflake.com/s/ip-not-allowed for details")) - .toBe("see https://community.snowflake.com/s/ip-not-allowed for details") +// Extensions serve as proof (shallow explicit paths) and as prose guards +// (suppression after dotted files); bounded at 30 chars, unicode-aware. +describe("maskString paths — filename extensions", () => { + it("recognizes long extensions so following prose survives", () => { + expect(mask("/Users/jdoe/data.parquet was deleted")) + .toBe(" was deleted") + expect(mask("wrote /home/jdoe/out/events.jsonl then stopped")) + .toBe("wrote then stopped") }) -}) -describe("maskString paths — fleet round 20 (closer anchors, forward-slash UNC)", () => { - it("closing delimiters anchor paths in structured stderr", () => { - expect(mask("[ENOENT]/Users/jdoe/client/a.sql")).toBe("[ENOENT]") - expect(mask("error)/Users/jdoe/client/a.sql")).toBe("error)") - expect(mask("math (a+b)/(c+d) here")).toBe("math (a+b)/(c+d) here") + it("terminal dotted filename may span multiple words", () => { + expect(mask("read /Users/jdoe/reports/Q4 final reviewed version.sql failed")) + .toBe("read failed") }) - it("forward-slash UNC masks; dotted hosts stay protocol-relative URLs", () => { - expect(mask("open //fileserver/client-share/private.sql failed")).toBe("open failed") - // dotless-server discriminator: dotted first components are URL hosts - expect(mask("fetch //cdn.example.com/lib.js failed")).toBe("fetch //cdn.example.com/lib.js failed") + it("extensions up to 14 chars count as proof and as prose guards", () => { + expect(mask("read ./customer_secret.properties failed")).toBe("read failed") + expect(mask("read ../private.configuration now")).toBe("read now") + // the suppression direction: prose after a long-extension file survives + expect(mask("/Users/jdoe/app.properties was deleted")).toBe(" was deleted") }) -}) -describe("maskString paths — fleet round 21 (colon-anchored bare files)", () => { - it("errno-style colon prefixes anchor single-component files", () => { - expect(mask("ENOENT:/private.sql failed")).toBe("ENOENT: failed") - // the protocol-relative guard alone protects URLs — full pin set stays green - expect(mask("fetch //cdn.example.com/lib.js failed")).toBe("fetch //cdn.example.com/lib.js failed") + it("hyphenated extensions prove shallow paths", () => { + expect(mask("read ./client.code-workspace failed")).toBe("read failed") + expect(mask(String.raw`open \client\private.code-workspace denied`)).toBe("open denied") }) -}) -describe("maskString paths — fleet round 22 (forward-slash UNC homes)", () => { - it("forward-slash UNC homes get the high-PII tail", () => { - expect(mask("stat //fileserver/share/Users/Jane Doe does not exist")) - .toBe("stat does not exist") - expect(mask("fetch //cdn.example.com/lib.js failed")).toBe("fetch //cdn.example.com/lib.js failed") + it("unicode extensions prove explicit paths", () => { + expect(mask("read ./customer.配置 failed")).toBe("read failed") + expect(mask("read /秘密.配置 now")).toBe("read now") }) -}) -describe("maskString paths — fleet round 23 (NBSP components, hyphenated extensions)", () => { - it("nonbreaking spaces continue components; tabs stay boundaries", () => { - expect(mask("read /Users/Jane Doe/client/model.sql failed")).toBe("read failed") - expect(mask("/Users/jdoe/client\tsecret/models/a.sql")).toBe(" secret/models/a.sql") + it("combining marks count in extensions", () => { + expect(mask("read ./customer.é failed")).toBe("read failed") }) - it("hyphenated extensions prove shallow paths", () => { - expect(mask("read ./client.code-workspace failed")).toBe("read failed") - expect(mask(String.raw`open \client\private.code-workspace denied`)).toBe("open denied") + it("extensions up to 30 chars prove explicit paths", () => { + expect(mask("read ./customer.sublime-workspace failed")).toBe("read failed") + expect(mask("/Users/jdoe/app.properties was deleted")).toBe(" was deleted") }) }) -describe("maskString paths — fleet round 24 (PowerShell tilde paths)", () => { - it("backslash tilde paths mask; regex tilde prose never does", () => { - expect(mask(String.raw`read ~\client-repo\models\private.sql failed`)).toBe("read failed") - expect(mask(String.raw`read ~jane\Client Secret\a.sql now`)).toBe("read now") - expect(mask(String.raw`regex ~\d+ used here`)).toBe(String.raw`regex ~\d+ used here`) - expect(mask("approx ~5 items left")).toBe("approx ~5 items left") +// The must-not-mask set and the by-design residue: public URLs, non-path +// slashes, mid-sentence prose, column-aligned log output, and tab as a +// field boundary. +describe("maskString paths — must NOT mask — prose, URLs, and documented boundaries", () => { + it("does NOT mask non-path slashes", () => { + expect(mask("content-type application/json rejected")).toBe("content-type application/json rejected") + expect(mask("requirement dbt-core~=1.11.0 not satisfied")).toBe("requirement dbt-core~=1.11.0 not satisfied") + expect(mask("on 8/17/2026 at 3:2 ratio 1/2")).toBe("on 8/17/2026 at 3:2 ratio 1/2") + expect(mask("endpoint /mcp returned 404")).toBe("endpoint /mcp returned 404") }) -}) -describe("maskString paths — fleet round 25 (dotfile tildes, letter-bearing drive proofs, NBSP particles)", () => { - it("dot-prefixed backslash tilde paths mask; escaped-dot regex prose never does", () => { - expect(mask(String.raw`read ~\.config\altimate\secret.json failed`)).toBe("read failed") - expect(mask(String.raw`sub ~\.\d pattern`)).toBe(String.raw`sub ~\.\d pattern`) + it("leaves public URL paths to the URL rules (never word-anchored)", () => { + expect(mask("POST https://api.openai.com/v1/chat/completions failed")) + .toBe("POST https://api.openai.com/v1/chat/completions failed") + // internal hosts keep their existing, stronger treatment + expect(mask("GET http://localhost:8080/api/x timed out")).toContain("") }) - it("slash drive-relative proof needs only a letter; ratios stay out", () => { - expect(mask("read C:.client/models/private.sql failed")).toBe("read failed") - expect(mask("read C:client repo/models/private.sql failed")).toBe("read failed") - expect(mask("score C:8/10 fine")).toBe("score C:8/10 fine") - expect(mask("avg C:8.5/10 shown")).toBe("avg C:8.5/10 shown") + it("still leaves public https doc-links alone (provider help URLs)", () => { + expect(mask("see https://community.snowflake.com/s/ip-not-allowed for details")) + .toBe("see https://community.snowflake.com/s/ip-not-allowed for details") }) - it("name particles work across nonbreaking spaces", () => { - expect(mask("/Users/Mary van der Berg does not exist")).toBe(" does not exist") + it("still never eats mid-sentence prose after a path", () => { + expect(mask("open /app/x.sql failed with error")).toBe("open failed with error") + expect(mask("File not found: /app/models/a.sql was deleted upstream")) + .toBe("File not found: was deleted upstream") }) -}) -describe("maskString paths — fleet round 26 (one-char tilde components)", () => { - it("one-char components mask when the next component proves the path", () => { - expect(mask(String.raw`read ~\a\client\private.sql failed`)).toBe("read failed") - expect(mask(String.raw`chain ~\w\d+ used`)).toBe(String.raw`chain ~\w\d+ used`) - expect(mask(String.raw`escapes ~\n\r\t here`)).toBe(String.raw`escapes ~\n\r\t here`) + it("colon anchors never bite into URLs", () => { + expect(mask("POST https://api.openai.com/v1/chat/completions failed")) + .toBe("POST https://api.openai.com/v1/chat/completions failed") + expect(mask("fetch //cdn.example.com/lib.js failed")) + .toBe("fetch //cdn.example.com/lib.js failed") + expect(mask("connect db-host:5432/postgres refused")) + .toBe("connect db-host:5432/postgres refused") }) -}) -describe("maskString paths — fleet round 27 (one-char rooted, brace anchors, unicode extensions)", () => { - it("one-char rooted components defer proof to the next component", () => { - expect(mask(String.raw`read \a\client\private.sql failed`)).toBe("read failed") - expect(mask(String.raw`escape chain \n\r\t here`)).toBe(String.raw`escape chain \n\r\t here`) + it("tab is a field boundary, not path continuation (documented residue)", () => { + // tab-delimited log columns after a path must survive; the bare relative + // fragment after the tab is the documented undecidable-fragment boundary + expect(mask("/Users/jdoe/client\tsecret/models/a.sql")).toBe(" secret/models/a.sql") }) - it("closing braces anchor paths", () => { - expect(mask("{ENOENT}/Users/jdoe/client/a.sql")).toBe("{ENOENT}") - expect(mask("json {a}/{b} template")).toBe("json {a}/{b} template") + it("three or more spaces stay a column boundary", () => { + expect(mask("read /opt/data/x.sql 404 NotFound")).toBe("read 404 NotFound") }) +}) - it("unicode extensions prove explicit paths", () => { - expect(mask("read ./customer.配置 failed")).toBe("read failed") - expect(mask("read /秘密.配置 now")).toBe("read now") +describe("maskString paths — composition with the rest of the masking chain", () => { + it("composes with the earlier rules in the chain", () => { + // credential first, then the path + expect(mask("sk-abcdefghijklmnopqrstuvwx leaked into /Users/jdoe/log.txt")) + .toBe("sk-*** leaked into ") + // quoted path still collapses via the quote rule — no leak either way + expect(mask("open '/Users/jdoe/x.sql' failed")).not.toContain("jdoe") }) }) -describe("maskString paths — fleet round 28 (ampersands, bounds, marks)", () => { - it("shell ampersands anchor paths", () => { - expect(mask("cmd&&/Users/jdoe/client/private.sh failed")).toBe("cmd&& failed") - expect(mask("AT&T report ready")).toBe("AT&T report ready") +// Every quantified unit is space- or separator-anchored with disjoint +// inner classes (unambiguous parse => linear time); proof scans are +// bounded by the filesystem's 255-byte component limit; the nested- +// quantifier ReDoS shape is banned. +describe("maskString paths — performance — linearity and DoS budgets", () => { + it("adversarial delimiter + slash-free run completes in linear time", () => { + const t0 = performance.now() + mask("/a/b, " + "a".repeat(5000)) + expect(performance.now() - t0).toBeLessThan(500) // was exponential: ~2s at 28 chars }) - it("drive-relative letter scan reaches the component limit", () => { - expect(mask("read C:" + "1".repeat(65) + "a/models/private.sql failed")).toBe("read failed") + it("multi-word lookahead stays linear on adversarial input", () => { + const t0 = performance.now() + mask("/a/b, " + "word ".repeat(1000)) + expect(performance.now() - t0).toBeLessThan(500) }) - it("explicit shallow paths take many spaced words; deep tails keep the prose cap", () => { - expect(mask("read ./Q1 final audited customer revenue report.sql failed")).toBe("read failed") - // prose after a deep extensionless path must NOT be chased to a dotted token - expect(mask("read /opt/x error reading the project config.yml here")) - .toBe("read error reading the project config.yml here") + it("extension-proof delimiter branch stays linear on adversarial input", () => { + let t0 = performance.now() + mask("/a/b/x;" + "a".repeat(5000)) + expect(performance.now() - t0).toBeLessThan(500) + t0 = performance.now() + mask("/a/b/x;" + "a.".repeat(2500)) + expect(performance.now() - t0).toBeLessThan(500) }) - it("combining marks count in extensions", () => { - expect(mask("read ./customer.é failed")).toBe("read failed") + it("widened run class stays linear on adversarial input", () => { + const t0 = performance.now() + mask("s3://b/k " + "w\\x ".repeat(1200)) + expect(performance.now() - t0).toBeLessThan(500) }) -}) -describe("maskString paths — fleet round 29 (symbol components, longer extensions)", () => { - it("rooted components carry + and & at length 3+; quantified escapes never do", () => { - expect(mask(String.raw`read \C++ Projects\client\secret.sql failed`)).toBe("read failed") - expect(mask(String.raw`read \R&D\client\secret.sql failed`)).toBe("read failed") - expect(mask(String.raw`pattern \n+\r+ found`)).toBe(String.raw`pattern \n+\r+ found`) + it("double-space continuation stays linear on adversarial input", () => { + const t0 = performance.now() + mask("/Users/x/y" + " zz".repeat(1200)) + expect(performance.now() - t0).toBeLessThan(500) }) - it("extensions up to 30 chars prove explicit paths", () => { - expect(mask("read ./customer.sublime-workspace failed")).toBe("read failed") - expect(mask("/Users/jdoe/app.properties was deleted")).toBe(" was deleted") + it("spaced prefix components stay linear on adversarial input", () => { + const t0 = performance.now() + mask("./" + "a b ".repeat(1500)) + expect(performance.now() - t0).toBeLessThan(500) }) -}) -describe("maskString paths — layer zero (known-prefix literals) + fast path", () => { - const os = require("os") - it("paths under the local home mask by exact literal — any username shape", () => { - const home = os.homedir() - expect(mask(`ENOENT: no such file ${home}/client repo/秘密 file.sql retry`)).toBe("ENOENT: no such file retry") - expect(mask(`stat ${home} does not exist`)).toBe("stat does not exist") + it("UNC home opener stays linear on adversarial input", () => { + const t0 = performance.now() + mask("\\\\" + "srv ".repeat(1500) + "x") + expect(performance.now() - t0).toBeLessThan(500) }) - it("paths under cwd mask by exact literal", () => { - expect(mask(`failed in ${process.cwd()}/models/private.sql now`)).toBe("failed in now") + it("delimiter runs are linear: bounded proof scans", () => { + let t0 = performance.now() + mask("C:\\a\\b" + ")".repeat(2000) + "\\c") + expect(performance.now() - t0).toBeLessThan(200) + t0 = performance.now() + mask(")a".repeat(2500) + "/x") + expect(performance.now() - t0).toBeLessThan(200) }) - it("shallow explicit windows terminals mask (dual-flagged symmetry gap)", () => { - expect(mask(String.raw`read .\customer_secret.sql failed`)).toBe("read failed") - expect(mask(String.raw`read \customer_secret.sql failed`)).toBe("read failed") - expect(mask(String.raw`expected \d+\.\d+ but got x`)).toBe(String.raw`expected \d+\.\d+ but got x`) + it("proof scans reach the filesystem component limit (255)", () => { + expect(mask("/Users/jdoe/client)" + "a".repeat(65) + "/models/private.sql leaked")).toBe(" leaked") + // delimiter runs stay linear at the raised bound + const t0 = performance.now() + mask("C:\\a\\b" + ")".repeat(2000) + "\\c") + expect(performance.now() - t0).toBeLessThan(300) }) - it("separator-free strings take the fast path unchanged", () => { - expect(mask("invalid_token: authentication expired, retry later")) - .toBe("invalid_token: authentication expired, retry later") + it("unified proof stays linear", () => { + const t0 = performance.now() + mask("C:\\a\\b" + ")".repeat(2000) + "\\c") + expect(performance.now() - t0).toBeLessThan(300) }) }) From ced57f47dfddb342b3e424358981df3ff24e3b0a Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 20:13:25 +0800 Subject: [PATCH 42/46] fix: layer-zero ordering and boundaries, drive-relative single files - known-prefix literals run AFTER the structural rules: replacing the prefix first stripped the opener and orphaned terminal spaced components ($HOME/client repo leaked 'repo'); structure now sees the original string and literals mop up what it missed - literal matchers carry a token-boundary lookbehind so they never fire mid-URL or mid-token - drive-relative single-component files (C:secret.sql) mask via a letter-first dotted-terminal proof; the main gate opens on a boundary drive-colon since such strings have no separator --- packages/opencode/src/altimate/telemetry/index.ts | 10 ++++++---- .../test/telemetry/mask-file-paths.test.ts | 14 +++++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 3905f6711..2acde4668 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -45,6 +45,7 @@ const log = Log.create({ service: "telemetry" }) * | extend err = tostring(customDimensions.error_class) * | summarize count() by err */ + // altimate_change end /** True when a test runner is driving the process rather than a real user session. @@ -113,13 +114,13 @@ const PM_KNOWN_PREFIXES: RegExp[] = [] for (const root of [process.cwd(), os.homedir()]) { if (!root || root.length < 4) continue for (const v of new Set([root, root.replace(/\\/g, "\\\\"), root.replace(/\\/g, "/")])) { - PM_KNOWN_PREFIXES.push(new RegExp(pmEscape(v), "gi")) + PM_KNOWN_PREFIXES.push(new RegExp("(?") + if (out.includes("/") || out.includes("\\") || /(?") .replace(PATH_RULES.posix, "$1") .replace(PATH_RULES.tilde, "$1") + for (const re of PM_KNOWN_PREFIXES) out = out.replace(re, "") + out = out // a literal-prefix mask followed by a structurally-masked remainder // collapses to one marker .replace(/(?:[\\/]?)+/g, "") diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index 738546f43..de69b0292 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -39,13 +39,24 @@ describe("maskString paths — preprocessing — escape stripping, known-prefix it("paths under the local home mask by exact literal — any username shape", () => { const home = os.homedir() expect(mask(`ENOENT: no such file ${home}/client repo/秘密 file.sql retry`)).toBe("ENOENT: no such file retry") - expect(mask(`stat ${home} does not exist`)).toBe("stat does not exist") + expect(mask(`stat ${home} does not exist`)).toBe("stat not exist") }) it("paths under cwd mask by exact literal", () => { expect(mask(`failed in ${process.cwd()}/models/private.sql now`)).toBe("failed in now") }) + it("literal mop-up never orphans a spaced terminal (structure runs first)", () => { + const home = os.homedir() + expect(mask(`stat ${home}/client repo does not exist`)).toBe("stat does not exist") + }) + + it("known-prefix literals never fire mid-token (URL interiors, longer tokens)", () => { + const home = os.homedir() + expect(mask(`see https://example.com${home}/docs now`)).toBe(`see https://example.com${home}/docs now`) + expect(mask(`at path:${home}/x.sql end`)).toBe("at path: end") + }) + it("separator-free strings take the fast path unchanged", () => { expect(mask("invalid_token: authentication expired, retry later")) .toBe("invalid_token: authentication expired, retry later") @@ -322,6 +333,7 @@ describe("maskString paths — windows paths — drive, UNC, relative, and roote }) it("shallow explicit windows terminals mask (POSIX symmetry)", () => { + expect(mask("read C:customer_secret.sql failed")).toBe("read failed") expect(mask(String.raw`read .\customer_secret.sql failed`)).toBe("read failed") expect(mask(String.raw`read \customer_secret.sql failed`)).toBe("read failed") expect(mask(String.raw`expected \d+\.\d+ but got x`)).toBe(String.raw`expected \d+\.\d+ but got x`) From a32d716a569eebefbc7f6ccd51165d7db668122d Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 22:23:24 +0800 Subject: [PATCH 43/46] fix: tilde symbol components, drive-relative terminal filename breadth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - the backslash-tilde proof reuses the rooted proof's component classes (+& at length 3+, spaced continuations): ~\C++ Projects\... masks - the drive-relative terminal proof accepts any (spaced) run whose EXTENSION carries a letter — C:123_report.sql, C:.env.local, and C:customer secret.sql mask while versions and ratios (C:8.5, C:1.2.3) have all-numeric extensions and never qualify; the fast-path gate opens on any non-space after a boundary drive-colon --- packages/opencode/src/altimate/telemetry/index.ts | 6 +++--- .../test/telemetry/mask-file-paths.test.ts | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 2acde4668..6b681bd8e 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -120,10 +120,10 @@ for (const root of [process.cwd(), os.homedir()]) { const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:" + [pmCI("gs"), pmCI("s3") + "[anAN]?", pmCI("abfs") + "[sS]?", pmCI("wasb") + "[sS]?", pmCI("adl"), pmCI("dbfs"), pmCI("hdfs")].join("|") + "):\\/\\/|" + pmCI("file") + ":\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "gu"), windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|(?:\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?|(? { + expect(mask("read C:123_customer_secret.sql failed")).toBe("read failed") + expect(mask("read C:.env.local failed")).toBe("read failed") + expect(mask("read C:customer secret.sql failed")).toBe("read failed") + // versions and ratios have all-numeric extensions and never qualify + expect(mask("avg C:8.5 shown")).toBe("avg C:8.5 shown") + expect(mask("version C:1.2.3 tagged")).toBe("version C:1.2.3 tagged") + }) + it("shallow explicit windows terminals mask (POSIX symmetry)", () => { expect(mask("read C:customer_secret.sql failed")).toBe("read failed") expect(mask(String.raw`read .\customer_secret.sql failed`)).toBe("read failed") @@ -418,6 +427,11 @@ describe("maskString paths — tilde paths", () => { expect(mask(String.raw`sub ~\.\d pattern`)).toBe(String.raw`sub ~\.\d pattern`) }) + it("symbol-bearing first components mask (parity with rooted proofs)", () => { + expect(mask(String.raw`read ~\C++ Projects\client\secret.sql failed`)).toBe("read failed") + expect(mask(String.raw`read ~\R&D\client\secret.sql failed`)).toBe("read failed") + }) + it("one-char components mask when the next component proves the path", () => { expect(mask(String.raw`read ~\a\client\private.sql failed`)).toBe("read failed") expect(mask(String.raw`chain ~\w\d+ used`)).toBe(String.raw`chain ~\w\d+ used`) From 35320edc2f361dc524c96bdc5bc58f88a259d15e Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 22:44:51 +0800 Subject: [PATCH 44/46] fix: JSON-doubled home separators, honest NBSP tests, comment accuracy - windowsHome's structured separators accept doubling ({1,2}), so JSON-printed UNC/drive homes keep spaced-name protection - the NBSP tests now construct U+00A0 explicitly (one relied on an invisible literal byte, the other tested plain spaces) - layer-ordering comment corrected (literals mop up AFTER structure); the trailing-word doctrine now states the spaced-filename interior reach; a duplicated timing probe got a distinct spaced-run shape --- packages/opencode/src/altimate/telemetry/index.ts | 13 ++++++++----- .../opencode/test/telemetry/mask-file-paths.test.ts | 11 ++++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 6b681bd8e..11115d951 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -101,9 +101,10 @@ const PM_WC = "(?:[\\p{L}\\p{N}_#@().'-]{2,}|[\\p{L}\\p{N}_#@().'+&-]{3,})" const PM_WCC = "[\\p{L}\\p{N}_#@().'+&-]+" const pmSpFileX = (sep: string) => "(?:(?:" + PM_SP + "{1,2}" + pmR(sep) + "{1,64}){1,12}(?<=" + PM_EXT + "))?" const pmTail = (sep: string, term: string) => pmSpan(sep) + "*" + pmChunks(sep) + pmSpFile(sep) + term -// Layer zero — the local user's home and cwd are KNOWN literal values, so -// paths under them are replaced by exact match before any structural rule -// runs. Exact matching handles every username shape (spaces, NBSP, unicode) +// Known-prefix literals — the local user's home and cwd are KNOWN values, +// replaced by exact match AFTER the structural rules (structure must see the +// original string: stripping the prefix first orphans terminal spaced +// components). The literal pass mops up whatever structure missed. Exact matching handles every username shape (spaces, NBSP, unicode) // with zero false positives; the structural rules below remain for paths the // literals cannot know (other drives, UNC shares, cloud URIs, relative // forms, WSL-mounted homes). Variants cover JSON-doubled backslashes and @@ -119,7 +120,7 @@ for (const root of [process.cwd(), os.homedir()]) { } const PATH_RULES = { cloud: new RegExp(PM_ANCHOR + "(?:(?:" + [pmCI("gs"), pmCI("s3") + "[anAN]?", pmCI("abfs") + "[sS]?", pmCI("wasb") + "[sS]?", pmCI("adl"), pmCI("dbfs"), pmCI("hdfs")].join("|") + "):\\/\\/|" + pmCI("file") + ":\\/{1,3})" + pmTail(SEP_P, PM_TERM_UNC), "gu"), - windowsHome: new RegExp(PM_ANCHOR + "(?:(?:\\\\\\\\\\?\\\\)?[A-Za-z]:" + SEP_W + "?|(?:\\\\\\\\(?:\\?\\\\" + pmCI("unc") + "\\\\)?|(? { expect(mask(String.raw`stat \\?\C:\Users\Jane Doe\models\a.sql failed`)).toBe("stat failed") }) + it("JSON-doubled separators still get home treatment", () => { + expect(mask(String.raw`stat \\fileserver\\Users\\Jane Doe gone`)).toBe("stat gone") + expect(mask(String.raw`stat C:\\Users\\Jane Doe missing`)).toBe("stat missing") + }) + it("home rules reach through spaced prefix components and UNC shares", () => { expect(mask("stat /mnt disk/c/Users/Jane Doe does not exist")).toBe("stat does not exist") expect(mask(String.raw`stat \\fileserver\Users\Jane Doe does not exist`)).toBe("stat does not exist") @@ -212,7 +217,7 @@ describe("maskString paths — terminal components and tails — spaced, unicode }) it("name particles work across nonbreaking spaces", () => { - expect(mask("/Users/Mary van der Berg does not exist")).toBe(" does not exist") + expect(mask("/Users/Mary\u00A0van\u00A0der\u00A0Berg does not exist")).toBe(" does not exist") }) }) @@ -786,9 +791,9 @@ describe("maskString paths — performance — linearity and DoS budgets", () => expect(performance.now() - t0).toBeLessThan(300) }) - it("unified proof stays linear", () => { + it("mixed spaced-run proofs stay linear", () => { const t0 = performance.now() - mask("C:\\a\\b" + ")".repeat(2000) + "\\c") + mask("/a/b;" + "word ".repeat(1500) + ";tail") expect(performance.now() - t0).toBeLessThan(300) }) }) From 22f0c541f903315ef0af5b9e25cf04b755733957 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Wed, 19 Aug 2026 22:55:59 +0800 Subject: [PATCH 45/46] chore: rewrap known-prefix comment to block style --- packages/opencode/src/altimate/telemetry/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 11115d951..e9d9888cb 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -104,8 +104,9 @@ const pmTail = (sep: string, term: string) => pmSpan(sep) + "*" + pmChunks(sep) // Known-prefix literals — the local user's home and cwd are KNOWN values, // replaced by exact match AFTER the structural rules (structure must see the // original string: stripping the prefix first orphans terminal spaced -// components). The literal pass mops up whatever structure missed. Exact matching handles every username shape (spaces, NBSP, unicode) -// with zero false positives; the structural rules below remain for paths the +// components). The literal pass mops up whatever structure missed. Exact +// matching handles every username shape (spaces, NBSP, unicode) with zero +// false positives; the structural rules below remain for paths the // literals cannot know (other drives, UNC shares, cloud URIs, relative // forms, WSL-mounted homes). Variants cover JSON-doubled backslashes and // swapped separators. Same approach as Salesforce's telemetry GDPR scrub From 4acad5ea497f1d458574ae7f5bdba9a37b59fea7 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Fri, 21 Aug 2026 02:31:22 +0800 Subject: [PATCH 46/46] fix: bounded bridges, linear drive-colon, fresh cwd, growth tests, FQDN homes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the human review's five blockers: - spaced continuation is now a PROOF, not a reach: bridge words use a class excluding delimiters and colon (prose 'failed,' / 'https:' can never join), at most two words may bridge, and the proving separator must lead to a letter-bearing chain — so URLs, dates, fractions, MIME types, and source refs no longer swallow the clause between a path and a later slash. classification is restored. An old-vs-new corpus diff (800 live messages) shows one real message regaining its swallowed diagnostic, zero leaks. - drive-colon quadratic eliminated three ways: 8 KB entry truncation, a plausible-filename gate for separator-free input, and {1,256} bounds on every opener run ('a:' x 8000: 2986ms -> 1ms). The pre-existing email-rule quadratic gets a left boundary. - known-prefix literals: homedir cached once, cwd variants rebuilt whenever process.cwd() changes (the CLI chdirs after import). - timing tests assert growth ratios at two sizes (median-of-3), not single-size wall clocks; the drive-colon shape is covered explicitly. - the home rule accepts FQDN UNC hosts (//server.example.com/... /Users/... masks); schemed public URLs remain preserved. --- .../opencode/src/altimate/telemetry/index.ts | 51 ++++-- .../test/telemetry/mask-file-paths.test.ts | 154 +++++++++++------- 2 files changed, 136 insertions(+), 69 deletions(-) diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index e9d9888cb..321c9af2b 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -89,9 +89,14 @@ const SEP_W = "[\\\\\\/]" // never rely on the divergence) const pmCI = (w: string) => w.split("").map((c) => ("[" + c + c.toUpperCase() + "]")).join("") const pmR = (sep: string) => (sep === SEP_P ? PM_R_P : PM_R) +const PM_WR = "(?:[^\\s\\/\\\\'\"`,;:\\]}>]|'(?=[\\p{L}\\p{N}_]))" +const PM_WR_P = "(?:[^\\s\\/'\"`,;:\\]}>]|'(?=[\\p{L}\\p{N}_]))" +const pmWR = (sep: string) => (sep === SEP_P ? PM_WR_P : PM_WR) +const PM_SEG_L = "(?=[^\\s'\"`]{0,128}[\\p{L}\\p{M}]|[\\s'\"`,;)\\]}>]|$)" const pmSpan = (sep: string) => - "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "{0,256}(?:" + PM_SP + "{1,2}" + pmR(sep) + "{1,64}){0,6}(?:" + sep + "|" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))|[\"'`](?=" + pmR(sep) + "{1,256}(?:" + PM_SP + "{1,2}" + pmR(sep) + "{1,64}){0,6}(?:" + sep + "|" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?]))))" -const pmChunks = (sep: string) => "(?:(?:" + PM_SP + "{1,2}" + pmR(sep) + "+)+" + sep + pmSpan(sep) + "*)*" + "(?:[^\\s'\"`)\\]},;>]|'(?=[\\p{L}\\p{N}_])|[,;)\\]}>](?=" + pmR(sep) + "{0,256}(?:" + PM_SP + "{1,2}" + pmWR(sep) + "{1,64}){0,2}(?:" + sep + PM_SEG_L + "|" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?])))|[\"'`](?=" + pmR(sep) + "{1,256}(?:" + PM_SP + "{1,2}" + pmWR(sep) + "{1,64}){0,2}(?:" + sep + PM_SEG_L + "|" + PM_EXT + "(?=$|[\\s.,;:)\\]}!?]))))" +const pmChunks = (sep: string) => + "(?:(?:" + PM_SP + "{1,2}" + pmWR(sep) + "{1,64}){1,2}" + sep + PM_SEG_L + pmSpan(sep) + "*){0,2}" // terminal dotted filename: up to four spaced words that END in an extension const pmSpFile = (sep: string) => "(?:(?:" + PM_SP + "{1,2}" + pmR(sep) + "+){1,4}(?<=" + PM_EXT + "))?" const PM_TERM_COND = "(?:(? pmSpan(sep) + "*" + pmChunks(sep) // swapped separators. Same approach as Salesforce's telemetry GDPR scrub // (os.homedir() literal) and gatsby-telemetry's cleanPaths (cwd prefixes). const pmEscape = (v: string) => v.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&") -const PM_KNOWN_PREFIXES: RegExp[] = [] -for (const root of [process.cwd(), os.homedir()]) { - if (!root || root.length < 4) continue +const pmPrefixVariants = (root: string): RegExp[] => { + if (!root || root.length < 4) return [] + const out: RegExp[] = [] for (const v of new Set([root, root.replace(/\\/g, "\\\\"), root.replace(/\\/g, "/")])) { - PM_KNOWN_PREFIXES.push(new RegExp("(? 8192) s = s.slice(0, 8192) let out = s // ANSI CSI sequences (colored subprocess stderr) would otherwise split // tokens so neither credential nor path rules can see them @@ -1456,7 +1481,7 @@ export namespace Telemetry { .replace(/Bearer\s+[A-Za-z0-9._-]{20,}/gi, "Bearer ***") // Fast path: a string with no separator cannot contain a path — skip the // whole path stack (most telemetry strings carry no path at all). - if (out.includes("/") || out.includes("\\") || /(?") .replace(PATH_RULES.posix, "$1") .replace(PATH_RULES.tilde, "$1") - for (const re of PM_KNOWN_PREFIXES) out = out.replace(re, "") + for (const re of pmKnownPrefixes()) out = out.replace(re, "") out = out // a literal-prefix mask followed by a structurally-masked remainder // collapses to one marker @@ -1499,7 +1524,7 @@ export namespace Telemetry { return out // altimate_change end // Email addresses — providers occasionally echo caller identity in error text. - .replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, "") + .replace(/(?") // Internal hostnames in URLs — keeps parity with `parseAPICallError`'s // `maskInternalHost` so an error message containing the same URL doesn't // leak through telemetry while metadata.url is masked. Covers: diff --git a/packages/opencode/test/telemetry/mask-file-paths.test.ts b/packages/opencode/test/telemetry/mask-file-paths.test.ts index a01221302..23b14523d 100644 --- a/packages/opencode/test/telemetry/mask-file-paths.test.ts +++ b/packages/opencode/test/telemetry/mask-file-paths.test.ts @@ -569,8 +569,13 @@ describe("maskString paths — delimiters and quotes inside components — separ .toBe("read failed") }) - it("delimiter lookahead accepts multi-word components", () => { - expect(mask("read /data/x, big client repo/models/a.sql done")).toBe("read done") + it("delimiter bridges are bounded to two spaced words", () => { + // two spaced words still prove a delimiter... + expect(mask("read /data/x, client repo/models/a.sql done")).toBe("read done") + // ...three or more read as prose and stay a boundary (they were the + // vector that swallowed clauses between a path and any later slash) + expect(mask("read /data/x, big client repo/models/a.sql done")) + .toBe("read , big client repo/models/a.sql done") }) it("continues through ) when a later separator proves it is path content", () => { @@ -665,6 +670,64 @@ describe("maskString paths — filename extensions", () => { }) }) +describe("maskString paths — prose survives around a masked path", () => { + // A spaced run may only bridge to a later separator under strict proof + // (<= 2 clean words, no delimiter/colon words, a letter reachable past the + // separator). URLs, dates, fractions, MIME types, and second path-ish + // tokens no longer prove a bridge, so the clause between a path and any + // later slash survives. + it("later slashes never swallow the clause between", () => { + expect(mask("open /app/x.sql failed, see https://docs.example.com/e/123")) + .toBe("open failed, see https://docs.example.com/e/123") + expect(mask("open /app/x.sql failed on 8/17/2026 today")).toBe("open failed on 8/17/2026 today") + expect(mask("open /app/x.sql failed at ratio 1/2 today")).toBe("open failed at ratio 1/2 today") + expect(mask("open /app/x.sql failed: application/json expected")) + .toBe("open failed: application/json expected") + expect(mask("Model /app/models/a.sql references missing source raw/orders")) + .toBe("Model references missing source raw/orders") + }) + + it("internal-host classification is preserved after a path", () => { + expect(mask("read /opt/x.sql then GET http://10.0.0.5/api/y failed")) + .toBe("read then GET failed") + }) + + it("distinct errors keep distinct masked text (errorHash identity)", () => { + const a = mask("Model /app/models/a.sql references missing source raw/orders") + const b = mask("Model /app/models/b.sql references missing column raw/customers") + expect(a).not.toBe(b) + }) + + it("FQDN UNC homes mask; schemed public URLs stay", () => { + expect(mask("ENOENT: //server.example.com/share/Users/jdoe/secret.txt")).toBe("ENOENT: ") + expect(mask("see https://community.snowflake.com/s/ip-not-allowed for details")) + .toBe("see https://community.snowflake.com/s/ip-not-allowed for details") + }) +}) + +describe("maskString paths — known-prefix freshness", () => { + it("cwd literals follow process.chdir (shallow extensionless roots)", () => { + const os = require("os") + const fs = require("fs") + const path = require("path") + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "customer-repo-")) + const prev = process.cwd() + try { + process.chdir(dir) + const real = fs.realpathSync(dir) + // the post-chdir cwd must be in the literal list: whichever layer wins, + // the directory name (customer-identifying) never survives + const out = mask(`stat ${real} failed`) + expect(out).not.toContain(real) + expect(out).not.toContain("customer-repo") + expect(out).toContain("") + } finally { + process.chdir(prev) + fs.rmdirSync(dir) + } + }) +}) + // The must-not-mask set and the by-design residue: public URLs, non-path // slashes, mid-sentence prose, column-aligned log output, and tab as a // field boundary. @@ -728,72 +791,51 @@ describe("maskString paths — composition with the rest of the masking chain", // inner classes (unambiguous parse => linear time); proof scans are // bounded by the filesystem's 255-byte component limit; the nested- // quantifier ReDoS shape is banned. -describe("maskString paths — performance — linearity and DoS budgets", () => { - it("adversarial delimiter + slash-free run completes in linear time", () => { - const t0 = performance.now() - mask("/a/b, " + "a".repeat(5000)) - expect(performance.now() - t0).toBeLessThan(500) // was exponential: ~2s at 28 chars - }) - - it("multi-word lookahead stays linear on adversarial input", () => { - const t0 = performance.now() - mask("/a/b, " + "word ".repeat(1000)) - expect(performance.now() - t0).toBeLessThan(500) - }) - - it("extension-proof delimiter branch stays linear on adversarial input", () => { - let t0 = performance.now() - mask("/a/b/x;" + "a".repeat(5000)) - expect(performance.now() - t0).toBeLessThan(500) - t0 = performance.now() - mask("/a/b/x;" + "a.".repeat(2500)) - expect(performance.now() - t0).toBeLessThan(500) - }) +describe("maskString paths — performance — growth rates, not wall clocks", () => { + // A single-size wall-clock budget cannot distinguish O(n) from O(n^2) and + // flakes on loaded runners. Each adversarial shape is timed at two sizes; + // 8x the input must cost < 16x the time (linear ~8x, quadratic ~64x). + const t = (s: string) => { + // median of 3 — a single sample is at the mercy of the scheduler + const xs = [0, 0, 0].map(() => { + const a = performance.now() + mask(s) + return performance.now() - a + }) + return xs.sort((x, y) => x - y)[1] + } + const growth = (gen: (n: number) => string) => { + mask(gen(1000)) // warmup + return t(gen(8000)) / Math.max(t(gen(1000)), 0.05) + } - it("widened run class stays linear on adversarial input", () => { - const t0 = performance.now() - mask("s3://b/k " + "w\\x ".repeat(1200)) - expect(performance.now() - t0).toBeLessThan(500) + it("drive-colon runs grow linearly (was quadratic: v:col SQL stalled seconds)", () => { + expect(growth(n => "a:".repeat(n / 2))).toBeLessThan(16) }) - it("double-space continuation stays linear on adversarial input", () => { - const t0 = performance.now() - mask("/Users/x/y" + " zz".repeat(1200)) - expect(performance.now() - t0).toBeLessThan(500) + it("delimiter runs grow linearly", () => { + expect(growth(n => "C:\\a\\b" + ")".repeat(n) + "\\c")).toBeLessThan(16) }) - it("spaced prefix components stay linear on adversarial input", () => { - const t0 = performance.now() - mask("./" + "a b ".repeat(1500)) - expect(performance.now() - t0).toBeLessThan(500) + it("interleaved delimiters grow linearly", () => { + expect(growth(n => ")a".repeat(n / 2) + "/x")).toBeLessThan(16) }) - it("UNC home opener stays linear on adversarial input", () => { - const t0 = performance.now() - mask("\\\\" + "srv ".repeat(1500) + "x") - expect(performance.now() - t0).toBeLessThan(500) + it("spaced word runs after a path grow linearly", () => { + expect(growth(n => "/a/b, " + "word ".repeat(n / 5))).toBeLessThan(16) }) - it("delimiter runs are linear: bounded proof scans", () => { - let t0 = performance.now() - mask("C:\\a\\b" + ")".repeat(2000) + "\\c") - expect(performance.now() - t0).toBeLessThan(200) - t0 = performance.now() - mask(")a".repeat(2500) + "/x") - expect(performance.now() - t0).toBeLessThan(200) + it("separator-free word runs grow linearly (email rule boundary)", () => { + expect(growth(n => "x".repeat(n))).toBeLessThan(16) }) - it("proof scans reach the filesystem component limit (255)", () => { - expect(mask("/Users/jdoe/client)" + "a".repeat(65) + "/models/private.sql leaked")).toBe(" leaked") - // delimiter runs stay linear at the raised bound - const t0 = performance.now() - mask("C:\\a\\b" + ")".repeat(2000) + "\\c") - expect(performance.now() - t0).toBeLessThan(300) + it("backslash runs grow linearly", () => { + expect(growth(n => '"' + "\\".repeat(n) + '"')).toBeLessThan(16) }) - it("mixed spaced-run proofs stay linear", () => { - const t0 = performance.now() - mask("/a/b;" + "word ".repeat(1500) + ";tail") - expect(performance.now() - t0).toBeLessThan(300) + it("entry truncation caps any input at 8 KB of work", () => { + const a = performance.now() + mask("a:".repeat(200000)) + expect(performance.now() - a).toBeLessThan(300) }) })