/** * Protected Paths Extension * * WHAT: Blocks `write` and `edit` tool calls that target sensitive files * (secrets, keys, VCS internals, production config). Based on the * official protected-paths example, extended with glob matching. * * CONFIGURE: Edit the PROTECTED_PATHS constant below. Patterns are globs: * `*` matches within one path segment, * `**` matches across segments, * `?` matches a single character. * A leading globstar + slash means "in any directory". * * DISABLE: Remove/rename this file from the package `extensions/` folder, * or exclude it via the package manifest / settings. */ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; // --------------------------------------------------------------------------- // Protected path patterns. Add new entries here to widen protection. // Keep this list conservative — it blocks writes agent-wide. // --------------------------------------------------------------------------- const PROTECTED_PATHS: string[] = [ ".env*", // .env, .env.local, .env.production, ... "**/secrets/**", // anything under a secrets/ directory "**/*.pem", // TLS/cert private material "**/*.key", // private keys ".git/**", // VCS internals // Production environment config files (adjust to your conventions): "**/*.prod.yml", "**/*.prod.yaml", "**/*.production.yml", "**/*.production.yaml", "**/values-prod.yaml", // Helm prod values "**/config/prod*", // config/prod.json, config/production.*, ... ]; /** * Compile a glob pattern into a RegExp that matches a path suffix, so the * pattern matches regardless of whether the path is absolute or relative. */ function globToRegExp(glob: string): RegExp { let g = glob; if (g.startsWith("**/")) { g = g.slice(3); // the "(^|/)" prefix already means "in any directory" } let re = ""; for (let i = 0; i < g.length; i++) { const c = g[i]; if (c === "*") { if (g[i + 1] === "*") { re += ".*"; i++; } else { re += "[^/]*"; } } else if (c === "?") { re += "[^/]"; } else if ("\\^$+.()|{}[]".includes(c)) { re += `\\${c}`; } else { re += c; } } return new RegExp(`(^|/)${re}$`); } const COMPILED = PROTECTED_PATHS.map((p) => ({ glob: p, re: globToRegExp(p) })); export default function protectedPathsExtension(pi: ExtensionAPI) { pi.on("tool_call", async (event, ctx) => { if (event.toolName !== "write" && event.toolName !== "edit") { return undefined; } const raw = event.input.path as string | undefined; if (!raw) return undefined; const path = raw.replace(/\\/g, "/"); // normalize Windows separators const hit = COMPILED.find((c) => c.re.test(path)); if (hit) { if (ctx.hasUI) { ctx.ui.notify(`Blocked write to protected path: ${raw} (matches "${hit.glob}")`, "warning"); } return { block: true, reason: `Path "${raw}" is protected (pattern "${hit.glob}")` }; } return undefined; }); }