@bookedsolid/rea 0.8.0 → 0.9.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/README.md +268 -51
- package/SECURITY.md +24 -7
- package/THREAT_MODEL.md +196 -18
- package/dist/cli/serve.d.ts +8 -0
- package/dist/cli/serve.js +32 -6
- package/dist/cli/status.d.ts +40 -1
- package/dist/cli/status.js +101 -2
- package/dist/gateway/circuit-breaker.d.ts +8 -2
- package/dist/gateway/downstream-pool.d.ts +13 -1
- package/dist/gateway/downstream-pool.js +30 -2
- package/dist/gateway/downstream.d.ts +157 -0
- package/dist/gateway/downstream.js +307 -5
- package/dist/gateway/live-state.d.ts +252 -0
- package/dist/gateway/live-state.js +504 -0
- package/dist/gateway/server.d.ts +44 -1
- package/dist/gateway/server.js +101 -1
- package/dist/gateway/session-blocker.d.ts +132 -0
- package/dist/gateway/session-blocker.js +163 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
**Agentic governance layer for Claude Code — policy enforcement, hook-based safety gates, audit logging, and Codex-integrated adversarial review.**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@bookedsolid/rea)
|
|
6
|
+
[](https://github.com/bookedsolidtech/rea/actions/workflows/ci.yml)
|
|
7
|
+
[](https://docs.npmjs.com/generating-provenance-statements)
|
|
8
8
|
[](./LICENSE)
|
|
9
9
|
[](https://developercertificate.org/)
|
|
10
10
|
[](https://nodejs.org/)
|
|
11
11
|
|
|
12
|
-
> Status: 0.
|
|
12
|
+
> Status: `0.9.x` — published to npm with provenance. See
|
|
13
|
+
> [CHANGELOG.md](./CHANGELOG.md) for the per-release history.
|
|
13
14
|
|
|
14
15
|
---
|
|
15
16
|
|
|
@@ -31,18 +32,38 @@ Node 22+ and pnpm 9+ required.
|
|
|
31
32
|
REA is a governance layer for Claude Code. It is a single npm package that
|
|
32
33
|
ships four things:
|
|
33
34
|
|
|
34
|
-
1. A **hook layer** —
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
1. A **hook layer** — 14 shell scripts total. 12 are registered in the
|
|
36
|
+
shipped `.claude/settings.json` and fire on Claude Code's `PreToolUse`
|
|
37
|
+
/ `PostToolUse` events (secret scanning, dangerous-command
|
|
38
|
+
interception, blocked-path protection, settings protection,
|
|
39
|
+
attribution rejection, env-file protection, disclosure-policy
|
|
40
|
+
routing, dependency audit, changeset security, architecture advisory,
|
|
41
|
+
PR-issue-link advisory, and the Claude-Code push-review adapter).
|
|
42
|
+
One more shipped hook, `commit-review-gate.sh`, is a Claude
|
|
43
|
+
`PreToolUse: Bash` hook that matches `git commit` — it is shipped
|
|
44
|
+
ready-to-wire but intentionally NOT registered in the default
|
|
45
|
+
`.claude/settings.json`, so operators who want commit-time review can
|
|
46
|
+
opt in by adding a rule. The final script,
|
|
47
|
+
`push-review-gate-git.sh`, is a thin native-git adapter that sources
|
|
48
|
+
`hooks/_lib/push-review-core.sh` (the same shared core used by the
|
|
49
|
+
Claude-Code push-review adapter), so a fix to the push-review logic
|
|
50
|
+
lands in one place. It ships for consumers who manually configure
|
|
51
|
+
a wrapper-based `.husky/pre-push` (and as scaffolding for a future
|
|
52
|
+
installer revision). The default `rea init` installer emits a
|
|
53
|
+
standalone inline `.husky/pre-push` body instead of wiring the
|
|
54
|
+
adapter — see the Hooks section for details.
|
|
38
55
|
2. A **gateway layer** — an MCP server (`rea serve`) that proxies downstream
|
|
39
56
|
MCP servers through a middleware chain. Every tool call — native or
|
|
40
57
|
proxied — is classified, policy-checked, redacted, audited, and
|
|
41
|
-
size-capped before it executes.
|
|
42
|
-
|
|
58
|
+
size-capped before it executes. The gateway also supervises downstream
|
|
59
|
+
child processes: unexpected deaths are detected eagerly, the circuit
|
|
60
|
+
breaker never reuses a zombie client, and a `SESSION_BLOCKER` audit
|
|
61
|
+
event fires when a downstream crosses the per-session failure threshold.
|
|
62
|
+
3. A **policy runtime** — `.rea/policy.yaml` with a strict zod-validated
|
|
43
63
|
schema. Defines autonomy level, a hard ceiling (`max_autonomy_level`),
|
|
44
|
-
blocked paths, attribution rules, context protection, and
|
|
45
|
-
|
|
64
|
+
blocked paths, attribution rules, context protection, redaction and
|
|
65
|
+
injection tuning, review/cache knobs, and an optional Discord
|
|
66
|
+
notification webhook.
|
|
46
67
|
4. A **kill switch** — `.rea/HALT` is a single file. If it exists, every
|
|
47
68
|
tool call is denied at the middleware and hook layers. Use
|
|
48
69
|
`rea freeze --reason "..."` to create it and `rea unfreeze --reason "..."`
|
|
@@ -69,7 +90,8 @@ to build a separate package that composes with REA.
|
|
|
69
90
|
no `rea stop`, no systemd unit. A short-lived `.rea/serve.pid`
|
|
70
91
|
breadcrumb is written at startup so `rea status` can detect a live
|
|
71
92
|
gateway — it is removed on graceful shutdown and never used for
|
|
72
|
-
locking or lifecycle management.
|
|
93
|
+
locking or lifecycle management. A per-session `.rea/serve.state.json`
|
|
94
|
+
snapshot accompanies it for live per-downstream introspection.
|
|
73
95
|
- **Not a hosted service.** There is no REA Cloud, no SaaS tier, no
|
|
74
96
|
multi-token workstreams, no workload isolation platform.
|
|
75
97
|
- **Not a 70-agent roster.** 10 curated agents ship in the package. Four
|
|
@@ -130,10 +152,16 @@ its own.
|
|
|
130
152
|
rea doctor
|
|
131
153
|
```
|
|
132
154
|
|
|
133
|
-
`rea doctor` checks
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
155
|
+
`rea doctor` checks `.rea/` directory presence, policy parse, registry
|
|
156
|
+
parse, curated-agent presence, hook coverage, `.claude/settings.json`
|
|
157
|
+
wiring, commit-msg / pre-push git hooks, Codex CLI + agent availability
|
|
158
|
+
(when `codex_required: true`), and the TOFU fingerprint store. It
|
|
159
|
+
returns a pass/fail summary with specific remediation hints. In non-git
|
|
160
|
+
directories (knowledge repos, docs-only projects) the commit-msg and
|
|
161
|
+
pre-push checks are skipped cleanly — REA governs policy and injection
|
|
162
|
+
detection there, not pushes. Audit hash-chain integrity is verified by
|
|
163
|
+
a separate command — `rea check` (on-disk tail) or the full replay
|
|
164
|
+
verifier — not by `rea doctor`.
|
|
137
165
|
|
|
138
166
|
### 4. Watch the running gateway
|
|
139
167
|
|
|
@@ -144,10 +172,31 @@ rea status --json # JSON — pipe to jq
|
|
|
144
172
|
|
|
145
173
|
`rea status` is the live-process view. It reads the pidfile written by
|
|
146
174
|
`rea serve`, verifies the pid is alive, and surfaces the session id,
|
|
147
|
-
policy summary (profile, autonomy, HALT state),
|
|
148
|
-
last timestamp, whether the tail record's hash looks well-formed)
|
|
149
|
-
|
|
150
|
-
|
|
175
|
+
policy summary (profile, autonomy, HALT state), audit stats (lines,
|
|
176
|
+
last timestamp, whether the tail record's hash looks well-formed), and
|
|
177
|
+
— as of 0.9.0 — a **per-downstream live block** sourced from
|
|
178
|
+
`.rea/serve.state.json`. Each downstream entry includes:
|
|
179
|
+
|
|
180
|
+
| Field | Type | Meaning |
|
|
181
|
+
| --------------------------- | ------------------------------------ | --------------------------------------------------------------- |
|
|
182
|
+
| `name` | string | Registry server name |
|
|
183
|
+
| `connected` | boolean | MCP client currently holds an open stdio transport |
|
|
184
|
+
| `healthy` | boolean | Gateway considers the server safe to route calls to |
|
|
185
|
+
| `circuit_state` | `closed` \| `open` \| `half-open` | Current breaker position |
|
|
186
|
+
| `retry_at` | ISO timestamp \| `null` | Next allowed half-open probe, when `open` |
|
|
187
|
+
| `last_error` | string \| `null` | Bounded, redacted diagnostic from the most recent failure |
|
|
188
|
+
| `tools_count` | integer \| `null` | Tool count from the last successful `tools/list` |
|
|
189
|
+
| `open_transitions` | integer | Cumulative circuit-open events in this session |
|
|
190
|
+
| `session_blocker_emitted` | boolean | Whether `SESSION_BLOCKER` has fired for this server yet |
|
|
191
|
+
|
|
192
|
+
`.rea/serve.state.json` is the authoritative live source — it is written
|
|
193
|
+
atomically (temp+rename) on every circuit transition and supervisor
|
|
194
|
+
event, debounced through a 250 ms trailing timer so a flap storm can't
|
|
195
|
+
spam disk. State files written by a pre-0.9.0 gateway degrade gracefully:
|
|
196
|
+
`downstreams` surfaces as `null` with a hint to upgrade.
|
|
197
|
+
|
|
198
|
+
Use `rea check` when you want the pure on-disk view (policy + HALT +
|
|
199
|
+
tail audit) without probing for a live process.
|
|
151
200
|
|
|
152
201
|
### 5. Optional Prometheus `/metrics` endpoint
|
|
153
202
|
|
|
@@ -172,6 +221,39 @@ Set `REA_LOG_LEVEL=debug` for verbose gateway logs; the default is
|
|
|
172
221
|
`info`. Records are JSON lines on a non-TTY stderr and pretty-printed
|
|
173
222
|
on an interactive terminal.
|
|
174
223
|
|
|
224
|
+
### 6. Ask the gateway how it's doing — `__rea__health`
|
|
225
|
+
|
|
226
|
+
The gateway advertises a single built-in tool, `__rea__health`, in
|
|
227
|
+
every `listTools` response regardless of downstream state. Calling it
|
|
228
|
+
returns a snapshot of gateway version, uptime, HALT state, policy
|
|
229
|
+
summary, and per-downstream health. The handler **short-circuits the
|
|
230
|
+
middleware chain** — it is callable under HALT and at any autonomy
|
|
231
|
+
level — because it is the tool an operator reaches for when everything
|
|
232
|
+
else is frozen. Every invocation still writes an audit record.
|
|
233
|
+
|
|
234
|
+
The wire response is **sanitized by default**: `halt_reason` and
|
|
235
|
+
`downstreams[].last_error` surface as `null`. Full diagnostic detail
|
|
236
|
+
lives in the audit record's metadata (`halt_reason`,
|
|
237
|
+
`downstream_errors[]`) — local disk, hash-chained, not
|
|
238
|
+
LLM-reachable — which is the right sink for trusted-operator text.
|
|
239
|
+
|
|
240
|
+
Operators who genuinely need error strings on the MCP wire can opt in:
|
|
241
|
+
|
|
242
|
+
```yaml
|
|
243
|
+
# .rea/policy.yaml
|
|
244
|
+
gateway:
|
|
245
|
+
health:
|
|
246
|
+
expose_diagnostics: true
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Opt-in mode still runs the full sanitizer pass: `redactSecrets` replaces
|
|
250
|
+
known secret patterns with `[REDACTED:*]`, `classifyInjection` replaces
|
|
251
|
+
any non-`clean` diagnostic string (verdicts `suspicious` or
|
|
252
|
+
`likely_injection`) with the exported `INJECTION_REDACTED_PLACEHOLDER`
|
|
253
|
+
token — the literal string `<redacted: suspected injection>` — and
|
|
254
|
+
oversize values are bounded before scanning so an adversarial downstream
|
|
255
|
+
can't DoS the tool with a multi-megabyte error.
|
|
256
|
+
|
|
175
257
|
## Architecture
|
|
176
258
|
|
|
177
259
|
### Middleware chain
|
|
@@ -192,12 +274,12 @@ tool call
|
|
|
192
274
|
│ rate-limit — token bucket per server │
|
|
193
275
|
│ circuit-breaker — trip on downstream failure │
|
|
194
276
|
│ redact (args) — secrets in arguments │
|
|
195
|
-
│ injection — prompt-injection heuristics │
|
|
196
277
|
│ │
|
|
197
278
|
│ ==== EXECUTE ==== │
|
|
198
279
|
│ │
|
|
199
|
-
│ redact (result) — secrets in result │
|
|
200
280
|
│ result-size-cap — bounded response │
|
|
281
|
+
│ redact (result) — secrets in result │
|
|
282
|
+
│ injection — prompt-injection in result │
|
|
201
283
|
│ audit.exit — hash-chained record close │
|
|
202
284
|
└───────────────────────────────────────────────────┘
|
|
203
285
|
│
|
|
@@ -209,14 +291,104 @@ result
|
|
|
209
291
|
from policy. Policy is re-read on every invocation — any edit to
|
|
210
292
|
`policy.yaml` takes effect on the next tool call.
|
|
211
293
|
|
|
212
|
-
|
|
294
|
+
The `__rea__health` meta-tool is the one documented exception: it
|
|
295
|
+
short-circuits the chain (see §6 above) and writes an audit record from
|
|
296
|
+
the short-circuit handler itself.
|
|
297
|
+
|
|
298
|
+
### Gateway supervisor
|
|
299
|
+
|
|
300
|
+
Downstream MCP servers run as child processes over stdio. The
|
|
301
|
+
`DownstreamConnection` wrapper wires the SDK `StdioClientTransport`'s
|
|
302
|
+
`onclose` + `onerror` callbacks, so an unexpected child death — OS
|
|
303
|
+
OOM-kill, unhandled exception in the child, stdio pipe error outside a
|
|
304
|
+
caller-initiated close — is detected **eagerly**: the client and
|
|
305
|
+
transport are nulled before the next `callTool` tries to use them. The
|
|
306
|
+
following call forces a genuine reconnect rather than invoking through a
|
|
307
|
+
stale handle.
|
|
308
|
+
|
|
309
|
+
"Not connected" errors from the SDK (the in-flight fallback) are
|
|
310
|
+
promoted to the same respawn path with the same eager invalidation.
|
|
311
|
+
A 30-second flapping guard refuses a second reconnect that lands too
|
|
312
|
+
quickly after the previous one — the child is clearly unhealthy and the
|
|
313
|
+
circuit breaker is a better place to handle it.
|
|
314
|
+
|
|
315
|
+
`SessionBlockerTracker` subscribes to circuit-breaker
|
|
316
|
+
`onStateChange` events and counts circuit-open transitions per
|
|
317
|
+
`(session_id, server_name)`. Once the threshold (default: 3) is
|
|
318
|
+
crossed, exactly one `SESSION_BLOCKER` audit record is appended and a
|
|
319
|
+
LOUD structured log line is emitted — subsequent opens do not re-fire
|
|
320
|
+
until recovery (a transition to `closed`) re-arms the emit. A new
|
|
321
|
+
session (new `rea serve` process) drops every counter and starts fresh.
|
|
322
|
+
|
|
323
|
+
### Live state
|
|
324
|
+
|
|
325
|
+
`.rea/serve.state.json` is the on-disk live snapshot. It is written
|
|
326
|
+
once at boot and again on every circuit transition or supervisor event,
|
|
327
|
+
debounced through a 250 ms trailing timer and flushed atomically via
|
|
328
|
+
temp-file + rename. The snapshot carries a `session_id` (boot-time
|
|
329
|
+
ownership key) and `owner_pid`; a newly-started `rea serve` whose
|
|
330
|
+
predecessor crashed without cleanup can detect the abandoned file and
|
|
331
|
+
take over ownership rather than stalling forever. `rea status` is a
|
|
332
|
+
read-only consumer of this file.
|
|
333
|
+
|
|
334
|
+
### Downstream environment safety
|
|
335
|
+
|
|
336
|
+
`rea serve` does **not** forward `process.env` wholesale to downstream
|
|
337
|
+
children. Each child gets:
|
|
338
|
+
|
|
339
|
+
1. A fixed allowlist of neutral OS vars (`PATH`, `HOME`, `TZ`,
|
|
340
|
+
`NODE_OPTIONS`, …).
|
|
341
|
+
2. Any names opted into via `registry.yaml#servers[].env_passthrough` —
|
|
342
|
+
the schema refuses secret-looking names (`*_TOKEN`, `*_KEY`,
|
|
343
|
+
`*_SECRET`, …), so secrets must be named explicitly.
|
|
344
|
+
3. Values from the registry's `env:` mapping, which may contain
|
|
345
|
+
`${VAR}` placeholders resolved against the host environment
|
|
346
|
+
(0.3.0). Secret-looking values are redacted in logs by default.
|
|
347
|
+
A `${VAR}` whose host variable is unset is treated as fatal — the
|
|
348
|
+
downstream is marked unhealthy rather than handed an unresolved
|
|
349
|
+
placeholder.
|
|
213
350
|
|
|
214
|
-
|
|
215
|
-
Claude Code tool-invocation time, independently of the gateway. Both
|
|
216
|
-
layers fail closed. Bypassing one does not disable the other.
|
|
351
|
+
### Hook layer
|
|
217
352
|
|
|
218
|
-
|
|
219
|
-
|
|
353
|
+
Hooks are shell scripts. 14 ship in the package; 12 are wired into
|
|
354
|
+
the default `.claude/settings.json` and run at Claude Code
|
|
355
|
+
tool-invocation time, independently of the gateway. The remaining
|
|
356
|
+
two (`commit-review-gate.sh` and `push-review-gate-git.sh`) ship
|
|
357
|
+
ready-to-wire but are not registered by default — see "What REA is"
|
|
358
|
+
above and the inventory table at the end of this section for the full
|
|
359
|
+
picture. Both layers (hooks and the gateway middleware) fail closed.
|
|
360
|
+
Bypassing one does not disable the other.
|
|
361
|
+
|
|
362
|
+
Every hook uses `set -euo pipefail` (or `set -uo pipefail` for the
|
|
363
|
+
ones that process stdin JSON) and performs a HALT check near the top.
|
|
364
|
+
The review-gate hooks (`push-review-gate.sh`, `push-review-gate-git.sh`,
|
|
365
|
+
`commit-review-gate.sh`) additionally anchor `REA_ROOT` to their own
|
|
366
|
+
on-disk location (BUG-012 fix, 0.6.2) — for those hooks,
|
|
367
|
+
`CLAUDE_PROJECT_DIR` is accepted only as an advisory signal because it
|
|
368
|
+
is caller-controlled. The remaining hooks (e.g. `secret-scanner.sh`,
|
|
369
|
+
`settings-protection.sh`, `blocked-paths-enforcer.sh`,
|
|
370
|
+
`dangerous-bash-interceptor.sh`) still derive `REA_ROOT` from
|
|
371
|
+
`${CLAUDE_PROJECT_DIR:-$(pwd)}`; extending the script-anchor idiom to
|
|
372
|
+
those hooks is tracked as an open hardening item. Cross-repo
|
|
373
|
+
invocations (running a review-gate hook from a consumer project that
|
|
374
|
+
is not the rea install) short-circuit cleanly using
|
|
375
|
+
`git --git-common-dir` comparison (0.6.1).
|
|
376
|
+
|
|
377
|
+
The two push-review adapters that ship in `hooks/` share a single
|
|
378
|
+
implementation core at `hooks/_lib/push-review-core.sh` (0.7.0 BUG-008
|
|
379
|
+
cleanup) so a fix lands in one place: `push-review-gate.sh` consumes
|
|
380
|
+
Claude-Code PreToolUse JSON and is what `rea init` copies to
|
|
381
|
+
`.claude/hooks/`; `push-review-gate-git.sh` consumes git's native
|
|
382
|
+
`.husky/pre-push` refspec lines and is shipped for consumers who wire
|
|
383
|
+
a wrapper-based `.husky/pre-push` that execs it directly. The default
|
|
384
|
+
`rea init` installer does NOT currently emit that wrapper — it writes
|
|
385
|
+
a standalone inline gate body as `.husky/pre-push` (source of truth:
|
|
386
|
+
`src/cli/install/pre-push.ts`). The native-git adapter and the
|
|
387
|
+
inline installer currently implement the same protected-path logic
|
|
388
|
+
separately; unifying the husky installer on the adapter is tracked as
|
|
389
|
+
follow-up hardening. `commit-review-gate.sh` is a standalone Claude
|
|
390
|
+
`PreToolUse: Bash` hook that matches `git commit`; it does not source
|
|
391
|
+
the push-review core.
|
|
220
392
|
|
|
221
393
|
### Slash commands
|
|
222
394
|
|
|
@@ -228,9 +400,13 @@ during `rea init`.
|
|
|
228
400
|
Ten curated agents ship in the package: `rea-orchestrator`, `code-reviewer`,
|
|
229
401
|
`codex-adversarial`, `security-engineer`, `accessibility-engineer`,
|
|
230
402
|
`typescript-specialist`, `frontend-specialist`, `backend-engineer`,
|
|
231
|
-
`qa-engineer`, `technical-writer`.
|
|
232
|
-
(`client-engagement`, `bst-internal`, `
|
|
233
|
-
|
|
403
|
+
`qa-engineer`, `technical-writer`. Profiles
|
|
404
|
+
(`client-engagement`, `bst-internal`, `bst-internal-no-codex`,
|
|
405
|
+
`lit-wc`, `open-source`, `open-source-no-codex`, `minimal`) layer
|
|
406
|
+
additional specialists on top. The `-no-codex` variants match their
|
|
407
|
+
parents but default `review.codex_required: false` so teams without a
|
|
408
|
+
Codex CLI on the bench get a first-class opt-out rather than relying on
|
|
409
|
+
`REA_SKIP_CODEX_REVIEW`.
|
|
234
410
|
|
|
235
411
|
The orchestrator is the single entry point for non-trivial tasks. The
|
|
236
412
|
CLAUDE.md template installed by `rea init` instructs the host agent:
|
|
@@ -259,9 +435,38 @@ Three things make this work:
|
|
|
259
435
|
2. The **`/codex-review` slash command** is one of the five shipped
|
|
260
436
|
commands. It produces an audit entry including the request summary,
|
|
261
437
|
response summary, and pass/fail signal.
|
|
262
|
-
3. The **`push-review-gate.sh` hook**
|
|
263
|
-
|
|
264
|
-
|
|
438
|
+
3. The **`push-review-gate.sh` hook** blocks (exit 2) every protected-path
|
|
439
|
+
push that does not carry a matching `codex.review` audit entry for the
|
|
440
|
+
pushed `head_sha` with a `verdict` of `pass` or `concerns`. The only
|
|
441
|
+
other way through the protected-path branch is an active Codex-only
|
|
442
|
+
waiver (`REA_SKIP_CODEX_REVIEW=<reason>`, 0.8.0 narrowing). For
|
|
443
|
+
**non-protected-path** pushes the gate runs a separate review-cache
|
|
444
|
+
lookup — this is where the cache predicate and pushed-ref key
|
|
445
|
+
hardening live. The cache-hit predicate requires
|
|
446
|
+
`.hit == true and .result == "pass"` (0.8.0 hardening — a cached
|
|
447
|
+
`fail` verdict no longer satisfies the gate), and the cache key is
|
|
448
|
+
derived from the **pushed source ref** (from pre-push stdin) rather
|
|
449
|
+
than the checkout branch, so `git push origin hotfix:main` from a
|
|
450
|
+
`feature` checkout correctly looks up the `hotfix` cache entry.
|
|
451
|
+
|
|
452
|
+
### Codex-only waiver semantics (0.8.0)
|
|
453
|
+
|
|
454
|
+
Through 0.7.0, `REA_SKIP_CODEX_REVIEW=<reason>` short-circuited the
|
|
455
|
+
**entire** push-review gate — operators reached for it to silence a
|
|
456
|
+
transient Codex outage and accidentally bypassed HALT, the cross-repo
|
|
457
|
+
guard, and the general push-review gate. 0.8.0 narrows it to what the
|
|
458
|
+
name implies: the waiver satisfies **only** the protected-path Codex
|
|
459
|
+
audit requirement. HALT, cross-repo guard, ref-resolution failures, and
|
|
460
|
+
push-review-cache misses still block. The skip audit record is still
|
|
461
|
+
named `codex.review.skipped` and still fails the `codex.review` jq
|
|
462
|
+
predicate — skipping a review is not a review.
|
|
463
|
+
|
|
464
|
+
For the previous whole-gate bypass, use `REA_SKIP_PUSH_REVIEW=<reason>`
|
|
465
|
+
(unchanged, 0.5.0). It writes `push.review.skipped` with an
|
|
466
|
+
`os_identity` sub-object (uid, whoami, hostname, pid, ppid, tty, ci)
|
|
467
|
+
so auditors can distinguish a real operator from a forged git-config
|
|
468
|
+
actor, and refuses on CI runners unless the policy opts in via
|
|
469
|
+
`review.allow_skip_in_ci: true`.
|
|
265
470
|
|
|
266
471
|
Codex responses are treated as untrusted input. They flow through the
|
|
267
472
|
`redact` and `injection` middleware on return — same treatment as any
|
|
@@ -269,30 +474,32 @@ other downstream tool result. Codex never receives `.rea/policy.yaml`
|
|
|
269
474
|
content in its prompts; Codex reviews diffs, not policy.
|
|
270
475
|
|
|
271
476
|
If Codex is not installed, `rea doctor` warns with a one-line install
|
|
272
|
-
hint. REA does not require Codex to function
|
|
273
|
-
|
|
477
|
+
hint. REA does not require Codex to function — the `bst-internal-no-codex`
|
|
478
|
+
and `open-source-no-codex` profiles disable the requirement entirely,
|
|
479
|
+
and `ClaudeSelfReviewer` is the in-process fallback (tagged
|
|
480
|
+
`degraded: true` in the audit record so self-review is visible and
|
|
481
|
+
countable).
|
|
274
482
|
|
|
275
483
|
## Hooks
|
|
276
484
|
|
|
277
|
-
|
|
485
|
+
Fourteen hooks. Each does one thing.
|
|
278
486
|
|
|
279
487
|
| Hook | Event | One-line purpose |
|
|
280
488
|
| --- | --- | --- |
|
|
281
489
|
| `dangerous-bash-interceptor` | PreToolUse: Bash | Block categories of destructive shell commands |
|
|
282
490
|
| `env-file-protection` | PreToolUse: Bash | Block reads of `.env*` files |
|
|
283
|
-
| `dependency-audit-gate` | PreToolUse: Bash |
|
|
491
|
+
| `dependency-audit-gate` | PreToolUse: Bash | Verify packages exist on the registry before install |
|
|
284
492
|
| `commit-review-gate` | PreToolUse: Bash | Intercept `git commit`; require review on non-trivial diffs |
|
|
285
|
-
| `push-review-gate` | PreToolUse: Bash | Intercept `git push
|
|
286
|
-
| `
|
|
493
|
+
| `push-review-gate` | PreToolUse: Bash | Intercept `git push` (Claude-Code-JSON adapter); protected-path + Codex audit |
|
|
494
|
+
| `push-review-gate-git` | `.husky/pre-push` | Native git adapter around the same core |
|
|
495
|
+
| `attribution-advisory` | PreToolUse: Bash | Block commits / PRs containing AI attribution markers |
|
|
496
|
+
| `pr-issue-link-gate` | PreToolUse: Bash | Advisory warn when `gh pr create` has no linked issue |
|
|
497
|
+
| `security-disclosure-gate` | PreToolUse: Bash | Route security-keyword `gh issue create` to private disclosure |
|
|
287
498
|
| `secret-scanner` | PreToolUse: Write\|Edit | Scan file writes for credential patterns |
|
|
288
|
-
| `settings-protection` | PreToolUse: Write\|Edit | Block agent writes to `.claude/settings.json
|
|
499
|
+
| `settings-protection` | PreToolUse: Write\|Edit | Block agent writes to `.claude/settings.json`, hook dirs, policy |
|
|
289
500
|
| `blocked-paths-enforcer` | PreToolUse: Write\|Edit | Enforce `blocked_paths` from policy |
|
|
290
|
-
| `changeset-security-gate` | PreToolUse: Write\|Edit |
|
|
291
|
-
| `architecture-review-gate` | PostToolUse: Write\|Edit | Flag edits crossing architectural boundaries |
|
|
292
|
-
|
|
293
|
-
A twelfth hook, `security-disclosure-gate`, intercepts `gh issue create`
|
|
294
|
-
commands containing security-sensitive keywords and redirects to private
|
|
295
|
-
disclosure. It is installed as part of the Bash PreToolUse set.
|
|
501
|
+
| `changeset-security-gate` | PreToolUse: Write\|Edit | Guard changesets against GHSA leaks and malformed frontmatter |
|
|
502
|
+
| `architecture-review-gate` | PostToolUse: Write\|Edit | Flag edits crossing architectural boundaries (advisory) |
|
|
296
503
|
|
|
297
504
|
## Slash commands
|
|
298
505
|
|
|
@@ -311,7 +518,7 @@ rejected, not ignored.
|
|
|
311
518
|
|
|
312
519
|
| Field | Type | Purpose |
|
|
313
520
|
| --- | --- | --- |
|
|
314
|
-
| `version` | string, `"1"` | Schema version; only `"1"` accepted in
|
|
521
|
+
| `version` | string, `"1"` | Schema version; only `"1"` accepted in the current major |
|
|
315
522
|
| `profile` | string | Profile name from `profiles/` (e.g. `bst-internal`) |
|
|
316
523
|
| `autonomy_level` | `L0`\|`L1`\|`L2`\|`L3` | Current autonomy. `L0` = read-only; `L3` = full tool access |
|
|
317
524
|
| `max_autonomy_level` | `L0`\|`L1`\|`L2`\|`L3` | Hard ceiling. `autonomy_level` cannot exceed this |
|
|
@@ -321,6 +528,13 @@ rejected, not ignored.
|
|
|
321
528
|
| `context_protection.delegate_to_subagent` | string[] | Commands that must run in a subagent context to preserve the parent's context window |
|
|
322
529
|
| `context_protection.max_bash_output_lines` | number | Truncate long bash output at this line count |
|
|
323
530
|
| `notification_channel` | string | Optional Discord webhook URL. Empty string = no notifications |
|
|
531
|
+
| `review.codex_required` | boolean | When `false`, protected-path pushes don't require a Codex audit (first-class no-Codex mode). Default `true` |
|
|
532
|
+
| `review.cache_max_age_seconds` | number | TTL for entries in `.rea/review-cache.jsonl`. Default 3600 |
|
|
533
|
+
| `review.allow_skip_in_ci` | boolean | When `true`, `REA_SKIP_PUSH_REVIEW` is accepted on CI runners. Default `false` |
|
|
534
|
+
| `injection.suspicious_blocks_writes` | boolean | `bst-internal` posture — `suspicious` verdict on a write/destructive tool denies instead of warning. Default `false` |
|
|
535
|
+
| `redact.patterns[]` | string[] | User-supplied secret patterns; vetted via `safe-regex` at load |
|
|
536
|
+
| `redact.match_timeout_ms` | number | Per-call regex budget. Default 100 |
|
|
537
|
+
| `gateway.health.expose_diagnostics` | boolean | When `true`, `__rea__health` emits redacted+classified diagnostic strings on the wire. Default `false` (null) |
|
|
324
538
|
|
|
325
539
|
`autonomy_level > max_autonomy_level` is rejected at parse time. Setting
|
|
326
540
|
`promotion_requires_human_approval: false` requires the CLI flag
|
|
@@ -345,8 +559,11 @@ npx @bookedsolid/rea init --from-reagent
|
|
|
345
559
|
- Leaves `.reagent/` in place; you delete it manually after verifying
|
|
346
560
|
`rea doctor` passes and a dogfood run completes.
|
|
347
561
|
|
|
348
|
-
|
|
349
|
-
|
|
562
|
+
See [MIGRATION-0.5.0.md](./MIGRATION-0.5.0.md) for the BUG-008 / BUG-009
|
|
563
|
+
/ BUG-010 coordinated fix window. Between 0.5.0 and 0.9.0, the breaking
|
|
564
|
+
semantic change worth calling out is 0.8.0's narrowing of
|
|
565
|
+
`REA_SKIP_CODEX_REVIEW` to a Codex-only waiver — see the CHANGELOG
|
|
566
|
+
entry for the migration steps.
|
|
350
567
|
|
|
351
568
|
## Security
|
|
352
569
|
|
package/SECURITY.md
CHANGED
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Supported Versions
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
|
5
|
+
Security fixes land on the latest minor line. Older minors receive fixes only
|
|
6
|
+
when the issue is critical and a backport is tractable.
|
|
7
|
+
|
|
8
|
+
| Version | Supported |
|
|
9
|
+
| ------- | ------------------------------------------- |
|
|
10
|
+
| 0.9.x | Yes — active line |
|
|
11
|
+
| 0.8.x | Critical fixes only, 30 days from 0.9.0 |
|
|
12
|
+
| 0.7.x | No — superseded; upgrade recommended |
|
|
13
|
+
| ≤ 0.6.x | No — superseded; upgrade recommended |
|
|
14
|
+
| < 0.1 | No (pre-release) |
|
|
9
15
|
|
|
10
16
|
## Reporting a Vulnerability
|
|
11
17
|
|
|
@@ -85,10 +91,21 @@ REA's security model is defense-in-depth across two independent layers:
|
|
|
85
91
|
|
|
86
92
|
**Hook layer** (development-time, Claude Code hooks):
|
|
87
93
|
|
|
88
|
-
-
|
|
89
|
-
|
|
94
|
+
- 14 shell scripts ship in the hook layer. 12 are wired into Claude Code's
|
|
95
|
+
`PreToolUse` / `PostToolUse` events via the default `.claude/settings.json`.
|
|
96
|
+
Two are shipped but NOT registered by default: `commit-review-gate.sh`
|
|
97
|
+
is a `PreToolUse: Bash` hook that matches `git commit` for operators who
|
|
98
|
+
opt into commit-time review by adding a rule, and `push-review-gate-git.sh`
|
|
99
|
+
is a native-git adapter that sources `hooks/_lib/push-review-core.sh`
|
|
100
|
+
(the same shared core used by the Claude-Code push-review adapter),
|
|
101
|
+
shipped for consumers who wire a wrapper-based `.husky/pre-push` that
|
|
102
|
+
execs it directly. `rea init`'s default installer emits a standalone
|
|
103
|
+
inline `.husky/pre-push` body rather than a wrapper; unifying the
|
|
104
|
+
husky installer on the adapter is tracked as a follow-up
|
|
105
|
+
- `security-disclosure-gate` routes public security-keyword issue creation to private disclosure
|
|
90
106
|
- `settings-protection` prevents agents from modifying their own safety rails
|
|
91
107
|
- `dangerous-bash-interceptor` blocks categories of destructive shell commands
|
|
108
|
+
- `push-review-gate` and the shared-core adapter (`push-review-gate-git.sh` sourcing `hooks/_lib/push-review-core.sh`) anchor trust on the hook's own on-disk location via `BASH_SOURCE` rather than caller-controlled env vars; see `THREAT_MODEL.md §5.18`. The shipped inline `.husky/pre-push` body uses `git rev-parse --show-toplevel` to locate `REA_ROOT` — extending the script-anchor idiom to the inline path is tracked follow-up hardening
|
|
92
109
|
|
|
93
110
|
Both layers operate independently — compromising one does not disable the other.
|
|
94
111
|
|
|
@@ -99,6 +116,6 @@ Both layers operate independently — compromising one does not disable the othe
|
|
|
99
116
|
- Policy parsing is strict zod schema — unknown fields rejected, not ignored
|
|
100
117
|
- Path traversal protection on profile loading (regex + path containment check)
|
|
101
118
|
- CI publish pipeline includes gitleaks secret scanning, npm provenance attestation via OIDC, SBOM generation, and payload validation
|
|
102
|
-
- All shell hooks
|
|
119
|
+
- All shell hooks set fail-fast flags with explicit variable quoting (`set -euo pipefail`, or `set -uo pipefail` for hooks that consume stdin JSON where a single `jq`-path miss must not abort before the conditional branches run)
|
|
103
120
|
- Commits are signed and DCO-signed-off; `main` branch protection requires passing checks and review
|
|
104
121
|
- npm publish uses OIDC provenance; no long-lived NPM tokens in CI
|