@yaosu/pi-path-guard 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,32 +33,35 @@ After installing, run `/reload` or restart pi. 安装后 `/reload` 或重启 pi
33
33
  ### `/guard` command
34
34
 
35
35
  - `/guard` — interactive mode picker (title shows the full decision matrix; choices are bilingual) 交互式选择防护模式(标题展示完整判定矩阵,选项中英双语)
36
- - `/guard <strict|normal|loose|trusted>` — quick switch (trusted requires a warning confirmation) 快捷切换(trusted 需警告确认)
36
+ - `/guard <strict|normal|loose|trusted|naked>` — quick switch (trusted requires a warning; naked requires a double warning) 快捷切换(trusted 需警告确认;naked 需两级确认)
37
37
  - Invalid argument → falls back to the interactive picker 非法参数 → 兜底弹出交互选择
38
38
  - Every new session resets to `normal` 每次新会话自动回到 `normal`
39
39
 
40
40
  ### Guard mode matrix / 防护模式矩阵
41
41
 
42
- | Checkpoint / 判定点 | strict | normal | loose | trusted |
43
- | --- | --- | --- | --- | --- |
44
- | Protected paths (.env/.ssh/keys/credentials) / 受保护路径 | block | block | block | block |
45
- | Block group (mkfs/reboot/block-device writes/bulk delete) / Block 组危险命令 | block | block | block | block |
46
- | Confirm group (sudo/ssh/chmod 777 …) / Confirm 组 | block | confirm | confirm | confirm |
47
- | git destructive (reset --hard/clean -f …) / git 破坏性 | confirm | confirm | confirm | confirm |
48
- | In-project write/edit/new / 项目内写/改/新建 | confirm | pass | pass | pass |
49
- | In-project delete / 项目内删除 | confirm | confirm | pass | pass |
50
- | Outside write (new file) / 项目外写新文件 | confirm | confirm | pass | pass |
51
- | Outside overwrite existing / 项目外覆盖已存在 | block | block | confirm | pass |
52
- | Outside delete ordinary / 项目外删除普通文件 | block | block | confirm | pass |
53
- | `>` truncate existing file / 截断已有文件 | confirm | confirm | confirm | confirm |
54
- | cwd=HOME write / HOME 目录写 | confirm | confirm | pass | pass |
55
- | No UI (headless) / 无交互界面 | block* | block* | block* | block* |
42
+ | Checkpoint / 判定点 | strict | normal | loose | trusted | naked |
43
+ | --- | --- | --- | --- | --- | --- |
44
+ | Protected paths (.env/.ssh/keys/credentials) / 受保护路径 | block | block | block | block | pass |
45
+ | Block group (mkfs/reboot/block-device writes/bulk delete) / Block 组危险命令 | block | block | block | block | confirm |
46
+ | Confirm group (sudo/ssh/chmod 777 …) / Confirm 组 | block | confirm | confirm | confirm | pass |
47
+ | git destructive (reset --hard/clean -f …) / git 破坏性 | confirm | confirm | confirm | confirm | pass |
48
+ | In-project write/edit/new / 项目内写/改/新建 | confirm | pass | pass | pass | pass |
49
+ | In-project delete / 项目内删除 | confirm | confirm | pass | pass | pass |
50
+ | Outside write (new file) / 项目外写新文件 | confirm | confirm | pass | pass | pass |
51
+ | Outside overwrite existing / 项目外覆盖已存在 | block | block | confirm | pass | pass |
52
+ | Outside delete ordinary / 项目外删除普通文件 | block | block | confirm | pass | pass |
53
+ | `>` truncate existing file / 截断已有文件 | confirm | confirm | confirm | confirm | pass |
54
+ | cwd=HOME write / HOME 目录写 | confirm | confirm | pass | pass | pass |
55
+ | No UI (headless) / 无交互界面 | block* | block* | block* | block* | pass |
56
56
 
57
57
  *block = denied directly, no confirmation opportunity / 直接阻止,无确认机会;confirm = prompt / 弹窗询问;pass = allow / 放行;\*headless: items that would be confirmed are blocked instead / 无 UI 时需确认项一律阻止
58
58
 
59
+ > ⚠️ **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.
60
+ > ⚠️ **裸奔模式**:除系统级破坏命令外几乎全部放行——受保护路径、write/edit 工具检查、git 破坏性、截断、外部删除/覆盖均放行,无 UI 下也放行;但系统级破坏命令(mkfs/reboot/批量删除/写块设备)仍会**弹窗询问**。切换需要**两级确认**(两次弹窗)。仅当你需要最少的路径守护干扰时使用。
61
+
59
62
  ### Core capabilities / 核心能力
