@yaosu/pi-path-guard

Path Guard for pi — blocks destructive commands & path overwrites, protects .env/keys, with strict/normal/loose/trusted/naked guard modes (/guard). pi 防误删/防误覆盖扩展,支持 5 种防护模式。

Packages

Package details

extension

Install @yaosu/pi-path-guard from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@yaosu/pi-path-guard
Package
@yaosu/pi-path-guard
Version
1.5.7
Published
Sep 19, 2026
Downloads
2,909/mo · 451/wk
Author
yaosu
License
MIT
Types
extension
Size
149.6 KB
Dependencies
0 dependencies · 1 peer
Pi manifest JSON
{
  "extensions": [
    "./extensions"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-path-guard

Path Guard — pi extension: prevents accidental deletes / overwrites / edits Path Guard — pi 扩展:防误删 / 防误覆盖 / 防误改

Intercepts destructive operations in tool calls (bash, write, edit): protected paths (.env, .ssh, keys, credentials, …), system-destructive commands (mkfs/reboot/block-device writes/bulk deletes, …), overwrites/deletes outside the project, and > truncation of existing files — deciding block / confirm / pass per guard mode.

拦截 bash / write / edit 等工具调用中的破坏性操作:受保护路径(.env.ssh、密钥、凭据等)、系统级破坏命令(mkfs/reboot/写块设备/批量删除等)、项目外覆盖/删除、> 截断已有文件等,按防护模式决定 阻止 / 询问 / 放行

Feature highlights

  • 🛡 5 modes, every rule tunablestrict / normal (default) / loose / trusted / naked run off one built-in matrix of 19 guard rules; switching modes tightens or loosens the whole guard, and the active mode persists across sessions (/guard or /guard <mode>). Inside every mode, each of the 19 rules is individually re-tunable to block / confirm / pass (or reset to its built-in default) via /guard → rules — no code changes; only your overrides are stored. 五种防护模式,规则逐条可调strict/normal/loose/trusted/naked 共用一套内置的 19 条守护规则矩阵;切换模式即整体收紧或放宽防护,当前模式跨会话保留。而每种模式下,19 条规则每一条都能独立调整为 阻止/询问/放行(或恢复内置默认),经 /guard → rules 即可,无需改代码,仅保存你的覆盖项。
  • 📁 Protected & trusted paths/guard → paths splits into two categories: protected paths (/guard paths protected …, the default) are guarded in every mode (incl. naked); trusted paths (/guard paths trusted …) are always allowed — operations on them pass like trusted mode, in any mode. System-important paths can never be trusted (see below). 管理受保护路径信任路径两类路径。
  • 🔒 Three-way verdict — every intercepted operation resolves to block / confirm / pass: block refuses outright, confirm asks you, pass executes. How strict the guard is depends entirely on your rules.

⚠️ Security notice: pi extensions run with full system permissions and can execute arbitrary code. Review the source before installing (this project is open source — see extensions/path-guard.ts).

Install / 安装

# From git (recommended / 推荐)
pi install git:github.com/yaodashanren/pi-path-guard@v1

# Local directory (development / 本地目录,开发用)
pi install /path/to/pi-path-guard

# Try without installing (no settings change / 临时试用,不写入 settings)
pi -e ./pi-path-guard

# npm: scope package
# pi install npm:@yaosu/pi-path-guard

After installing, run /reload or restart pi. 安装后 /reload 或重启 pi 生效。

Usage / 使用

/guard command

  • /guard — interactive main menu with three actions: Switch mode (title shows the full decision matrix; choices are bilingual), Customize per-mode guard rules, and Manage custom protected paths. The main menu loops: a sub-menu's back returns to the previous menu, and eventually back to this main menu; only cancelling at the top level (no selection) exits the command. 交互式主菜单,三个动作:切换防护模式(标题展示完整判定矩阵,选项中英双语)、定制每模式守护规则管理自定义受保护路径。主菜单为循环:子菜单的 back 逐级返回上一级,最终回到本主菜单;只有顶层取消(不选)才退出命令
  • /guardrules sub-menu: pick a mode → the rule editor lists all 19 rules with their current levels; pick one → set block / confirm / pass (or reset to the built-in default). Stays in the editor so you can set several rules per mode before choosing back. Also offers a read-only full overview matrix in a scrollable viewer (↑/↓/PgUp/PgDn scroll, q/⏎/esc to close) and reset (clear all overrides). pathGuard.rules.{mode}.{rule} in settings.json. /guard rules 子菜单:选模式 → 规则编辑器列出全部 19 条规则及其当前级别;选一条 → 设为 block / confirm / pass(或恢复内置默认)。改完停留在编辑器可连续改多条,再选 back。另提供只读 overview 全矩阵(可滚动查看,↑/↓/PgUp/PgDn 滚动,q/⏎/esc 关闭)与 reset(清空全部覆盖),存于 settings.json 的 pathGuard.rules.{mode}.{rule}
  • /guard <strict|normal|loose|trusted|naked> — quick switch (trusted requires a warning; naked requires a double warning) 快捷切换(trusted 需警告确认;naked 需两级确认)
  • Invalid argument → falls back to the interactive main menu 非法参数 → 兜底弹出交互主菜单
  • The active mode persists across sessions via global settings.json (pathGuard.mode in ~/.pi/agent/settings.json), falling back to normal. /guard <mode> writes it back there. Persistence is intentionally global-only, never project-scoped: writing a project .pi/settings.json would make the project "trust-requiring", so pi would start asking for trust on the next launch (defaultProjectTrust=ask) and a declined/untrusted launch would silently ignore the saved mode — the old behavior that made a saved mode revert to normal. Global settings are never trust-gated, so the mode always survives. 模式跨会话持久化到全局 settings.json(~/.pi/agent/settings.jsonpathGuard.mode),缺省回 normal/guard <mode> 切换时回写到该文件。持久化刻意只写全局、不写项目:写入项目 .pi/settings.json 会让项目变为需信任项目,导致下次启动 pi 弹出信任询问,若项目被拒/未信任则保存的模式会被静默忽略(这正是旧版模式重置为 normal 的根因)。全局设置不受信任判定门控,模式必然保留
  • /guard paths protected add|rm|list|clear <path> (alias: omit protected) — manage custom protected paths, enforced in EVERY mode (incl. naked); /guard paths trusted add|rm|list|clear <path> — manage trusted paths (always allowed, trusted-mode protection; adding one requires a warning confirm; protected/system paths are refused) 管理自定义受保护/信任路径
  • The active mode is shown in the footer status bar (🛡 <mode>, 🛡 NAKED in warning color) 当前模式显示在底部状态栏(🛡 <mode>,naked 用警示色 🛡 NAKED

Protected & trusted paths / 受保护路径与信任路径

/guard → paths (or /guard paths) presents two categories: /guard → paths(或 /guard paths)提供两个分类:

# Protected paths (default category) — guarded in EVERY mode incl. naked 受保护路径
/guard paths protected list | add <path> | rm <path> | clear
/guard paths list | add <path> | rm <path> | clear   # same, category defaults to protected

# Trusted paths — always allowed (trusted-mode protection) 信任路径——始终放行
/guard paths trusted list | add <path> | rm <path> | clear

Protected paths are checked against the resolved real path and protected in every mode — even naked (built-in protected paths are NOT enforced in naked; only yours are). They are also settable statically: 受保护路径按解析后的真实路径匹配,且在任何模式下都被守护——包括 naked(内置受保护路径在 naked 下不生效,但你自定义的始终生效)。也可在 settings.json 静态配置:

"pathGuard": { "extraProtected": ["~/secrets", "/path/to/important.txt"] }

Trusted paths are the inverse: path-guard treats every operation whose target lies inside one as if the active mode were trusted for just that path — writes/edits/deletes/overwrites/truncates/in-place edits there pass without any prompt, in every mode (even strict). Protection always outranks trust: 信任路径则相反:落在信任路径内的所有操作都被视为“对该路径采用 trusted 模式的保护”——写入/编辑/删除/覆盖/截断/就地修改一律不弹窗、直接放行,在任何模式下都生效(包括 strict)。但保护始终优先于信任:

  • A trusted path can never be a protected path — adding .env/.ssh/keys/node_modules/… (built-in system paths) or an existing user-protected path is refused. 信任路径绝不可是受保护路径——添加 .env/.ssh/密钥/node_modules 等内置系统路径或已被你设为受保护的路径会被拒绝
  • A protected file inside a trusted subtree is still blocked (e.g. a .env under a trusted dir stays protected). 即便信任目录里出现受保护文件也仍会被拦截(如信任目录下的 .env 依旧受保护)。
  • Adding a trusted path requires an interactive warning confirm (refused without a UI). 添加信任路径需要警告确认(无 UI 下拒绝)。

Also settable statically: 也可在 settings.json 静态配置:

"pathGuard": {
  "extraProtected": ["~/secrets"],
  "trustedPaths":   ["/path/to/scratch-or-build-dir"]
}

Tunable rules / 可调规则

Each mode's judgement is a set of 19 rules; override any per mode in settings.json (rule = "block" | "confirm" | "pass"; invalid values are ignored): 每个模式的判定由 19 条规则组成;可在 settings.json 里按模式覆盖(rule"block"|"confirm"|"pass",非法值忽略):

"pathGuard": {
  "mode": "normal",
  "rules": {
    "normal":  { "deleteOutside": "confirm", "confirmGroup": "pass" },
    "naked":   { "blockGroup": "block" }
  }
}

Rule IDs: blockGroup, confirmGroup, writeOutside, writeHome, writeInProject, deleteOutside, deleteInProject, overwriteOutsideExisting, overwriteOutsideNew, overwriteInProject, truncateInProject, truncateOutside, gitDestructive, pipeToShellInProject, pipeToShellOutside, runScriptInProject, runScriptOutside, runScriptProtected, scriptUnresolved. Defaults reproduce the matrix above exactly.

Guard mode matrix / 防护模式矩阵

Checkpoint / 判定点 strict normal loose trusted naked
Protected paths (.env/.ssh/keys/credentials) / 受保护路径 block block block block pass
Block group (mkfs/reboot/block-device writes/bulk delete) / Block 组危险命令 block block block block confirm
Confirm group (sudo/ssh/chmod 777 …) / Confirm 组 block confirm confirm confirm pass
git destructive (reset --hard/clean -f …) / git 破坏性 confirm confirm confirm confirm pass
In-project write/edit/new / 项目内写/改/新建 confirm pass pass pass pass
In-project delete / 项目内删除 confirm confirm pass pass pass
Outside write (new file) / 项目外写新文件 confirm confirm pass pass pass
Outside overwrite existing / 项目外覆盖已存在 block block confirm pass pass
Outside delete ordinary / 项目外删除普通文件 block block confirm pass pass
> truncate existing in-project / 截断项目内已有文件 confirm confirm pass pass pass
> truncate existing outside / 截断项目外已有文件 block confirm confirm pass pass
Pipe to shell (in-workspace, curl…|bash) / 管道到 shell(项目内) confirm pass pass pass pass
Pipe to shell (remote/outside, curl…|bash) / 管道到 shell(远程/项目外) confirm confirm pass pass pass
Run script in-project (source/. / bash x.sh) / 运行脚本·项目内 confirm confirm pass pass pass
Run script outside/HOME / 运行脚本·项目外/HOME block confirm confirm pass pass
Run script of built-in protected path / 运行脚本·内置保护 block confirm confirm pass pass
Run script with an unresolvable $VAR/glob target / 运行脚本·路径不可解析 block confirm confirm pass pass
cwd=HOME write / HOME 目录写 confirm confirm pass pass pass
No UI (headless) / 无交互界面 block* block* block* block* pass

*block = denied directly, no confirmation opportunity / 直接阻止,无确认机会;confirm = prompt / 弹窗询问;pass = allow / 放行;*headless: items that would be confirmed are blocked instead / 无 UI 时需确认项一律阻止

⚠️ naked mode / 裸奔模式: passes almost everything — protected paths, the write/edit tool checks, git destructive, truncation, outside deletes/overwrites all pass even with no UI. Only system-destructive commands (mkfs/reboot/bulk-delete/block-device writes) are still confirmed. Switching requires a double confirmation (two prompts). Use only when you want minimal path-guard interference. ⚠️ 裸奔模式:除系统级破坏命令外几乎全部放行——受保护路径、write/edit 工具检查、git 破坏性、截断、外部删除/覆盖均放行,无 UI 下也放行;但系统级破坏命令(mkfs/reboot/批量删除/写块设备)仍会弹窗询问。切换需要两级确认(两次弹窗)。仅当你需要最少的路径守护干扰时使用。

Core capabilities / 核心能力

  • Protected-path interception / 受保护路径拦截: .env / .envrc / .ssh / .secrets / .aws / .kube / private keys (*.pem/*.key/*.p12/*.pfx, id_rsa/id_ed25519) / credentials / shell configs (.bashrc …) / node_modules / dist / build … blocked hard in every mode (except naked) — 任何模式下硬性阻止(naked 除外)

  • Block group / Block 组危险命令: mkfs.* / mkswap / poweroff / reboot / shutdown / dd to block devices / > /dev/sdX / find -delete / find -exec rm / xargs rm

  • Confirm group / Confirm 组: sudo / doas / pkexec / chmod 777 / ssh / scp / sftp / rsh / telnet / wget -O /dev/null

  • Overwrite detection / 覆盖检测: mv / cp / install / tee / ln -f / rsync --delete on existing targets, classified by in/out project; rsync/scp remote targets (user@host:/path, host:/path) are never resolved as local paths — 目标已存在时按内外策略处理;rsync/scp 远程目标不会被当成本地路径解析

  • Redirect truncation / 重定向截断: > existing file (incl. 2> / &> / >|, excluding >> and devices) → confirm — > existing file 截断已有文件需确认(含 >|

  • Redirect / download / dd target location / 重定向·下载·dd 目标位置: a redirect (>, >> …), dd of=, curl -o|-O or wget -O target that lies outside the project (or a HOME write) is judged like an overwrite (writeOutside / writeHome / overwriteOutsideExisting / overwriteOutsideNew); devices (/dev/null …) and trusted paths are exempt — 重定向 / dd of= / curl -o / wget -O 的目标在项目外(或 HOME 下写入)时按覆盖规则判定;设备与信任路径放行

  • Shell wrapper recursion / shell 包装器递归: strips sudo/nohup/timeout/env … prefixes, recurses into bash -c / eval; quote-aware tokenization — 前缀剥除后分析真实命令;引号感知分词

  • git destructive commands / git 破坏性命令: clean -f / reset --hard / checkout -- . / checkout|switch -f|--force / restore . / worktree remove --force / tag -d / branch -D / push --force / stash drop (a narrow restore --source=<ref> -- <path> is not treated as destructive) — restore --source 带路径的常规用法不再弹窗

  • Dangerous pipe-to-shell / 危险管道到 shell: curl … \| bash / wget -qO- … \| sh / python -c '…' \| sh — strict confirms at all positions; normal passes in-workspace and confirms remote/outside sources; other modes pass (per pipeToShell* rules) — 判定 curl/wget 等下载或解释器内联代码的输出被管道进 shell 执行

  • Run-script guard / 运行脚本守护: source file / . file / bash|sh|zsh|dash|ksh [flags] script are judged by target path instead of a blanket confirm — in-project (runScriptInProject), outside/HOME (runScriptOutside), built-in protected (runScriptProtected), each tunable per mode; user-protected targets stay hard-blocked in every mode (incl naked) and trusted targets always pass — a $VAR/glob target that cannot be resolved follows the new scriptUnresolved rule (strict block / normal·loose confirm / trusted·naked pass), but its literal tail is still inspected first: a user-protected tail stays hard-blocked in every mode, a built-in protected tail ($D/id_rsa, $D/.ssh/config) uses runScriptProtected, and a bare $VAR with nothing literal to inspect stays a conservative confirm even in trusted — 按目标路径判定并可按模式调整:项目内 / 项目外 / 内置保护各一条规则;用户自定义保护路径硬拦、信任路径放行。无法静态解析的 $VAR/通配目标走新规则 scriptUnresolved(strict 阻止 / normal·loose 询问 / trusted·naked 放行),但会先检查字面尾部:命中用户自定义保护路径仍全模式硬拦,命中内置保护(如 $D/id_rsa$D/.ssh/config)走 runScriptProtected,而无任何字面信息的裸 $VAR 即使在 trusted 下也保持确认

  • Bypass resistance / 防绕过: variable/wildcard paths that can't be statically resolved always confirm (script targets excepted — see the run-script guard: they follow scriptUnresolved and are exempt only in trusted/naked); command substitutions ($(...) / backticks) are recursively judged; any hard block in a compound command blocks the whole thing — 变量/通配符路径一律 confirm;命令替换($(...) / 反引号)递归判定;复合命令任一段硬性阻止则整体阻止

  • Block escape hints / 拦截提示: every block message appends a short, category-aware "To run anyway / 如需执行:" hint — an English hint followed by the Chinese note on its own indented line — user-configured protected paths suggest /guard paths rm, built-in protected paths & system-destructive commands point to /guard naked, rule-level blocks suggest /guard loose or /guard rules — 每次拦截都会附一条按类别给出的解除建议:英文提示一行、中文注释另起一行缩进(头部 To run anyway / 如需执行:

  • Session pass from the confirm dialog / 弹窗内会话级放行: every confirm prompt offers a third option 🔓 Allow & set <rule> = pass (session) (multiple rules → N rules), so a repeated prompt can be answered in place without opening /guard rules; the pass is in-memory only (never persisted, cleared on a new session) and is not offered for confirmGroup (sudo/ssh/chmod 777), system-destructive commands, or in naked mode — 确认弹窗提供会话级放行第三选项,仅内存生效(不落盘、新会话清除);confirmGroup、系统级破坏命令与 naked 模式不提供

Known limitations / 已知边界

  • Static heuristics, not a sandbox / 静态启发式,不是沙箱: the guard inspects the bash command text and the write/edit target paths; it is meant to catch accidental destructive operations, not to defeat a determined adversary or prompt injection. Run untrusted code in a real sandbox / VM / container under a least-privilege account — 本扩展只检查 bash 命令文本与 write/edit 目标路径,用于拦截误操作,并非对抗恶意输入或 prompt injection 的完整沙箱;不可信代码请放到真正的沙箱/容器/虚拟机里运行。
  • POSIX-oriented / 面向 POSIX: path handling assumes POSIX separators and /dev-style device names; Windows is not a supported target — 路径判定基于 POSIX 分隔符与 /dev 设备名,不支持 Windows。
  • What is not statically expanded / 不做静态展开: variable/glob targets ($F, *.log) cannot be resolved, so they trigger a conservative confirm instead of being followed — except the script-file target of source/./<interp> script, which is additionally judged by its literal tail (scriptUnresolved rule; a bare $VAR with no literal tail still confirms); ~user is not expanded either but is anchored outside the project (never mistaken for an in-project path); aliases, shell functions, eval of dynamically built strings, and bash -c / $(...) nesting beyond depth 4 are not resolved (too deep → confirm) — 变量/通配符目标($F*.log)无法静态解析,一律保守 confirm而不展开;~user 同样不展开,但按项目外处理(不会被误当成项目内路径);别名、shell 函数、动态拼接后 eval、以及超过 4 层的 bash -c / $() 嵌套不会被解析(过深 → confirm)。
  • Conservative by design / 宁可误报: unresolvable targets confirm; in a no-UI environment, confirm-grade operations are blocked instead of prompted — expect the occasional false positive and tune it with /guard rules or trusted paths — 无法解析的目标会 confirm;无 UI 时需确认项直接阻止;可能出现误报,可用 /guard rules 或信任路径调整。
  • Scope / 范围: only the bash tool and the write/edit tools are guarded; network access, MCP servers, other tools and extensions are out of scope — 仅守护 bash 工具与 write/edit 工具;网络、MCP、其它工具与扩展不在范围内。

Development / 开发与测试

Automated tests (275 assertions) load the real extension with a mocked pi API, covering the 5 modes × protected paths / dangerous commands / truncation / git destructive / dangerous pipe-to-shell matrix, plus /guard command interaction, trusted-mode confirmation, naked-mode double confirmation, the footer status indicator, settings.json mode persistence, custom protected paths (incl. naked), trusted paths (always-allowed, incl. protected-path refusal and strict-mode pass), run-script judging (source/./bash × in/out/protected/trusted), redirect/download/dd outside+variable targets, command-substitution recursion, and per-mode rule overrides:

cd tests && node --experimental-strip-types test-pathguard.ts

自动化测试(275 断言)模拟 pi API 加载真实扩展,覆盖 5 种模式 × 受保护路径 / 危险命令 / 截断 / git 破坏性 / 危险管道到 shell 等判定矩阵,以及 /guard 命令交互、trusted 确认与 naked 两级确认、底部状态栏指示、settings.json 模式持久化、自定义受保护路径(含 naked)、信任路径(始终放行,含受保护路径拒绝与 strict 下放行)、运行脚本判定(source/./bash × 项目内/外/受保护/信任)、重定向/下载/dd 的项目外与变量目标判定、命令替换递归、按模式规则覆盖等流程:

cd tests && node --experimental-strip-types test-pathguard.ts

Changelog

See CHANGELOG.md for the full version history (aligned with package.json); the latest release is v1.5.7.

License

MIT © yaosu