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..a0e2eb7bcc 100644 --- a/test/unit/local-branch.test.ts +++ b/test/unit/local-branch.test.ts @@ -1147,6 +1147,29 @@ describe("local branch analysis", () => { expect(analysis.prPacket.markdown).not.toMatch(/C:\\Users\\alice/i); }); + it("hides root-user home paths from public PR packet changed paths", () => { + const analysis = buildLocalBranchAnalysis({ + input: { + login: "oktofeesh1", + repoFullName: repo.fullName, + body: "Fixes #7", + changedFiles: [{ path: "/root/work/src/cache.ts", additions: 12, deletions: 2, status: "modified" }], + validation: [{ command: "npm test -- cache", status: "passed" }], + }, + repo, + issues: [{ repoFullName: repo.fullName, number: 7, title: "Cache refresh fails", state: "open", labels: ["bug"], linkedPrs: [] }], + pullRequests: [], + profile, + outcomeHistory, + scoringSnapshot, + scoringProfile, + }); + + expect(analysis.prPacket.markdown).toContain("## Changed Paths"); + expect(analysis.prPacket.markdown).toContain("[local path hidden]"); + expect(analysis.prPacket.markdown).not.toContain("/root/work"); + }); + it("removes snake_case private signals from public PR packet markdown", () => { const analysis = buildLocalBranchAnalysis({ input: { diff --git a/test/unit/redaction.test.ts b/test/unit/redaction.test.ts index 6be22518b1..780e431a27 100644 --- a/test/unit/redaction.test.ts +++ b/test/unit/redaction.test.ts @@ -32,6 +32,8 @@ 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/project/src")).toBe(false); + expect(isPublicSafeText("clone failed at /root/work/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);