60
63
 
61
- - **Protected-path interception / 受保护路径拦截**: `.env` / `.ssh` / `.aws` / `.kube` / private keys (`*.pem`/`*.key`) / credentials / shell configs (`.bashrc` …) / `node_modules` / `dist` / `build` … blocked hard in every mode — 任何模式下硬性阻止
64
+ - **Protected-path interception / 受保护路径拦截**: `.env` / `.ssh` / `.aws` / `.kube` / private keys (`*.pem`/`*.key`) / credentials / shell configs (`.bashrc` …) / `node_modules` / `dist` / `build` … blocked hard in every mode (except naked) 任何模式下硬性阻止(naked 除外)
62
65
  - **Block group / Block 组危险命令**: `mkfs.*` / `mkswap` / `poweroff` / `reboot` / `shutdown` / `dd` to block devices / `> /dev/sdX` / `find -delete` / `find -exec rm` / `xargs rm`
63
66
  - **Confirm group / Confirm 组**: `sudo` / `doas` / `pkexec` / `chmod 777` / `ssh` / `scp` / `sftp` / `rsh` / `telnet` / `wget -O /dev/null`
64
67
  - **Overwrite detection / 覆盖检测**: `mv` / `cp` / `install` / `tee` / `ln -f` / `rsync --delete` on existing targets, classified by in/out project — 目标已存在时按内外策略处理
@@ -69,13 +72,13 @@ After installing, run `/reload` or restart pi. 安装后 `/reload` 或重启 pi
69
72
 
70
73
  ## Development / 开发与测试
71
74
 
72
- Automated tests (83 assertions) load the real extension with a mocked pi API, covering the 4 modes × protected paths / dangerous commands / truncation / git destructive matrix, plus `/guard` command interaction and trusted-mode confirmation flow:
75
+ Automated tests (96 assertions) load the real extension with a mocked pi API, covering the 5 modes × protected paths / dangerous commands / truncation / git destructive matrix, plus `/guard` command interaction, trusted-mode confirmation, and naked-mode double confirmation:
73
76
 
74
77
  ```bash
75
78
  cd tests && node --experimental-strip-types test-pathguard.ts
76
79
  ```
77
80
 
78
- 自动化测试(83 断言)模拟 pi API 加载真实扩展,覆盖 4 种模式 × 受保护路径 / 危险命令 / 截断 / git 破坏性等判定矩阵,以及 `/guard` 命令交互与 trusted 确认流程:
81
+ 自动化测试(96 断言)模拟 pi API 加载真实扩展,覆盖 5 种模式 × 受保护路径 / 危险命令 / 截断 / git 破坏性等判定矩阵,以及 `/guard` 命令交互、trusted 确认与 naked 两级确认流程:
79
82
 
