import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { homedir, tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { HardGuardPolicyLoader } from "#src/hard-guard-loader"; const roots: string[] = []; const ENV_KEYS = ["PI_CODING_AGENT_DIR", "XDG_CONFIG_HOME"] as const; const savedEnv: Partial> = {}; afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); for (const key of ENV_KEYS) { if (savedEnv[key] === undefined) delete process.env[key]; else process.env[key] = savedEnv[key]; delete savedEnv[key]; } }); function setEnv(key: (typeof ENV_KEYS)[number], value: string | undefined) { if (!(key in savedEnv)) savedEnv[key] = process.env[key]; if (value === undefined) delete process.env[key]; else process.env[key] = value; } describe("HardGuardPolicyLoader", () => { it("loads only the global JSONC hard-guard file and preserves allow exceptions", () => { const root = mkdtempSync(join(tmpdir(), "hard-guard-loader-")); roots.push(root); const userPath = join(root, "hard-guards.jsonc"); mkdirSync(root, { recursive: true }); writeFileSync(userPath, `{ // Allow public examples, deny the rest of the credentials tree. "version": 1, "permission": { "*": "allow", "~/.ssh/**": "deny", "~/.ssh/*.example": "allow" } }`); const loader = new HardGuardPolicyLoader(userPath); const config = loader.loadGlobalConfig(); expect(config.permission).toEqual({ "*": "allow", "~/.ssh/**": "deny", "~/.ssh/*.example": "allow", }); expect(loader.getConfigIssues()).toEqual([]); }); it("fails closed with an issue when the file is missing", () => { const root = mkdtempSync(join(tmpdir(), "hard-guard-loader-")); roots.push(root); const userPath = join(root, "hard-guards.jsonc"); const loader = new HardGuardPolicyLoader(userPath); expect(loader.loadGlobalConfig().permission).toEqual({ "*": "deny" }); expect(loader.getConfigIssues().join("\n")).toContain("denying all"); }); it("fails closed when the file is invalid", () => { const root = mkdtempSync(join(tmpdir(), "hard-guard-loader-")); roots.push(root); const userPath = join(root, "hard-guards.jsonc"); writeFileSync(userPath, `{ "version": 1, "permission": { "*": "ask" } }`); const loader = new HardGuardPolicyLoader(userPath); expect(loader.loadGlobalConfig().permission).toEqual({ "*": "deny" }); expect(loader.getConfigIssues().join("\n")).toContain("allow or deny"); }); it("rejects an unsupported version instead of interpreting it", () => { const root = mkdtempSync(join(tmpdir(), "hard-guard-loader-")); roots.push(root); const userPath = join(root, "hard-guards.jsonc"); writeFileSync( userPath, `{ "version": 2, "permission": { "*": "allow" } }`, ); const loader = new HardGuardPolicyLoader(userPath); expect(loader.loadGlobalConfig().permission).toEqual({ "*": "deny" }); expect(loader.getConfigIssues().join("\n")).toContain( "unsupported hard guard version", ); }); it("keeps the last good snapshot when the file turns invalid", () => { const root = mkdtempSync(join(tmpdir(), "hard-guard-loader-")); roots.push(root); const userPath = join(root, "hard-guards.jsonc"); writeFileSync( userPath, `{ "version": 1, "permission": { "*": "allow", "~/.ssh/**": "deny" } }`, ); const loader = new HardGuardPolicyLoader(userPath); const first = loader.loadGlobalConfig(); expect(first.permission).toEqual({ "*": "allow", "~/.ssh/**": "deny", }); // A corrupt edit must not weaken the boundary: the last valid snapshot // keeps applying while the file stays broken. writeFileSync(userPath, "{ not json"); const second = loader.loadGlobalConfig(); expect(second).toEqual(first); expect(loader.getConfigIssues().join("\n")).toContain("Failed to load"); }); it("restores the file's own rules once it becomes valid again", () => { const root = mkdtempSync(join(tmpdir(), "hard-guard-loader-")); roots.push(root); const userPath = join(root, "hard-guards.jsonc"); writeFileSync( userPath, `{ "version": 1, "permission": { "*": "allow", "~/.ssh/**": "deny" } }`, ); const loader = new HardGuardPolicyLoader(userPath); loader.loadGlobalConfig(); writeFileSync(userPath, "{ not json"); loader.loadGlobalConfig(); writeFileSync( userPath, `{ "version": 1, "permission": { "*": "allow", "~/.aws/**": "deny" } }`, ); const restored = loader.loadGlobalConfig(); expect(restored.permission).toEqual({ "*": "allow", "~/.aws/**": "deny", }); }); describe("protectedWrites", () => { function writeConfig(userPath: string, protectedWrites: unknown): void { writeFileSync( userPath, JSON.stringify({ version: 1, permission: { "*": "allow" }, protectedWrites, }), ); } function makeLoader() { const root = mkdtempSync(join(tmpdir(), "hard-guard-loader-")); roots.push(root); const userPath = join(root, "hard-guards.jsonc"); return { root, userPath, loader: new HardGuardPolicyLoader(userPath) }; } it("expands {agentDir} honoring PI_CODING_AGENT_DIR", () => { setEnv("PI_CODING_AGENT_DIR", "/tmp/fake-agent-dir"); const { userPath, loader } = makeLoader(); writeConfig(userPath, [ { pattern: "{agentDir}/auth.json", label: "Pi 凭据" }, ]); expect(loader.getProtectedWrites()).toEqual([ { pattern: "/tmp/fake-agent-dir/auth.json", label: "Pi 凭据" }, ]); }); it("expands {configHome} honoring XDG_CONFIG_HOME", () => { setEnv("XDG_CONFIG_HOME", "/tmp/fake-config-home"); const { userPath, loader } = makeLoader(); writeConfig(userPath, [ { pattern: "{configHome}/my-pi/**", label: "pi-ssh 凭据库" }, ]); expect(loader.getProtectedWrites()).toEqual([ { pattern: "/tmp/fake-config-home/my-pi/**", label: "pi-ssh 凭据库" }, ]); }); it("falls back to ~/.config for {configHome} and expands leading ~/", () => { setEnv("XDG_CONFIG_HOME", undefined); const { userPath, loader } = makeLoader(); writeConfig(userPath, [ { pattern: "{configHome}/my-pi/**", label: "xdg" }, { pattern: "~/secret.txt", label: "home" }, ]); expect(loader.getProtectedWrites()).toEqual([ { pattern: join(homedir(), ".config", "my-pi/**"), label: "xdg" }, { pattern: join(homedir(), "secret.txt"), label: "home" }, ]); }); it("returns empty rules when no protectedWrites key exists", () => { const { userPath, loader } = makeLoader(); writeFileSync( userPath, `{ "version": 1, "permission": { "*": "allow" } }`, ); expect(loader.getProtectedWrites()).toEqual([]); expect(loader.getConfigIssues()).toEqual([]); }); it("returns empty rules when the file is missing (requests deny anyway)", () => { const { loader } = makeLoader(); expect(loader.getProtectedWrites()).toEqual([]); expect(loader.getConfigIssues().join("\n")).toContain("denying all"); }); it("treats a malformed protectedWrites as an invalid file", () => { const { userPath, loader } = makeLoader(); writeConfig(userPath, { pattern: "/x", label: "missing array" }); expect(loader.loadGlobalConfig().permission).toEqual({ "*": "deny" }); expect(loader.getConfigIssues().join("\n")).toContain( "protectedWrites", ); }); it("keeps the last good protectedWrites when the file turns invalid", () => { const { userPath, loader } = makeLoader(); writeConfig(userPath, [ { pattern: "/vault/auth.json", label: "Pi 凭据" }, ]); loader.loadGlobalConfig(); writeFileSync(userPath, "{ definitely not json at all"); loader.loadGlobalConfig(); expect(loader.getProtectedWrites()).toEqual([ { pattern: "/vault/auth.json", label: "Pi 凭据" }, ]); expect(loader.getConfigIssues().join("\n")).toContain("Failed to load"); }); }); });