From 3ecaaffeec32840eece3f06bc571f9e444a61a0b Mon Sep 17 00:00:00 2001 From: minion1227 Date: Thu, 25 Jun 2026 08:49:32 -0700 Subject: [PATCH] fix(signals): redact /root/ local paths on the public-safe boundary (#1375) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The canonical public/private boundary (PUBLIC_UNSAFE_PATTERN / isPublicSafeText, the #542 primitive) and the safeRepoPath changed-file redactor listed /Users/, /home/, /tmp/ but omitted /root/ — the root user's home. A local branch analysed from a /root/... working tree (containers, CI, devcontainers) could leak that absolute path onto public GitHub surfaces. Add /root/ to both denylists, matching the intent already established in miner-dashboard-recommendations.ts (/(?:Users|home|root|tmp|var)/). Behavior-preserving for every existing input. Adds regression tests for the boundary primitive and the public PR packet path redactor. --- src/signals/local-branch.ts | 2 +- src/signals/redaction.ts | 2 +- test/unit/local-branch.test.ts | 22 ++++++++++++++++++++++ test/unit/redaction.test.ts | 7 +++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/signals/local-branch.ts b/src/signals/local-branch.ts index 98c5cde15a..e3de63d47c 100644 --- a/src/signals/local-branch.ts +++ b/src/signals/local-branch.ts @@ -1228,7 +1228,7 @@ function firstCommitTitle(messages: string[] | undefined): string | undefined { function safeRepoPath(path: string): string { /* v8 ignore next -- Empty path fallback protects malformed local-git adapters; path redaction is covered by local branch tests. */ - return /^(\/Users\/|\/home\/|\/tmp\/|[A-Z]:\/Users\/)/i.test(String(path).replace(/\\/g, "/")) ? "[local path hidden]" : String(path || "(unknown path)").replace(/\\/g, "/"); + return /^(\/Users\/|\/home\/|\/root\/|\/tmp\/|[A-Z]:\/Users\/)/i.test(String(path).replace(/\\/g, "/")) ? "[local path hidden]" : String(path || "(unknown path)").replace(/\\/g, "/"); } export function isTestFile(file: string): boolean { diff --git a/src/signals/redaction.ts b/src/signals/redaction.ts index 7b6e7af8a5..cb75a85dbf 100644 --- a/src/signals/redaction.ts +++ b/src/signals/redaction.ts @@ -19,7 +19,7 @@ // intentionally NOT collapsed onto `PUBLIC_UNSAFE_TERMS`. export const PUBLIC_UNSAFE_TERMS = String.raw`reward\w*|score\w*|wallet|hotkey|coldkey|mnemonic|farming|payout|ranking|raw[-_\s]?trust|trust[-_\s]?score|private[-_\s]?reviewability|reviewability`; -export const PUBLIC_UNSAFE_PATTERN = new RegExp(String.raw`\b(${PUBLIC_UNSAFE_TERMS})\b|/Users/|/home/|/tmp/|[A-Z]:[\\/]Users[\\/]`, "i"); +export const PUBLIC_UNSAFE_PATTERN = new RegExp(String.raw`\b(${PUBLIC_UNSAFE_TERMS})\b|/Users/|/home/|/root/|/tmp/|[A-Z]:[\\/]Users[\\/]`, "i"); /** True iff `text` contains nothing that must stay private — i.e. it is safe to surface on a public GitHub surface. */ export function isPublicSafeText(text: string): boolean { diff --git a/test/unit/local-branch.test.ts b/test/unit/local-branch.test.ts index afdd92b0fb..de33b1902a 100644 --- a/test/unit/local-branch.test.ts +++ b/test/unit/local-branch.test.ts @@ -1122,6 +1122,28 @@ describe("local branch analysis", () => { expect(JSON.stringify(analysis.prPacket)).not.toMatch(/reward|score|wallet|hotkey|farming|payout|ranking|trust score|\/Users\/example/i); }); + it("hides a /root/ changed-file path from the public PR packet (regression for #1375)", () => { + const analysis = buildLocalBranchAnalysis({ + input: { + login: "oktofeesh1", + repoFullName: repo.fullName, + changedFiles: [{ path: "/root/work/src/cache.ts", additions: 12, deletions: 2, status: "modified" }], + validation: [{ command: "npm test -- cache", status: "passed" }], + }, + repo, + issues: [], + pullRequests: [], + profile, + outcomeHistory, + scoringSnapshot, + scoringProfile, + }); + + expect(analysis.prPacket.markdown).toContain("[local path hidden]"); + expect(analysis.prPacket.markdown).not.toContain("/root/work"); + expect(JSON.stringify(analysis.prPacket)).not.toContain("/root/work"); + }); + it("removes Windows home paths from public PR packet title and validation lines", () => { const analysis = buildLocalBranchAnalysis({ input: { diff --git a/test/unit/redaction.test.ts b/test/unit/redaction.test.ts index 6be22518b1..4dddc442be 100644 --- a/test/unit/redaction.test.ts +++ b/test/unit/redaction.test.ts @@ -32,11 +32,18 @@ describe("isPublicSafeText (#542 shared public/private boundary)", () => { it("rejects local filesystem paths (posix and Windows)", () => { expect(isPublicSafeText("/Users/alice/project")).toBe(false); expect(isPublicSafeText("/home/bob/repo")).toBe(false); + expect(isPublicSafeText("/root/repo")).toBe(false); expect(isPublicSafeText("/tmp/scratch")).toBe(false); expect(isPublicSafeText("C:\\Users\\carol\\repo")).toBe(false); expect(isPublicSafeText("C:/Users/carol/repo")).toBe(false); }); + it("rejects /root/ home paths so a container/CI working tree cannot leak (regression for #1375)", () => { + // The root user's home was the one local-path family the canonical boundary omitted, even though + // miner-dashboard-recommendations.ts already treats /root/ as local. Repro from the issue: + expect(isPublicSafeText("/root/project/src/index.ts")).toBe(false); + }); + it("is case-insensitive", () => { expect(isPublicSafeText("WALLET")).toBe(false); expect(isPublicSafeText("Payout")).toBe(false);