80
83
  ```bash
81
84
  cd tests && node --experimental-strip-types test-pathguard.ts
@@ -37,6 +37,11 @@
37
37
  * - trusted most permissive: overwrites and ordinary-file deletes pass too
38
38
  * - Protected paths (credentials/config/keys) and the Block group (format/shutdown/bulk-delete/
39
39
  * block-device writes) are blocked directly in every mode, with no confirmation opportunity
40
+ *
41
+ * v3.1 adds (upgrade from v3): naked mode — passes almost everything (protected paths, write/edit
42
+ * checks, git destructive, truncate, outside deletes/overwrites); only system-destructive
43
+ * Block-group commands (mkfs/reboot/block-device writes/bulk delete) are still confirmed.
44
+ * Switching to naked requires a double confirmation (stronger than trusted's single warning).
40
45
  */
41
46
 
42
47
  import type {
@@ -185,8 +190,8 @@ const HOME = homedir();
185
190
 
186
191
  // ─── Guard Modes ─────────────────────────────────────────────────────
187
192
 
188
- /** Guard mode: strict (full) / normal (default) / loose (relaxed) / trusted (most permissive) */
189
- type GuardMode = "strict" | "normal" | "loose" | "trusted";
193
+ /** Guard mode: strict (full) / normal (default) / loose (relaxed) / trusted (most permissive) / naked (no protection) */
194
+ type GuardMode = "strict" | "normal" | "loose" | "trusted" | "naked";
190
195
 
191
196
  /** Current session guard mode (switched via /guard; reset to normal on session_start) */
192
197
  let currentMode: GuardMode = "normal";
@@ -197,6 +202,7 @@ const GUARD_MODES: readonly GuardMode[] = [
197
202
  "normal",
198
203
  "loose",
199
204
  "trusted",
205
+ "naked",
200
206
  ];
201
207
 
202
208
  /** Whether a string is a valid guard mode (for /guard argument validation) */
@@ -214,24 +220,26 @@ const MODE_DESCRIPTIONS: Record<GuardMode, string> = {
214
220
  "Loose: pass new-file writes & deletes, confirm overwrites / 放宽:新建/删除免问,覆盖需确认",
215
221
  trusted:
216
222
  "Trusted: pass overwrites & ordinary-file deletes / 最宽松:覆盖/删除普通文件也免问",
223
+ naked:
224
+ "Naked: pass everything except system-destructive cmds (confirmed) / 裸奔:除系统级破坏命令外全部放行(破坏命令弹窗询问)",
217
225
  };
218
226
 
219
227
  /** Full decision matrix (shown as the /guard picker title, English only) */
220
228
  const MODE_MATRIX = [
221
229
  "Path Guard Mode Matrix (B=block / ?=confirm / .=pass)",
222
- " Checkpoint strict normal loose trusted",
223
- " Protected paths .env/.ssh/keys B B B B",
224
- " System-destructive mkfs/reboot B B B B",
225
- " Privilege/remote sudo/ssh/chmod777 B ? ? ?",
226
- " Git destructive reset --hard ? ? ? ?",
227
- " In-project write/edit/new ? . . .",
228
- " In-project delete ? ? . .",
229
- " Outside write (new file) ? ? . .",
230
- " Outside overwrite existing B B ? .",
231
- " Outside delete ordinary B B ? .",
232
- " Truncate existing > file ? ? ? ?",
233
- " HOME dir write ? ? . .",
234
- " No UI (headless) B B B B",
230
+ " Checkpoint strict normal loose trusted naked",
231
+ " Protected paths .env/.ssh/keys B B B B .",
232
+ " System-destructive mkfs/reboot B B B B ?",
233
+ " Privilege/remote sudo/ssh/chmod777 B ? ? ? .",
234
+ " Git destructive reset --hard ? ? ? ? .",
235
+ " In-project write/edit/new ? . . . .",
236
+ " In-project delete ? ? . . .",
237
+ " Outside write (new file) ? ? . . .",
238
+ " Outside overwrite existing B B ? . .",
239
+ " Outside delete ordinary B B ? . .",
240
+ " Truncate existing > file ? ? ? ? .",
241
+ " HOME dir write ? ? . . .",
242
+ " No UI (headless) B B B B .",
235
243
  ].join("\n");
236
244
 
237
245
  /** Guard verdict: { block, reason } to block / undefined to allow (askConfirm returns a Promise) */
@@ -251,15 +259,15 @@ export default function (pi: ExtensionAPI) {
251
259
  // /guard slash command: view / switch guard mode
252
260
  pi.registerCommand("guard", {
253
261
  description:
254
- "Path Guard modes: /guard shows the current mode, /guard <strict|normal|loose|trusted> switches",
262
+ "Path Guard modes: /guard shows the current mode, /guard <strict|normal|loose|trusted|naked> switches",
255
263
  handler: async (args, ctx) => {
256
264
  const m = args?.trim().toLowerCase() ?? "";
257
265
 
258
- // Valid argument → switch directly (shortcut, no picker); trusted requires a warning confirmation
266
+ // Valid argument → switch directly (shortcut, no picker); trusted/naked require a warning confirmation
259
267
  if (isGuardMode(m)) {
260
- if (m === "trusted" && !(await confirmTrustedSwitch(ctx))) {
268
+ if (!(await confirmModeSwitch(m, ctx))) {
261
269
  ctx.ui.notify(
262
- "Cancelled: switching to trusted requires confirmation",
270
+ `Cancelled: switching to ${m} requires confirmation`,
263
271
  "info",
264
272
  );
265
273
  return;
@@ -293,9 +301,9 @@ export default function (pi: ExtensionAPI) {
293
301
  }
294
302
  const picked = chosen.split(/\s+/)[0] as GuardMode;
295
303
  if (isGuardMode(picked)) {
296
- if (picked === "trusted" && !(await confirmTrustedSwitch(ctx))) {
304
+ if (!(await confirmModeSwitch(picked, ctx))) {
297
305
  ctx.ui.notify(
298
- "Cancelled: switching to trusted requires confirmation",
306
+ `Cancelled: switching to ${picked} requires confirmation`,
299
307
  "info",
300
308
  );
301
309
  return;
@@ -330,6 +338,9 @@ function checkWriteEdit(
330
338
  const path = input.path;
331
339
  if (!path) return;
332
340
 
341
+ // naked disables ALL checks (incl. protected paths & the write/edit tools) → pass everything
342
+ if (currentMode === "naked") return;
343
+
333
344
  // Resolve the real cwd first (cwd may itself be a symlink), then the real target path,
334
345
  // preventing symlink escape to protected locations and symlink-cwd false positives
335
346
  const realCwd = resolveReal(ctx.cwd);
@@ -434,6 +445,14 @@ function classifySegment(
434
445
  // Recursion depth guard (bash -c / eval nested too deep to statically check → conservative confirm)
435
446
  if (depth > 4) return { kind: "confirm" };
436
447
 
448
+ // naked: pass everything EXCEPT system-destructive Block-group commands, which are confirmed
449
+ // (protected paths, write/edit, git destructive, truncate, etc. all pass in naked mode)
450
+ if (currentMode === "naked") {
451
+ return dangerousLevel(trimmed) === "block"
452
+ ? { kind: "confirm" }
453
+ : { kind: "pass" };
454
+ }
455
+
437
456
  // ① Redirect check:
438
457
  // - Write to a protected path (echo x > .env etc.) → block
439
458
  // - "> existing file" (truncate, not >> append, not a device) → confirm
@@ -1270,6 +1289,34 @@ async function confirmTrustedSwitch(
1270
1289
  );
1271
1290
  }
1272
1291
 
1292
+ /** Double confirmation before switching to naked: disables ALL protection (incl. protected paths, destructive commands, and write/edit checks) */
1293
+ async function confirmNakedSwitch(
1294
+ ctx: ExtensionCommandContext,
1295
+ ): Promise<boolean> {
1296
+ // No UI (headless) cannot confirm → conservatively refuse the switch
1297
+ if (!ctx.hasUI) return false;
1298
+ const first = await ctx.ui.confirm(
1299
+ "⚠️ Switch to NAKED mode?",
1300
+ "naked passes nearly everything: protected paths (.env/.ssh/keys), write/edit tool checks, git\ndestructive ops, truncation, and outside deletes/overwrites are no longer blocked or prompted.\nOnly system-destructive commands (mkfs/reboot/bulk-delete/block-device writes) are still\nconfirmed — everything else is allowed without a prompt.",
1301
+ );
1302
+ if (!first) return false;
1303
+ // Second, final confirmation — makes an accidental /guard naked far less likely
1304
+ return ctx.ui.confirm(
1305
+ "⚠️⚠️ FINAL confirmation — disable ALL protection?",
1306
+ "This is the final step. After this, path-guard passes nearly every operation with no blocking and\nno confirmation, including writes to protected paths and git destructive / truncate / outside\ndelete operations. Only system-destructive commands (mkfs/reboot/bulk-delete/block-device\nwrites) will still prompt for confirmation.\n\nOnly switch if you are certain you want minimal protection.",
1307
+ );
1308
+ }
1309
+
1310
+ /** Mode-switch confirmation: trusted → single warn; naked → double warn; others → no confirmation */
1311
+ async function confirmModeSwitch(
1312
+ mode: GuardMode,
1313
+ ctx: ExtensionCommandContext,
1314
+ ): Promise<boolean> {
1315
+ if (mode === "trusted") return confirmTrustedSwitch(ctx);
1316
+ if (mode === "naked") return confirmNakedSwitch(ctx);
1317
+ return true;
1318
+ }
1319
+
1273
1320
  async function askConfirm(
1274
1321
  ctx: ExtensionContext,
1275
1322
  message: string,
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@yaosu/pi-path-guard",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
- "description": "Path Guard for pi — blocks destructive commands & path overwrites, protects .env/keys, with strict/normal/loose/trusted guard modes (/guard). pi 防误删/防误覆盖扩展,支持 4 种防护模式。",
5
+ "description": "Path Guard for pi — blocks destructive commands & path overwrites, protects .env/keys, with strict/normal/loose/trusted/naked guard modes (/guard). pi 防误删/防误覆盖扩展,支持 5 种防护模式。",
6
6
  "keywords": [
7
7
  "pi-package",
8
8
  "pi-extension",