@gotgenes/pi-permission-system 16.2.1 → 17.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +2 -1
  3. package/package.json +1 -1
  4. package/src/access-intent/access-intent.ts +8 -10
  5. package/src/access-intent/access-path.ts +39 -16
  6. package/src/access-intent/bash/bash-path-resolver.ts +531 -0
  7. package/src/access-intent/bash/program.ts +21 -14
  8. package/src/access-intent/bash/token-classification.ts +1 -1
  9. package/src/canonicalize-path.ts +11 -5
  10. package/src/forwarded-permissions/permission-forwarder.ts +11 -1
  11. package/src/forwarding-manager.ts +7 -1
  12. package/src/handlers/before-agent-start.ts +1 -0
  13. package/src/handlers/gates/bash-path-extractor.ts +7 -5
  14. package/src/handlers/gates/bash-path.ts +13 -13
  15. package/src/handlers/gates/external-directory.ts +14 -16
  16. package/src/handlers/gates/path.ts +12 -7
  17. package/src/handlers/gates/skill-read.ts +7 -1
  18. package/src/handlers/gates/tool-call-gate-pipeline.ts +21 -4
  19. package/src/handlers/gates/tool.ts +4 -2
  20. package/src/index.ts +12 -1
  21. package/src/input-normalizer.ts +9 -4
  22. package/src/path-normalizer.ts +70 -0
  23. package/src/path-utils.ts +39 -28
  24. package/src/permission-manager.ts +21 -7
  25. package/src/permission-session.ts +35 -2
  26. package/src/prompting-gateway.ts +3 -0
  27. package/src/rule.ts +8 -6
  28. package/src/skill-prompt-sanitizer.ts +15 -4
  29. package/src/subagent-context.ts +17 -9
  30. package/test/access-intent/access-path.test.ts +124 -15
  31. package/test/access-intent/bash/program.test.ts +178 -80
  32. package/test/bash-external-directory.test.ts +15 -1
  33. package/test/canonicalize-path.test.ts +34 -8
  34. package/test/forwarding-manager.test.ts +7 -1
  35. package/test/handlers/external-directory-symlink-acceptance.test.ts +26 -4
  36. package/test/handlers/gates/bash-command-metamorphic.test.ts +5 -1
  37. package/test/handlers/gates/bash-external-directory.test.ts +5 -2
  38. package/test/handlers/gates/bash-path.test.ts +14 -9
  39. package/test/handlers/gates/external-directory-policy.test.ts +28 -18
  40. package/test/handlers/gates/external-directory.test.ts +9 -1
  41. package/test/handlers/gates/path.test.ts +90 -20
  42. package/test/handlers/gates/skill-read.test.ts +22 -13
  43. package/test/handlers/gates/tool-call-gate-pipeline.test.ts +2 -1
  44. package/test/handlers/gates/tool.test.ts +13 -1
  45. package/test/helpers/gate-fixtures.ts +11 -2
  46. package/test/helpers/session-fixtures.ts +3 -0
  47. package/test/input-normalizer.test.ts +104 -39
  48. package/test/path-normalizer.test.ts +123 -0
  49. package/test/path-utils.test.ts +130 -51
  50. package/test/permission-forwarder.test.ts +1 -0
  51. package/test/permission-manager-unified.test.ts +40 -0
  52. package/test/permission-resolver.test.ts +14 -32
  53. package/test/permission-session.test.ts +41 -0
  54. package/test/pi-infrastructure-read.test.ts +27 -4
  55. package/test/prompting-gateway.test.ts +1 -0
  56. package/test/rule.test.ts +88 -42
  57. package/test/session-rules.test.ts +21 -4
  58. package/test/skill-prompt-sanitizer.test.ts +69 -14
  59. package/test/subagent-context.test.ts +77 -31
  60. package/test/synthesize.test.ts +13 -11
  61. package/src/access-intent/bash/cwd-projection.ts +0 -498
