Files
pi-kit/extensions/permission-gate.ts
Aleksey Shakhmatov 75e9bf85ba feat(extensions): protected-paths, permission-gate, company-context
- protected-paths: glob-based block of write/edit to secrets/keys/.git/prod configs
- permission-gate: confirm dangerous bash (rm -rf, force push, push to protected branch, prod kubectl, docker prune, sudo)
- company-context: loads config/company.json (+ optional remoteConfigUrl w/ 2.5s timeout), injects corporate context via before_agent_start, registers /kit-config
- config path resolved via import.meta.url, not cwd; text template in company-context.md
2026-07-16 11:00:09 +03:00

62 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Permission Gate Extension
*
* WHAT: Asks for confirmation before running dangerous `bash` commands.
* In non-interactive mode (no UI) such commands are blocked by default.
* Based on the official permission-gate example, with more patterns.
*
* CONFIGURE: Edit the DANGEROUS_PATTERNS constant below. Each entry has a
* regex and a human-readable reason shown in the confirmation prompt.
*
* 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";
// ---------------------------------------------------------------------------
// Dangerous command patterns. Add new entries here.
// Matching is intentionally broad; the user confirms, so false positives are
// cheap while misses can be costly.
// ---------------------------------------------------------------------------
const DANGEROUS_PATTERNS: Array<{ re: RegExp; reason: string }> = [
{ re: /\brm\s+(-\w*r\w*f|-\w*f\w*r|--recursive)/i, reason: "recursive force delete (rm -rf)" },
{ re: /\bgit\s+push\b[^\n]*(--force\b|--force-with-lease\b|\s-f\b)/i, reason: "force push" },
{
re: /\bgit\s+push\b[^\n]*\b(main|master|release\/[^\s]+)\b/i,
reason: "push to a protected branch (main/master/release/*)",
},
{
re: /\bkubectl\b[^\n]*(--context[=\s]?\S*prod|--namespace[=\s]?\S*prod|\bctx-prod\b)/i,
reason: "kubectl against a production context/namespace",
},
{ re: /\bdocker\s+system\s+prune\b/i, reason: "docker system prune (removes containers/images/volumes)" },
{ re: /\bsudo\b/i, reason: "elevated privileges (sudo)" },
];
export default function permissionGateExtension(pi: ExtensionAPI) {
pi.on("tool_call", async (event, ctx) => {
if (event.toolName !== "bash") return undefined;
const command = (event.input.command as string | undefined) ?? "";
const hit = DANGEROUS_PATTERNS.find((p) => p.re.test(command));
if (!hit) return undefined;
if (!ctx.hasUI) {
// Non-interactive mode: cannot ask, so block by default.
return { block: true, reason: `Dangerous command blocked (${hit.reason}); no UI for confirmation` };
}
const choice = await ctx.ui.select(
`⚠️ Опасная команда — ${hit.reason}:\n\n ${command}\n\nВыполнить?`,
["Нет", "Да"],
);
if (choice !== "Да") {
return { block: true, reason: `Blocked by user (${hit.reason})` };
}
return undefined;
});
}