@tduyng/pi-guard 0.0.2 → 0.0.3

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 (2) hide show
  1. package/README.md +43 -53
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,85 +1,77 @@
1
1
  # @tduyng/pi-guard
2
2
 
3
- Four safety guards for pi: path reservations between parallel sessions, secret redaction on read results, git hook enforcement, and unhandled rejection handling.
3
+ Four guards that cost nothing until something goes wrong: parallel sessions off each other's files, secrets out of read results, git never hanging on an editor, and a stray rejection never killing the session.
4
4
 
5
- ## Reserve
5
+ ## What it costs
6
+
7
+ `reserve` is a deferred tool at 215 tokens in the sessions that load it. The other three are event hooks and spawn no schema at all, so a session that never reserves a path pays nothing for this package.
6
8
 
7
- Pi serializes writes inside one process, so two sessions in one folder still overwrite each other. This guard is the cross-session half. A session records what it holds in `.pi/reservations/<session>.json`, and a `tool_call` hook refuses `edit` and `write` against a path a live peer holds.
9
+ The failures it prevents are the expensive kind. A session killed mid task costs the whole turn, and a credential in a transcript costs a rotation.
8
10
 
9
- Reserving `extensions/pi-goal/src/` takes the whole subtree and an exact path takes one file, while `release` with no paths gives everything back and `list` prints every live session with what it holds.
11
+ ## Reserve
10
12
 
11
- Liveness is `process.kill(pid, 0)`, so a session that is killed takes its reservations with it. Nothing to keep alive, no heartbeat, no daemon.
13
+ Pi serializes writes inside one process, so two sessions in one folder still overwrite each other. This is the cross-session half.
12
14
 
13
- Reservations cover files, and `bash` is checked too: a command that writes a reserved path is
14
- turned away, whether it says `sed -i`, `git checkout`, or redirects into it. A command that only
15
- reads a reserved path is left alone. What this cannot see is a mutation that names no path at
16
- all, so `git add -A` and a bare `git commit` still take whatever is staged. Two sessions
17
- committing in one repo also race on `.git/index.lock`, and no reservation covers that. Delegate
18
- the commit with `isolation: "worktree"` on `subagent`, which works in a
19
- checkout of its own. The branch is named in the result and its commits outlive the checkout.
15
+ A session records what it holds in `.pi/reservations/<session>.json`, and a `tool_call` hook refuses `edit` and `write` against a path a live peer holds. Reserving `extensions/pi-goal/src/` takes the whole subtree, and an exact path takes one file. `release` with no paths gives everything back, and `list` prints every live session with what it holds.
20
16
 
21
- ## Cloak: secret redaction
17
+ Liveness is `process.kill(pid, 0)`, so a killed session takes its reservations with it. Nothing to keep alive, no heartbeat, no daemon.
22
18
 
23
- A `tool_result` hook intercepts `read` tool results and redacts secrets before they enter the model's context. Three independent checks, all configurable:
19
+ `bash` is checked too: a command that writes a reserved path is turned away, whether it says `sed -i`, `git checkout`, or redirects into the path, while a command that only reads it is left alone.
24
20
 
25
- ### Vendor key detection
21
+ What this cannot see is a mutation that names no path, so `git add -A` and a bare `git commit` still take whatever is staged. Two sessions committing in one repo also race on `.git/index.lock`, and no reservation covers that. Delegate the commit with `isolation: "worktree"` on `subagent`, which works in a checkout of its own, and the branch it names outlives the checkout.
26
22
 
27
- Matches known API key shapes by fixed literal prefix. No entropy or shape scanning. Each prefix has a word boundary check so a substring inside a longer identifier does not false-positive. Patterns:
23
+ ## Cloak
28
24
 
29
- - `AKIA` and `ASIA` (AWS access keys)
30
- - `sk-` (OpenAI/Anthropic)
31
- - `sk_live_` and `sk_test_` (Stripe)
32
- - `ghp_`, `gho_`, `ghs_`, `ghr_`, `github_pat_` (GitHub)
33
- - `eyJ` (JWTs, three-part base64 structure)
34
- - `xoxb-`, `xoxp-`, `xoxa-`, `xoxr-`, `xoxs-` (Slack)
35
- - `glpat-` (GitLab)
36
- - `AIza` (Google)
37
- - `npm_` (npm)
38
- - `SG.` (SendGrid)
39
- - `Bearer` followed by a token
25
+ A `tool_result` hook redacts secrets out of `read` results before they enter the model's context. Three checks, each switchable.
40
26
 