package/CHANGELOG.md CHANGED
@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [17.1.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v17.0.0...pi-permission-system-v17.1.0) (2026-06-28)
9
+
10
+
11
+ ### Features
12
+
13
+ * **pi-permission-system:** add PathNormalizer collaborator ([#510](https://github.com/gotgenes/pi-packages/issues/510)) ([d016089](https://github.com/gotgenes/pi-packages/commit/d016089690aff3821eabd5bd5351bb3a0c3639d4))
14
+
15
+ ## [17.0.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v16.2.1...pi-permission-system-v17.0.0) (2026-06-27)
16
+
17
+
18
+ ### ⚠ BREAKING CHANGES
19
+
20
+ * **pi-permission-system:** the path surface now also matches the canonical (symlink-resolved) form of bash path-rule tokens, so a path rule can fire on a symlink alias it previously missed, changing decisions on upgrade with no config edit.
21
+ * **pi-permission-system:** the path surface now also matches the canonical (symlink-resolved) form of a tool's file path. A path rule that previously matched only the as-typed spelling now also matches when the path resolves through a symlink to a target the pattern covers, which can change allow/deny decisions on upgrade with no config edit.
22
+
23
+ ### Features
24
+
25
+ * **pi-permission-system:** add AccessPath.forPath and forLiteral factories ([4323cae](https://github.com/gotgenes/pi-packages/commit/4323cae859907dd62d9cb401c194443717ab752c)), closes [#486](https://github.com/gotgenes/pi-packages/issues/486)
26
+ * **pi-permission-system:** match the canonical form on the bash-path gate ([6ce0c06](https://github.com/gotgenes/pi-packages/commit/6ce0c06b35c12eca32ffce5d4fe974c2f2dee393))
27
+ * **pi-permission-system:** match the canonical form on the path tool gate ([869ca76](https://github.com/gotgenes/pi-packages/commit/869ca761cf3d7a8367eb932106065a866d996ce4))
28
+
29
+
30
+ ### Documentation
31
+
32
+ * **pi-permission-system:** document canonical path-surface matching ([9606dce](https://github.com/gotgenes/pi-packages/commit/9606dce744bc1f95bd8424d2bb115a376d9a1191)), closes [#486](https://github.com/gotgenes/pi-packages/issues/486)
33
+
8
34
  ## [16.2.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v16.2.0...pi-permission-system-v16.2.1) (2026-06-27)
9
35
 
10
36
 
package/README.md CHANGED
@@ -17,7 +17,7 @@ Permission enforcement extension for the [Pi](https://pi.mariozechner.at/) codin
17
17
  - **Enforces allow / ask / deny** at tool-call time with UI confirmation dialogs
18
18
  - **Controls bash commands** with wildcard pattern matching (`git *: ask`, `rm -rf *: deny`)
19
19
  - **Gates MCP and skill access** at server, tool, and skill-name granularity
20
- - **Protects sensitive file patterns** — cross-cutting `path` rules deny `.env`, `~/.ssh/*`, etc. across all tools and bash at once
20
+ - **Protects sensitive file patterns** — cross-cutting `path` rules deny `.env`, `~/.ssh/*`, etc. across all tools and bash at once, matching both the path as referenced and its symlink-resolved form so a deny cannot be evaded through a symlink alias
21
21
  - **Guards external paths** — prompts before file tools or bash commands reach outside `cwd`
22
22
  - **Fails closed** — an internal gate error blocks the tool (with a `gate_error` review-log entry), and an unparseable bash command — or an opaque `bash -c`/`eval` wrapper — prompts (`ask`) rather than passing silently
23
23
  - **Forwards prompts from subagents** — `ask` policies work even in non-UI execution contexts
@@ -69,6 +69,7 @@ See [docs/session-approvals.md](docs/session-approvals.md) for details on sessio
69
69
 
70
70
  The `path` surface is a cross-cutting gate that applies to **all** file access — Pi tools, bash commands, MCP calls, and extension tools alike.
71
71
  Extension and MCP tools that operate on paths (via `input.path`, MCP's `input.arguments.path`, or a registered access extractor) are gated by default, so a `path` deny cannot be overridden by a per-tool allow — making it the right place to protect sensitive files like `.env` or `~/.ssh/*` from every tool at once.
72
+ A `path` pattern matches both the path as the agent references it and its canonical (symlink-resolved) form, so a deny still fires when a symlink aliases a sensitive target.
72
73
 
73
74
  For per-tool path patterns (`read`, `write`, `edit`, `find`, `grep`, `ls`), patterns are matched against the file path from `input.path`.
74
75
  This lets you express rules like "allow reads but deny `.env` files" at the individual tool level.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "16.2.1",
3
+ "version": "17.1.0",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,8 +17,9 @@ export interface ToolAccessIntent {
17
17
  /**
18
18
  * Precomputed equivalent policy values for a path-shaped surface.
19
19
  *
20
- * Used by the bash-path gate (`path` surface), whose cd-resolved lexical
21
- * `string[]` carry no canonical-boundary notion.
20
+ * Not gate-emitted: the resolver produces it internally by unwrapping an
21
+ * `access-path` intent via `matchValues()`, keeping the low-level manager
22
+ * string-based (it never imports `AccessPath`). See {@link ResolvedAccessIntent}.
22
23
  */
23
24
  export interface PathValuesAccessIntent {
24
25
  kind: "path-values";
@@ -31,9 +32,9 @@ export interface PathValuesAccessIntent {
31
32
  /**
32
33
  * An `AccessPath` value object for a path-shaped surface.
33
34
  *
34
- * Used by the external-directory gates; lets `AccessPath` flow into the
35
- * resolver as a first-class variant so the resolver — not the gate — asks it
36
- * for `matchValues()` (Tell-Don't-Ask).
35
+ * Emitted by every path gate (the `path` and `external_directory` surfaces);
36
+ * lets `AccessPath` flow into the resolver as a first-class variant so the
37
+ * resolver — not the gate — asks it for `matchValues()` (Tell-Don't-Ask).
37
38
  */
38
39
  export interface AccessPathAccessIntent {
39
40
  kind: "access-path";
@@ -42,11 +43,8 @@ export interface AccessPathAccessIntent {
42
43
  agentName?: string;
43
44
  }
44
45
 
45
- /** What a gate emits — the full three-variant union. */
46
- export type AccessIntent =
47
- | ToolAccessIntent
48
- | PathValuesAccessIntent
49
- | AccessPathAccessIntent;
46
+ /** What a gate emits — a raw tool input or an `AccessPath`. */
47
+ export type AccessIntent = ToolAccessIntent | AccessPathAccessIntent;
50
48
 
51
49
  /**
52
50
  * What the manager consumes — the `access-path` variant has already been
@@ -20,7 +20,9 @@ import {
20
20
  * - {@link value} returns `string` — the lexical absolute form, for display,
21
21
  * approval patterns, decision values, and logs.
22
22
  *
23
- * Construct via {@link forExternalDirectory}; the constructor is private.
23
+ * Construct via {@link forPath} (resolved, with optional cd-folded base) or
24
+ * {@link forLiteral} (literal-only, for an unknown base); the constructor is
25
+ * private.
24
26
  */
25
27
  export class AccessPath {
26
28
  private constructor(
@@ -65,24 +67,45 @@ export class AccessPath {
65
67
  }
66
68
 
67
69
  /**
68
- * Build an `AccessPath` for a tool-input or bash-token path resolved against
69
- * `cwd`.
70
+ * Build an `AccessPath` for a tool-input or bash-token path, resolved against
71
+ * `resolveBase` (the cd-folded effective directory; defaults to `cwd`).
70
72
  *
71
- * - `matchValues()` returns the same set as the former
72
- * `getExternalDirectoryPolicyValues(pathValue, cwd)` the lexical alias
73
- * union from `getPathPolicyValues` plus the canonical alias from
74
- * `canonicalNormalizePathForComparison` (#418).
75
- * - `boundaryValue()` returns `canonicalNormalizePathForComparison(pathValue, cwd)`,
76
- * which is win32-lowercased (#382) do not substitute a raw
77
- * `canonicalizePath` output here.
78
- * - `value()` returns `normalizePathForComparison(pathValue, cwd)`, the
79
- * absolute lexical form.
73
+ * Serves every path surface: the tool path gate, the tool external-directory
74
+ * gate, and the bash path/external-directory gates (which pass a cd-resolved
75
+ * `resolveBase`).
76
+ *
77
+ * - `matchValues()` returns the lexical alias union from `getPathPolicyValues`
78
+ * plus the canonical alias from `canonicalNormalizePathForComparison`
79
+ * (#418), so a config pattern on either the typed or symlink-resolved form
80
+ * matches.
81
+ * - `boundaryValue()` returns
82
+ * `canonicalNormalizePathForComparison(pathValue, resolveBase)`, which is
83
+ * win32-lowercased (#382) — do not substitute a raw `canonicalizePath`
84
+ * output here.
85
+ * - `value()` returns `normalizePathForComparison(pathValue, resolveBase)`,
86
+ * the absolute lexical form.
80
87
  */
81
- static forExternalDirectory(pathValue: string, cwd: string): AccessPath {
88
+ static forPath(
89
+ pathValue: string,
90
+ options: { cwd: string; resolveBase?: string; platform: NodeJS.Platform },
91
+ ): AccessPath {
92
+ const { cwd, resolveBase = cwd, platform } = options;
82
93
  return new AccessPath(
83
- normalizePathForComparison(pathValue, cwd),
84
- getPathPolicyValues(pathValue, { cwd }),
85
- canonicalNormalizePathForComparison(pathValue, cwd),
94
+ normalizePathForComparison(pathValue, resolveBase, platform),
95
+ getPathPolicyValues(pathValue, { cwd, resolveBase }, platform),
96
+ canonicalNormalizePathForComparison(pathValue, resolveBase, platform),
86
97
  );
87
98
  }
99
+
100
+ /**
101
+ * Build a literal-only `AccessPath` for a path whose effective base is
102
+ * unknown (a relative bash token after a non-literal `cd`).
103
+ *
104
+ * Carries no canonical alias and no absolute resolution — `matchValues()` is
105
+ * `[literal]` (or `[]` when empty) and `boundaryValue()` is `""` — so no
106
+ * spurious absolute or symlink-resolved rule can match (#393).
107
+ */
108
+ static forLiteral(literal: string): AccessPath {
109
+ return new AccessPath(literal, literal ? [literal] : [], "");
110
+ }
88
111
  }