@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 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
- [![npm version](https://img.shields.io/badge/npm-pending-lightgrey)](https://www.npmjs.com/package/@bookedsolid/rea)
6
- [![CI](https://img.shields.io/badge/ci-pending-lightgrey)](https://github.com/bookedsolidtech/rea/actions)
7
- [![provenance](https://img.shields.io/badge/npm%20provenance-pending-lightgrey)](https://docs.npmjs.com/generating-provenance-statements)
5
+ [![npm version](https://img.shields.io/npm/v/%40bookedsolid%2Frea?color=cb3837&label=npm)](https://www.npmjs.com/package/@bookedsolid/rea)
6
+ [![CI](https://github.com/bookedsolidtech/rea/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/bookedsolidtech/rea/actions/workflows/ci.yml)
7
+ [![npm provenance](https://img.shields.io/badge/npm%20provenance-attested-blue?logo=npm)](https://docs.npmjs.com/generating-provenance-statements)
8
8
  [![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
9
9
  [![DCO](https://img.shields.io/badge/DCO-required-green)](https://developercertificate.org/)
10
10
  [![Node](https://img.shields.io/badge/node-%3E%3D22-brightgreen)](https://nodejs.org/)
11
11
 
12
- > Status: 0.0.x, pre-release. Badges are placeholders until the first publish.
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** — 11 shell scripts wired into Claude Code's `PreToolUse`
35
- and `PostToolUse` events. Hooks enforce secret scanning, dangerous-command
36
- interception, blocked-path protection, settings protection, attribution
37
- rejection, and commit/push review gates.
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
- 3. A **policy runtime** `.rea/policy.yaml` with strict zod-validated
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 optional
45
- Discord notification webhook.
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 hook coverage, policy parse, husky commit-msg hook
134
- install, `.mcp.json` gateway wiring, Codex plugin availability, and the
135
- integrity of the audit hash chain. It returns a pass/fail summary with
136
- specific remediation hints.
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), and audit stats (lines,
148
- last timestamp, whether the tail record's hash looks well-formed). Use
149
- `rea check` when you want the pure on-disk view without probing for a
150
- live process.
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
- ### Hook layer
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
- Hooks are shell scripts wired into `.claude/settings.json`. They run at
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
- Every hook sources `hooks/_lib/halt-check.sh` and `hooks/_lib/policy-read.sh`
219
- at the top of the script. Every hook uses `set -euo pipefail`.
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`. Four profiles
232
- (`client-engagement`, `bst-internal`, `lit-wc`, `open-source`) layer
233
- additional specialists on top.
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** checks for a recent `/codex-review`
263
- audit entry on the current branch and warns (does not block) if none
264
- is present.
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, but the default workflow
273
- assumes it.
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
- Eleven hooks, down from reagent's 26. Each does one thing.
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 | Run `npm audit`; block on high/critical |
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`; warn if no recent `/codex-review` |
286
- | `attribution-advisory` | PreToolUse: Bash | Block commits containing AI attribution markers |
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 | Require changeset entry on security-relevant changes |
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 0.1.x |
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
- Reagent will be deprecated via `npm deprecate` within seven days of
349
- REA 0.1.0. The deprecation notice points users here.
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
- | Version | Supported |
6
- | ------- | --------- |
7
- | 0.1.x | Yes |
8
- | < 0.1 | No (pre-release) |
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
- - 11 Claude Code hooks enforce security at the point of tool invocation
89
- - `security-disclosure-gate` blocks public issue creation for security topics
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 use `set -euo pipefail` with explicit variable quoting
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