41
- Each match is replaced with a marker: the first 4 characters plus stars (minimum 8, maximum 20) plus `[REDACTED:type]`.
27
+ ### Vendor keys
42
28
 
43
- ### PEM block detection
29
+ Known API key shapes by fixed literal prefix, with a word boundary so a substring inside a longer identifier does not match. No entropy scanning and no shape guessing.
44
30
 
45
- Matches `-----BEGIN ...-----` certificate and key blocks. Replaced with `[REDACTED:PEM TYPE]`.
31
+ `AKIA` and `ASIA` for AWS, `sk-` for OpenAI and Anthropic, `sk_live_` and `sk_test_` for Stripe, `ghp_` `gho_` `ghs_` `ghr_` and `github_pat_` for GitHub, `eyJ` for JWTs, `xoxb-` `xoxp-` `xoxa-` `xoxr-` `xoxs-` for Slack, `glpat-` for GitLab, `AIza` for Google, `npm_` for npm, `SG.` for SendGrid, and `Bearer` followed by a token.
46
32
 
47
- ### Sensitive file types
33
+ Each match becomes the first 4 characters, then stars (minimum 8, maximum 20), then `[REDACTED:type]`.
48
34
 
49
- If the file path matches a sensitive pattern, the entire read is replaced with a single marker like `[REDACTED:Protected File (SSH Key)]`. Each pattern carries a type label used in the marker. Patterns include `.pem` (PEM File), `.crt` and `.cer` (Certificate File), `.p12` and `.pfx` (PKCS12 File), `.ssh/id_rsa`, `.ssh/id_ed25519`, `.ssh/id_ecdsa`, and `.ssh/id_dsa` (SSH Key), `.ssh/known_hosts` (SSH Known Hosts), `.ssh/authorized_keys` (SSH Authorized Keys), `.aws/credentials` (AWS Credentials), `.npmrc` (npm Config), `.pypirc` (PyPI Config), `.netrc` (Netrc File), `.docker/config.json` (Docker Config), `.kube/config` (Kube Config).
35
+ ### PEM blocks
50
36
 
51
- ### Custom patterns
37
+ `-----BEGIN ...-----` certificate and key blocks are replaced with `[REDACTED:PEM TYPE]`.
52
38
 
53
- Place a `cloak.json` in the agent directory (`getAgentDir()`) or in a project's `.pi/cloak.json` to add your own rules. A project file overrides the scalar keys and appends its `patterns` to the agent dir ones. Keys:
39
+ ### Sensitive file types
40
+
41
+ A path that matches a sensitive pattern replaces the whole read with one marker, such as `[REDACTED:Protected File (SSH Key)]`. The list covers `.pem`, `.crt`, `.cer`, `.p12`, `.pfx`, SSH keys and `known_hosts` and `authorized_keys`, `.aws/credentials`, `.npmrc`, `.pypirc`, `.netrc`, `.docker/config.json`, and `.kube/config`.
54
42
 
55
- - `enabled` (boolean, default `true`): master switch.
56
- - `cloakCharacter` (string, default `*`): masking character.
57
- - `cloakLength` (number or null): fixed replacement length. Null preserves original length.
58
- - `tryAllPatterns` (boolean, default `true`): apply all matching rules, not just the first.
59
- - `patterns` (array): custom rules with `filePattern` (glob), `cloakPattern` (regex), and optional `replace`.
60
- - `vendorKeys` (boolean, default `true`): built-in vendor prefix detection.
61
- - `pemBlocks` (boolean, default `true`): PEM block detection.
62
- - `sensitiveFileTypes` (boolean, default `true`): whole-file redaction for sensitive paths.
43
+ ### Rules of your own
63
44
 
64
- When multiple patterns overlap the same span, the first applied match wins and later matches overlapping that span are skipped.
45
+ `cloak.json` in the agent directory, or `.pi/cloak.json` in a project, adds patterns. A project file overrides the scalar keys and appends its `patterns` to the agent dir ones.
65
46
 
66
- Use `/cloak-status` to see whether cloak is enabled, how many custom patterns are loaded, and which built-in checks are on.
47
+ | Key | Default | Meaning |
48
+ | --- | --- | --- |
49
+ | `enabled` | `true` | Master switch |
50
+ | `cloakCharacter` | `*` | Masking character |
51
+ | `cloakLength` | `null` | Fixed replacement length, null keeps the original |
52
+ | `tryAllPatterns` | `true` | Apply every matching rule, not just the first |
53
+ | `patterns` | `[]` | Rules with `filePattern` (glob), `cloakPattern` (regex), and optional `replace` |
54
+ | `vendorKeys` | `true` | Built-in prefix detection |
55
+ | `pemBlocks` | `true` | PEM detection |
56
+ | `sensitiveFileTypes` | `true` | Whole-file redaction for sensitive paths |
67
57
 
