@tduyng/pi-guard 0.0.2 → 0.0.4

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 +51 -60
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,85 +1,72 @@
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 for the ways a session ends badly. Three are hooks and spawn no schema, so a session that never needs them pays nothing.
4
4
 
5
- ## Reserve
5
+ | Guard | Failure it prevents |
6
+ | --- | --- |
7
+ | `reserve` | A peer session edits a file this session is working on |
8
+ | `cloak` | A key file lands in the transcript and goes to the provider |
9
+ | `git-interceptor` | Git opens an editor and hangs the turn, or `--no-verify` skips a hook |
10
+ | `reject-guard` | One unhandled promise takes down the session |
6
11
 
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.
12
+ Only sessions that load `reserve` pay for it, at 215 tokens.
8
13
 
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.
14
+ ## Sessions in one folder
10
15
 
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.
16
+ Pi serializes writes inside one process. Two sessions in one folder do not see each other, so the second overwrites a file the first is halfway through reshaping.
12
17
 
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.
18
+ A session writes what it holds to `.pi/reservations/<session>.json`. A `tool_call` hook refuses `edit` and `write` against a path a live peer holds, and it checks `bash` too, so `sed -i`, `git checkout` and a redirect into a reserved path are turned away while a read-only command passes.
20
19
 
21
- ## Cloak: secret redaction
22
-
23
- A `tool_result` hook intercepts `read` tool results and redacts secrets before they enter the model's context. Three independent checks, all configurable:
24
-
25
- ### Vendor key detection
26
-
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:
28
-
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
40
-
41
- Each match is replaced with a marker: the first 4 characters plus stars (minimum 8, maximum 20) plus `[REDACTED:type]`.
42
-
43
- ### PEM block detection
20
+ ```text
21
+ reserve({action: "reserve", paths: ["extensions/pi-goal/src/"]})
22
+ reserve({action: "list"})
23
+ reserve({action: "release"})
24
+ ```
44
25
 
45
- Matches `-----BEGIN ...-----` certificate and key blocks. Replaced with `[REDACTED:PEM TYPE]`.
26
+ A directory reserves its whole subtree, an exact path reserves one file, and `release` with no paths gives everything back. The refusal names the holder, so the other session is one that can be inspected.
46
27
 
47
- ### Sensitive file types
28
+ Liveness is `process.kill(pid, 0)`, so a killed session takes its reservations with it. No heartbeat and no daemon. Writes go through a temp file and a rename, so two sessions reserving at once cannot leave a half-written file.
48
29
 
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).
30
+ A mutation that names no path is invisible to this, so `git add -A` and a bare `git commit` still take whatever is staged. Two sessions committing in one repository also race on `.git/index.lock`, and no reservation covers that. Commit from a `subagent` with `isolation: "worktree"` instead, whose checkout is its own.
50
31
 
51
- ### Custom patterns
32
+ ## Secrets in the transcript
52
33
 
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:
34
+ A `tool_result` hook cleans `read` results on their way into the model. Three checks, each switchable:
54
35
 
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.
36
+ | Check | Catches | Replacement |
37
+ | --- | --- | --- |
38
+ | Vendor keys | AWS, OpenAI, Anthropic, Stripe, GitHub, Slack, GitLab, Google, npm, SendGrid, JWTs, `Bearer` | First 4 characters, stars, `[REDACTED:type]` |
39
+ | PEM blocks | `-----BEGIN ...-----` certificates and keys | `[REDACTED:PEM TYPE]` |
40
+ | Sensitive paths | `.pem`, `.crt`, `.p12`, SSH keys, `.aws/credentials`, `.npmrc`, `.netrc`, `.docker/config.json`, `.kube/config` | The whole read becomes `[REDACTED:Protected File (...)]` |
63
41
 
64
- When multiple patterns overlap the same span, the first applied match wins and later matches overlapping that span are skipped.
42
+ Secrets have known prefixes, so `AKIA`, `sk-`, `ghp_`, `glpat-`, `xoxb-`, `AIza` and a dozen more are matched as literals with a word boundary. An earlier version scanned by entropy and flagged nix store hashes and `sha256` digests in derivations, so that check is gone.
65
43
 
66
- Use `/cloak-status` to see whether cloak is enabled, how many custom patterns are loaded, and which built-in checks are on.
44
+ Custom rules go in `<agent-dir>/cloak.json` or `.pi/cloak.json`. A project file overrides the scalar keys and appends its `patterns`.
67
45
 
68
- ### What cloak does not do
46
+ | Key | Default | Meaning |
47
+ | --- | --- | --- |
48
+ | `enabled` | `true` | Master switch |
49
+ | `cloakCharacter` | `*` | Masking character |
50
+ | `cloakLength` | `null` | Fixed replacement length, null keeps the original |
51
+ | `tryAllPatterns` | `true` | Apply every matching rule, not just the first |
52
+ | `patterns` | `[]` | Rules with `filePattern` (glob), `cloakPattern` (regex), optional `replace` |
53
+ | `vendorKeys` | `true` | Built-in prefix detection |
54
+ | `pemBlocks` | `true` | PEM detection |
55
+ | `sensitiveFileTypes` | `true` | Whole-file redaction |
69
56
 
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.
57
+ When two matches overlap, the first rule applied wins. A custom pattern replaces only its matched spans, while a sensitive file type replaces the whole read. `/cloak-status` shows whether cloak is on, how many custom patterns loaded, and which built-in checks are active.
71
58
 
72
- ## Git interceptor
59
+ ## Git in a non-interactive turn
73
60
 
74
- Two guards for agent-driven git commands:
61
+ `GIT_EDITOR=true`, `GIT_SEQUENCE_EDITOR=true` and `GIT_MERGE_AUTOEDIT=no` go 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.
75
62
 
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.
63
+ The extension blocks a bash command containing `--no-verify`, because the agent should fix what the hook complains about or ask. The check is string matching, best effort, and not a security boundary.
77
64
 
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.
65
+ ## The rejection that kills a session
79
66
 
80
- ## Reject guard
67
+ Pi installs an `uncaughtException` handler and no `unhandledRejection` handler, so one unhandled rejected promise tears down the session. The guard adds the missing listener, logs the reason with a stack trace to stderr prefixed `[reject-guard] unhandled rejection (session kept alive):`, and the turn continues.
81
68
 
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.
69
+ It installs once. `/reload` re-evaluates the module, and a flag on `globalThis` stops a second registration from logging every rejection twice.
83
70
 
84
71
  ## Install
85
72
 
@@ -89,6 +76,10 @@ pi install npm:@tduyng/pi-guard
89
76
 
90
77
  Local checkout: `pi install /path/to/dotfiles/pi/agent/extensions/pi-guard`
91
78
 
92
- ## Configure
79
+ All four guards activate on install. Add `cloak.json` when a rule fires too widely or misses a secret shape.
80
+
81
+ ## Limits
93
82
 
94
- No configuration required. All four guards activate on install. Customize redaction via `cloak.json` as described above.
83
+ - Cloak reads the `read` result on its way into the model. It does not touch the outbound provider payload, the assistant's reply, or any tool other than `read`, and it does not redact a file pasted into a prompt.
84
+ - A reservation is advisory between pi sessions. A shell opened by hand is outside it.
85
+ - `--no-verify` blocking can be worked around by anyone who wants to.
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.4",
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",