diff --git a/src/signals/focus-manifest.ts b/src/signals/focus-manifest.ts index 0f38623787..1638f365f4 100644 --- a/src/signals/focus-manifest.ts +++ b/src/signals/focus-manifest.ts @@ -289,7 +289,10 @@ const EMPTY_MANIFEST: FocusManifest = { * text must not leak reward, wallet/key, ranking, or local filesystem path material. */ export function isFocusManifestPublicSafe(text: string): boolean { - return !/\b(reward\w*|score\w*|wallets?|hotkeys?|coldkeys?|seed[-\s]?phrases?|mnemonics?|private[-\s]?keys?|farming|payouts?|rankings?|raw[-\s]?trust(?:[-\s]?scores?)?|trust[-\s]?scores?|private[-\s]?reviewability|reviewability(?:[-\s]?internals?)?|private[-\s]?scoreability|scoreability|public[-\s]?score[-\s]?(?:estimate|prediction|claim)s?|estimated[-\s]?scores?|score[-\s]?(?:estimate|prediction|preview)s?)\b|\/Users\/|\/home\/|\/tmp\/|[A-Z]:\\Users\\/i.test(text); + // Local filesystem path alternatives mirror the canonical PUBLIC_UNSAFE_PATTERN in redaction.ts: include + // `/root/` (container/CI home) and accept the forward-slash Windows form (`C:/Users/`), not only the + // backslash one — otherwise a `/root/...` or `C:/Users/...` path leaks through this public-safe guard. + return !/\b(reward\w*|score\w*|wallets?|hotkeys?|coldkeys?|seed[-\s]?phrases?|mnemonics?|private[-\s]?keys?|farming|payouts?|rankings?|raw[-\s]?trust(?:[-\s]?scores?)?|trust[-\s]?scores?|private[-\s]?reviewability|reviewability(?:[-\s]?internals?)?|private[-\s]?scoreability|scoreability|public[-\s]?score[-\s]?(?:estimate|prediction|claim)s?|estimated[-\s]?scores?|score[-\s]?(?:estimate|prediction|preview)s?)\b|\/Users\/|\/home\/|\/root\/|\/tmp\/|[A-Z]:[\\/]Users[\\/]/i.test(text); } function emptyManifest(source: FocusManifestSource, warnings: string[] = []): FocusManifest { diff --git a/test/unit/focus-manifest.test.ts b/test/unit/focus-manifest.test.ts index 566530b67f..f13c37225a 100644 --- a/test/unit/focus-manifest.test.ts +++ b/test/unit/focus-manifest.test.ts @@ -713,6 +713,19 @@ describe("public-safe invariant", () => { expect(isFocusManifestPublicSafe("paste your hotkey")).toBe(false); }); + it("rejects local filesystem paths, matching the canonical redaction guard", () => { + // Unix homes + container/CI `/root/` + tmp. + expect(isFocusManifestPublicSafe("see /Users/me/repo/src")).toBe(false); + expect(isFocusManifestPublicSafe("see /home/dev/repo/src")).toBe(false); + expect(isFocusManifestPublicSafe("see /root/repo/src")).toBe(false); + expect(isFocusManifestPublicSafe("see /tmp/build/out")).toBe(false); + // Windows, both backslash and forward-slash forms. + expect(isFocusManifestPublicSafe("see C:\\Users\\me\\repo")).toBe(false); + expect(isFocusManifestPublicSafe("see C:/Users/me/repo")).toBe(false); + // A relative path with none of these roots stays safe. + expect(isFocusManifestPublicSafe("see src/signals/focus-manifest.ts")).toBe(true); + }); + it("never emits public next steps that contain forbidden language for generated manifests", () => { // Deterministic property-style check (seeded LCG, no external generator dependency): // build a wide range of manifests/changed-paths from a fixture pool that deliberately