68
- ### What cloak does not do
58
+ When two matches overlap the same span, the first applied wins and later overlapping matches are skipped. `/cloak-status` shows whether cloak is on, how many custom patterns loaded, and which built-in checks are active.
69
59
 
70
- The extension does not scan by entropy or shape. A nix store hash, a `narHash` field, or a sha256 digest in a Nix derivation will not false-positive. The hook does not touch the outbound provider payload, the assistant's reply, or tools other than `read`. For files matching custom `patterns`, only the matched spans are replaced. Sensitive file types replace the entire read.
60
+ A nix store hash, a `narHash` field, or a sha256 digest in a derivation will not false-positive, because nothing scans by entropy. The hook does not touch the outbound provider payload, the assistant's reply, or any tool other than `read`. A custom `patterns` match replaces only the matched spans, while a sensitive file type replaces the whole read.
71
61
 
72
62
  ## Git interceptor
73
63
 
74
- Two guards for agent-driven git commands:
64
+ Two guards for agent-driven git.
75
65
 
76
- **Editor hang prevention**: sets `GIT_EDITOR=true`, `GIT_SEQUENCE_EDITOR=true`, and `GIT_MERGE_AUTOEDIT=no` on `process.env`. Pi builds a child's env by spreading `process.env`, so this one assignment covers the bash tool, `bg`, and any other spawn. Git never spawns an interactive editor.
66
+ Editor hang prevention sets `GIT_EDITOR=true`, `GIT_SEQUENCE_EDITOR=true`, and `GIT_MERGE_AUTOEDIT=no` on `process.env`. Pi builds a child's environment by spreading `process.env`, so one assignment covers the bash tool, `bg`, and every other spawn.
77
67
 
78
- **Hook bypass prevention**: blocks any bash command containing `--no-verify`. The agent should fix the underlying issue causing the hook to fail, or ask the human for help. This is string matching, best-effort, not a security boundary.
68
+ Hook bypass prevention blocks any bash command containing `--no-verify`. The agent should fix what the hook is complaining about, or ask you. This is string matching and best effort, not a security boundary.
79
69
 
80
70
  ## Reject guard
81
71
 
82
- Pi registers an `uncaughtException` handler but no `unhandledRejection` handler. An unhandled rejected promise tears down the whole session. This guard adds an `unhandledRejection` listener that logs the rejection reason (with stack trace when available) to stderr and keeps the session alive. The log line starts with `[reject-guard] unhandled rejection (session kept alive):`. The listener installs once. A `/reload` re-evaluates the module, and an install flag on `globalThis` keeps a second registration from logging every rejection twice.
72
+ Pi registers an `uncaughtException` handler but no `unhandledRejection` handler, so one unhandled rejected promise tears down the whole session. This guard adds the missing listener, logs the reason with a stack trace to stderr, and keeps the session alive. The line starts with `[reject-guard] unhandled rejection (session kept alive):`.
73
+
74
+ The listener installs once. `/reload` re-evaluates the module, and an install flag on `globalThis` keeps a second registration from logging every rejection twice.
83
75
 
84
76
  ## Install
85
77
 
@@ -89,6 +81,4 @@ pi install npm:@tduyng/pi-guard
89
81
 
90
82
  Local checkout: `pi install /path/to/dotfiles/pi/agent/extensions/pi-guard`
91
83
 
92
- ## Configure
93
-
94
- No configuration required. All four guards activate on install. Customize redaction via `cloak.json` as described above.
84
+ All four guards activate on install with no configuration. Customize redaction through `cloak.json`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tduyng/pi-guard",
3
- "version": "0.0.2",
4
- "description": "Four guards: keep parallel pi sessions off each other's files, redact secrets out of read results, keep a stray rejected promise from killing the session, and stop git from hanging on an editor or skipping hooks.",
3
+ "version": "0.0.3",
4
+ "description": "Secret redaction, cross-session path reservations, git safety, and a rejection guard. Three of the four add no schema at all.",
5
5
  "keywords": [
6
6
  "pi",
7
7
  "pi-package",