prae-cli 0.9.2 → 0.10.1
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/lib/bin.js +2 -6
- package/lib/{checkpoint-C_q_Rpa5.js → checkpoint-BLOGdWFu.js} +1 -1
- package/lib/cli-BlWTUnGJ.js +6 -0
- package/lib/{cli-B_B6-bUZ.js → cli-C_6dosaw.js} +629 -534
- package/lib/docs/pilot-claude-code.md +192 -0
- package/lib/hook-DxLgNtc4.js +693 -0
- package/lib/index.js +4 -4
- package/lib/init-BPlBJvA-.js +272 -0
- package/lib/{instructions-ya0LVnZy.js → instructions-fCJ4j7ce.js} +2 -1
- package/lib/{mcp-BCXbpqu4.js → mcp-DdEDvTvR.js} +1 -1
- package/lib/playground.js +717 -109
- package/lib/prae-baseline.prae.json +144 -1
- package/lib/{receipt-check-DQK_gVPc.js → receipt-check-2F4zLEW5.js} +1 -1
- package/lib/src-2iJy5MZc.js +3 -0
- package/lib/src-CZYvvEn5.js +1691 -0
- package/lib/{src-LA7NdLKb.js → src-CuiWC80o.js} +104 -25
- package/lib/{test-run-CCDgJ0Dp.js → test-run-BGsK-inZ.js} +1 -1
- package/lib/views-C7DD-Oif.js +1396 -0
- package/package.json +6 -6
- package/lib/evaluate-BqLxhYiP.js +0 -499
- package/lib/src-7X6dO7gC.js +0 -3
- package/lib/views-D4UEfbzt.js +0 -1505
- /package/lib/{ocsf-DhVGbBUw.js → ocsf-BwWyM7hc.js} +0 -0
- /package/lib/{src-CBGZVvt7.js → src-BoBCmiwj.js} +0 -0
- /package/lib/{src-d4c_n6wd.js → src-XukJfuml.js} +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Pilot: PRAE on one Claude Code laptop
|
|
2
|
+
|
|
3
|
+
PRAE runs as a Claude Code `PreToolUse` hook. Before every tool call (built-in
|
|
4
|
+
tools such as `Read`, `Write`, `Edit`, `Bash`, `Glob`, `Grep`, `WebFetch`, and
|
|
5
|
+
every MCP tool `mcp__<server>__<tool>`), Claude Code runs `prae hook
|
|
6
|
+
claude-code`. The hook judges the call against a policy pack with the same
|
|
7
|
+
kernel the other PRAE harnesses use, and appends the verdict to a signed,
|
|
8
|
+
hash-chained ledger on this machine.
|
|
9
|
+
|
|
10
|
+
It starts in **shadow mode**. Shadow mode records every verdict and blocks
|
|
11
|
+
nothing. When the record looks right, one command switches it to **enforce**.
|
|
12
|
+
|
|
13
|
+
There is no cloud account, no open port, no network call and no service to run.
|
|
14
|
+
|
|
15
|
+
**Verified on Claude Code 2.1.233**, from a fresh clone, by following this
|
|
16
|
+
guide in a temporary project:
|
|
17
|
+
|
|
18
|
+
- **Shadow.** One headless session made 10 tool calls: `Read`, `Write`, `Edit`,
|
|
19
|
+
`Bash`, `Glob`, `Grep`, `WebFetch` and `mcp__prae__read_file`. All 10 ran,
|
|
20
|
+
and each one was sealed with its verdict. The `.env` read was sealed as a
|
|
21
|
+
shadow deny (`BASE-005`), and `rm -rf` as a shadow hold (`BASE-002`).
|
|
22
|
+
- **Enforce.** Claude Code refused these: the `.env` read, the same read
|
|
23
|
+
through the MCP tool, a fetch to an unlisted host, edits to the pack and
|
|
24
|
+
to the hook settings, a shell command truncating the pack, and a read of
|
|
25
|
+
the signing key. `rm -rf` and a write outside the workspace were held, and
|
|
26
|
+
`claude -p` refused them. Nothing protected changed on disk.
|
|
27
|
+
- **Record.** `prae verify` passed the chain, with 25 of 25 signatures.
|
|
28
|
+
|
|
29
|
+
## 1. Install
|
|
30
|
+
|
|
31
|
+
You need Node 22.19 or later. Install the CLI globally, then preview the
|
|
32
|
+
change and apply it:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g prae-cli # prae-cli 0.10.0 or later carries the hook
|
|
36
|
+
prae init claude-code --scope user # prints the settings diff, writes nothing
|
|
37
|
+
prae init claude-code --scope user --apply # installs, shadow mode
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Install globally rather than with `npx prae-cli init …`: the settings entry
|
|
41
|
+
runs the `prae` it was installed from, and an `npx` copy lives in npm's
|
|
42
|
+
temporary cache, which npm may clear — the hook would then stop running, and
|
|
43
|
+
Claude Code 2.1.233 lets every call through when a hook cannot start.
|
|
44
|
+
|
|
45
|
+
You must name a target. `--scope user` is `~/.claude/settings.json`, which
|
|
46
|
+
covers every project. `--scope project` is `./.claude/settings.json`.
|
|
47
|
+
`--settings <file>` is any file you name, for example
|
|
48
|
+
`.claude/settings.local.json`. Nothing is written without `--apply`. The file
|
|
49
|
+
is written back as 2-space JSON, the format Claude Code itself writes, so a
|
|
50
|
+
hand-formatted file shows layout changes in the diff. Its values do not change.
|
|
51
|
+
|
|
52
|
+
`init` adds one entry and leaves every other setting and hook as it was:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
"PreToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "timeout": 10,
|
|
56
|
+
"command": "'/opt/homebrew/bin/node' '…/lib/node_modules/prae-cli/lib/bin.js' hook claude-code --shadow --pack '~/.prae/claude-code/prae-baseline.prae.json' --ledger '~/.prae/claude-code/ledger.jsonl' --key '~/.prae/claude-code/prae-signing.pem' --settings '~/.claude/settings.json'" }] }]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The real file holds absolute paths. The command names the node binary and
|
|
60
|
+
PRAE's own script by absolute path, so it does not depend on `PATH`. If you
|
|
61
|
+
upgrade or reinstall prae-cli, or switch Node versions, run `init` again. Claude Code reloads settings on its own,
|
|
62
|
+
so new tool calls go through PRAE without a restart.
|
|
63
|
+
|
|
64
|
+
Options:
|
|
65
|
+
|
|
66
|
+
- `--pack <file>`: use your own pack. The default copies the built-in baseline
|
|
67
|
+
(13 rules: download-and-execute, `rm -rf`, privilege escalation, block devices,
|
|
68
|
+
credential files, writes outside the workspace, force-push, publishes, and
|
|
69
|
+
credential stores — the macOS Keychain through `security` and its files,
|
|
70
|
+
`.netrc`, `.git-credentials`, `.npmrc`, Docker, kube, GnuPG, gh and gcloud
|
|
71
|
+
config — read directly or named in a shell command).
|
|
72
|
+
`prae check <file>` summarises a pack.
|
|
73
|
+
- `--ledger <file>`: write the ledger somewhere else.
|
|
74
|
+
- `--dir <dir>`: keep PRAE's state somewhere other than `~/.prae/claude-code/`.
|
|
75
|
+
|
|
76
|
+
## 2. What it records, and where
|
|
77
|
+
|
|
78
|
+
Everything lives in `~/.prae/claude-code/`, which is created with mode 0700:
|
|
79
|
+
|
|
80
|
+
| File | What it is |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `ledger.jsonl` | One row for each tool call. Every Claude Code session on the machine appends to this one file, under a lock, so parallel tool calls and parallel sessions stay one chain. |
|
|
83
|
+
| `prae-signing.pem` | The Ed25519 key that signs each row. It is generated once with mode 0600. |
|
|
84
|
+
| `prae-signing.pub.pem` | The public half. Give it to whoever verifies the record. |
|
|
85
|
+
| `prae-baseline.prae.json` | The pack in force, unless you passed `--pack`. |
|
|
86
|
+
|
|
87
|
+
Each row holds the time, the Claude Code `session_id`, the `tool_use_id` (as
|
|
88
|
+
`correlation`), the tool, its capability (`fs:read`, `fs:write`,
|
|
89
|
+
`proc:spawn`, `net:fetch`), the subject, the verdict, the rule and its reason,
|
|
90
|
+
the pack's name, version and sha256, and, in shadow mode, `shadow: true`. The
|
|
91
|
+
subject is the path or URL only. A shell call is stored as its program word
|
|
92
|
+
and a hash, never the command line. File contents and tool output are never
|
|
93
|
+
stored.
|
|
94
|
+
|
|
95
|
+
Read the record:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
P=~/.prae/claude-code
|
|
99
|
+
prae verify $P/ledger.jsonl --key $P/prae-signing.pub.pem
|
|
100
|
+
prae report $P/ledger.jsonl --pack $P/prae-baseline.prae.json --out report.html
|
|
101
|
+
prae console $P/ledger.jsonl --pack $P/prae-baseline.prae.json --out console/
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
In the report and the console, a shadow verdict is marked `· SHADOW`. It is
|
|
105
|
+
never counted as vetoed or held.
|
|
106
|
+
|
|
107
|
+
## 3. From shadow to enforce
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
prae init claude-code --scope user --enforce --apply
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This replaces PRAE's entry. It does not add a second one. In enforce mode:
|
|
114
|
+
|
|
115
|
+
| Verdict | What Claude Code does |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| allow | The hook prints nothing. Claude Code's own permission flow decides, as before. PRAE never answers `allow`, because that would skip your permission prompt. |
|
|
118
|
+
| deny | `permissionDecision: "deny"`. The call does not run, and Claude reads the reason, for example `PRAE BASE-005: … (ledger row 12)`. |
|
|
119
|
+
| ask | `permissionDecision: "ask"`. You get a permission prompt. In a headless `claude -p` run nobody can answer it, so Claude Code refuses the call (verified on 2.1.233). |
|
|
120
|
+
| gate error | The pack does not parse, the ledger cannot be written, the key is unreadable, the payload is broken, or the ledger lock is held for more than 3 seconds. The hook refuses the call (exit 2) and records the error in the ledger when it can. |
|
|
121
|
+
|
|
122
|
+
In shadow mode a gate error lets the call run. It shows a `systemMessage`
|
|
123
|
+
warning and records a `PRAE-ERROR` row marked `shadow`.
|
|
124
|
+
|
|
125
|
+
## 4. Uninstall
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
prae init claude-code --scope user --remove --apply
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This removes PRAE's hook entry and nothing else. The ledger and the key stay
|
|
132
|
+
in place, because they are the evidence of what ran. Delete
|
|
133
|
+
`~/.prae/claude-code/` yourself when you no longer need them.
|
|
134
|
+
|
|
135
|
+
## 5. Privacy
|
|
136
|
+
|
|
137
|
+
Nothing leaves the machine. The hook reads the payload Claude Code passes on
|
|
138
|
+
stdin, reads the pack, appends to a local file, and exits. It opens no port
|
|
139
|
+
and makes no network request. A `WebFetch` is judged by its URL and is never
|
|
140
|
+
fetched by PRAE. For credential checks the gate reads the file the agent is
|
|
141
|
+
about to read, in its own process, and only the finding (type, line,
|
|
142
|
+
confidence) reaches the ledger.
|
|
143
|
+
|
|
144
|
+
## 6. Limits, stated plainly
|
|
145
|
+
|
|
146
|
+
- **A hook that cannot start does not block, on current Claude Code.** In
|
|
147
|
+
enforce mode, PRAE refuses whenever it runs and fails: a bad pack, an
|
|
148
|
+
unwritable ledger, an exception, its own 3 s lock deadline, or broken
|
|
149
|
+
input. Before Claude Code 2.1.295, though, a hook that cannot start at all
|
|
150
|
+
(node or the checkout moved) or that hits Claude Code's 10 s timeout lets
|
|
151
|
+
the call through. This was checked on 2.1.233. Enforce writes
|
|
152
|
+
`onFailure: "block"`, which Claude Code 2.1.295 and later honour, and
|
|
153
|
+
which 2.1.233 ignores.
|
|
154
|
+
- **The shell is judged by its command line, not by its effect.** The baseline
|
|
155
|
+
stops `Read` of `.env`, but `cat .env` in `Bash` is a different call, and
|
|
156
|
+
no path rule sees it. Write shell rules (`command`) for the programs you care
|
|
157
|
+
about. PRAE does not yet screen tool output in Claude Code. A `PostToolUse`
|
|
158
|
+
hook can only replace output in each tool's exact output shape, which this
|
|
159
|
+
release does not take on.
|
|
160
|
+
- **PRAE's own files are protected only against plain attempts.** Writes to the
|
|
161
|
+
pack, the ledger and the hook settings are refused (`PRAE-SELF`), and so are
|
|
162
|
+
reads of the signing key. Shell commands that name the pack, the settings or
|
|
163
|
+
the key are refused too, even read-only ones such as `wc -c <pack>`. But the agent runs as you. A shell can spell a path
|
|
164
|
+
in ways no string check sees, so a determined agent with `Bash` can still
|
|
165
|
+
edit them. An edited ledger breaks its chain and its signatures. A wholesale
|
|
166
|
+
rewrite by someone who holds the key needs an external checkpoint
|
|
167
|
+
(`prae checkpoint`) to catch.
|
|
168
|
+
- **What the hook never sees.** Files you attach with `@` in a prompt are read
|
|
169
|
+
without a tool call, so no hook fires. Managed settings can switch user hooks
|
|
170
|
+
off (`allowManagedHooksOnly`, `disableAllHooks`).
|
|
171
|
+
- **Coverage depends on knowing what a tool does.** Built-in tools map to
|
|
172
|
+
capabilities by name. MCP tools map by their tool name: the reference
|
|
173
|
+
`filesystem` and `fetch` servers' tools, and `prae mcp`'s own, are
|
|
174
|
+
recognised. Any other MCP tool is recorded and matched only by rules that
|
|
175
|
+
name the tool. `WebSearch` has a query, not a URL, so host rules do not
|
|
176
|
+
apply to it. A path that only appears inside an array (for example
|
|
177
|
+
`read_multiple_files`) is not path-checked.
|
|
178
|
+
- **What happens after a hold is not recorded.** The row says the call was
|
|
179
|
+
held (`ask`). Whether you then approved it in the prompt is not written
|
|
180
|
+
back, because a `PreToolUse` hook gets no outcome. The report lists every
|
|
181
|
+
enforced hold under "awaiting human".
|
|
182
|
+
- **The agent's own account is not evidence.** In the verification runs the
|
|
183
|
+
model twice reported a refused call as having run. The ledger, Claude
|
|
184
|
+
Code's `permission_denials` and the file system agreed with each other, and
|
|
185
|
+
they are what to read.
|
|
186
|
+
- **Budgets cost a ledger read.** A pack with a `count` rule makes each call
|
|
187
|
+
count this session's earlier rows in the ledger. Packs without one never
|
|
188
|
+
read the ledger.
|
|
189
|
+
- **Latency.** Each tool call starts one node process. Measured on an M-series
|
|
190
|
+
Mac, the built `lib/bin.js` took 30 ms at p50 and 34 ms at p95. Run from
|
|
191
|
+
`src/bin.ts` with Node's type stripping, it took 60 ms at p50 and 64 ms at
|
|
192
|
+
p95. A bare `node -e 0` takes 20 ms on the same machine.
|