{"slug": "pi-block-dangerous-commands-syntax-aware", "title": "pi: block dangerous commands syntax-aware", "summary": "Pi, a command-line tool, now blocks dangerous shell commands using syntax-aware parsing with Tree-sitter instead of regex, preventing false positives and bypasses. The extension, developed by Thiago Perrotta, checks for destructive commands like `rm -rf`, `terraform apply`, and `git reset --hard`, following command substitutions and wrappers, and passes 48 tests.", "body_md": "♠ **Problem statement**: block destructive shell commands issued by\n[pi](https://pi.dev/) without resorting to grepping shell source with regular\nexpressions.\n\nMy first guard was a direct port of an older hook in Claude Code:\n\n``` js\nconst BLOCKED_COMMANDS: ReadonlyArray<{ pattern: RegExp; reason: string }> = [\n  { pattern: /terraform\\s+apply/, reason: \"terraform apply is blocked - use terraform plan first\" },\n  { pattern: /terraform\\s+destroy/, reason: \"terraform destroy is blocked for safety\" },\n  { pattern: /git\\s+reset\\s+.*--hard/, reason: \"git reset --hard is blocked - discards changes irreversibly\" },\n];\n\nconst blocked = BLOCKED_COMMANDS.find(({ pattern }) => pattern.test(command));\n```\n\nThis matched harmless strings such as `echo 'terraform destroy'`\n\n, while shell\nquoting, wrappers, substitutions, and reordered options kept opening holes.\nRegexes aren’t robust in this context.\n\nEnter ASTs! Shell source could leverage a shell parser.\n\nI switched the extension to the Bash grammar from\n[ tree-sitter-bash](https://github.com/tree-sitter/tree-sitter-bash), loaded\nthrough the WASM Tree-sitter runtime:\n\n``` js\nawait Parser.init();\nconst languagePath = require.resolve(\"tree-sitter-bash/tree-sitter-bash.wasm\");\nconst language = await Language.load(languagePath);\nreturn new Parser().setLanguage(language);\n```\n\n[Tree sitter](https://tree-sitter.github.io/) is an amazing parser.\n\nThe policy now works on executable names and arguments:\n\n``` js\nif (executable === \"rm\") {\n  const recursive = staticArguments.some(\n    (argument) => argument === \"--recursive\" || hasShortOption(argument, \"r\") || hasShortOption(argument, \"R\"),\n  );\n  const force = staticArguments.some(\n    (argument) => argument === \"--force\" || hasShortOption(argument, \"f\"),\n  );\n  if (recursive && force) return { command, reason: \"rm -rf is blocked for safety\" };\n}\n```\n\nThe AST walker also follows command substitutions, heredocs, `bash -c`\n\n, `eval`\n\n,\n`find -exec`\n\n, `xargs`\n\n, and common wrappers. Comments and quoted data remain data.\nMalformed syntax and dynamic guarded arguments fail closed.\n\n```\nℹ tests 48\nℹ suites 0\nℹ pass 48\nℹ fail 0\nℹ cancelled 0\nℹ skipped 0\nℹ todo 0\n```\n\nLocal extensions do not get npm dependencies installed by Pi. I kept `index.ts`\n\ndependency-free: on first load it checks for the parser files, runs a pinned\ninstallation when needed, then imports the real guard.\n\n``` js\nconst result = await pi.exec(\"npm\", [\"ci\", \"--ignore-scripts\"], {\n  cwd: extensionDirectory,\n  timeout: 120_000,\n});\n\nconst { default: dangerousCommandGuard } = await import(\"./guard.ts\");\nawait dangerousCommandGuard(pi);\n```\n\nFinally, testing the real tool call:\n\n```\n% rm -rf /tmp/foo\nrm -rf is blocked for safety\n% test -f /tmp/foo/marker && cat /tmp/foo/marker\nguard-test\n```\n\nThe guard blocked the command; `/tmp/foo/marker`\n\nsurvived.\n\nThe source lives in my [dotfiles](https://github.com/thiagowfx/.dotfiles/tree/d917c6b98e8107490bea92367c89d1b05e1de89b/pi/.pi/agent/extensions/dangerous-command-guard).\n\n🤖 *Drafted with *\n\n`/bloggify`\n\n.— § —\n\nReply via [email](mailto:serendipity@perrotta.dev?subject=Reply to: pi: block dangerous commands syntax-aware)", "url": "https://wpnews.pro/news/pi-block-dangerous-commands-syntax-aware", "canonical_source": "https://perrotta.dev/2026/07/pi-block-dangerous-commands-syntax-aware/", "published_at": "2026-07-28 08:57:06+00:00", "updated_at": "2026-08-04 09:04:15.616634+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools"], "entities": ["Pi", "Thiago Perrotta", "Tree-sitter", "tree-sitter-bash", "Claude Code"], "alternates": {"html": "https://wpnews.pro/news/pi-block-dangerous-commands-syntax-aware", "markdown": "https://wpnews.pro/news/pi-block-dangerous-commands-syntax-aware.md", "text": "https://wpnews.pro/news/pi-block-dangerous-commands-syntax-aware.txt", "jsonld": "https://wpnews.pro/news/pi-block-dangerous-commands-syntax-aware.jsonld"}}