@bookedsolid/rea 0.47.0 → 0.48.0
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/MIGRATING.md +88 -0
- package/THREAT_MODEL.md +140 -0
- package/dist/policy/loader.d.ts +13 -0
- package/dist/policy/loader.js +36 -0
- package/dist/policy/types.d.ts +52 -0
- package/hooks/_lib/shim-cache.sh +650 -0
- package/hooks/_lib/shim-runtime.sh +293 -3
- package/package.json +1 -1
- package/scripts/profile-hooks.mjs +10 -1
- package/templates/_lib_shim-cache.dogfood-staged.sh +650 -0
- package/templates/_lib_shim-runtime.dogfood-staged.sh +293 -3
package/MIGRATING.md
CHANGED
|
@@ -662,3 +662,91 @@ verdict that triggered it is.
|
|
|
662
662
|
document the ambivalence.
|
|
663
663
|
- **My pre-commit hook breaks on push** → not rea (rea ships no
|
|
664
664
|
pre-commit). Fix in your repo.
|
|
665
|
+
|
|
666
|
+
## Shim session-cache (added in 0.48.0)
|
|
667
|
+
|
|
668
|
+
Every Node-binary shim (`.claude/hooks/*.sh`) does three steps that
|
|
669
|
+
do not change across same-session same-CLI fires:
|
|
670
|
+
|
|
671
|
+
1. Resolve the rea CLI through the fixed 2-tier sandboxed order
|
|
672
|
+
(`node_modules/@bookedsolid/rea/dist/cli/index.js`, then
|
|
673
|
+
`./dist/cli/index.js`).
|
|
674
|
+
2. Sandbox-check the resolved CLI (realpath inside project + ancestor
|
|
675
|
+
`package.json` with `name: @bookedsolid/rea` + optional
|
|
676
|
+
`dist/cli/index.js` shape enforcement).
|
|
677
|
+
3. Version-probe via `rea hook <NAME> --help` to confirm the
|
|
678
|
+
subcommand exists in the resolved CLI.
|
|
679
|
+
|
|
680
|
+
The 0.48.0 per-session cache (`hooks/_lib/shim-cache.sh`) records
|
|
681
|
+
the answers under a key composed of `(session_token, project_realpath,
|
|
682
|
+
cli_realpath, cli_mtime, cli_size_bytes, euid, enforce_cli_shape,
|
|
683
|
+
shim_name, pkg_mtime, pkg_size_bytes, dist_dir_mtime, node_realpath,
|
|
684
|
+
node_mtime)`. Subsequent
|
|
685
|
+
fires that match the same key skip both the sandbox check (step 5
|
|
686
|
+
in `shim_run`) and the version probe (step 8). Cache hit latency is
|
|
687
|
+
roughly the cache-read cost (~5-10ms) instead of the full hot path
|
|
688
|
+
(~80-150ms on macOS).
|
|
689
|
+
|
|
690
|
+
### Disable switches
|
|
691
|
+
|
|
692
|
+
- `REA_SHIM_CACHE=0` in env disables both reads and writes for the
|
|
693
|
+
current process. `pnpm perf:hooks` sets this automatically so
|
|
694
|
+
baseline measurements reflect the uncached path.
|
|
695
|
+
- `policy.shim_cache.enabled: false` disables the cache via
|
|
696
|
+
`.rea/policy.yaml`:
|
|
697
|
+
```yaml
|
|
698
|
+
shim_cache:
|
|
699
|
+
enabled: false
|
|
700
|
+
```
|
|
701
|
+
Default is `enabled: true`. The block is optional — a vanilla
|
|
702
|
+
install with no `shim_cache:` block gets the cache on.
|
|
703
|
+
|
|
704
|
+
### Where the cache lives
|
|
705
|
+
|
|
706
|
+
`$TMPDIR/rea-shim-cache.<euid>/<key>.json` — per-user `0700`
|
|
707
|
+
directory, per-entry `0600` file. Entries are atomically written
|
|
708
|
+
via `mv` from `.tmp.$$` (no half-written reads). On reboot the
|
|
709
|
+
tmpfs is wiped — there is no cross-session persistence by design.
|
|
710
|
+
|
|
711
|
+
### Invalidation
|
|
712
|
+
|
|
713
|
+
Cache entries are invalidated automatically when any of the key
|
|
714
|
+
fields change. The most common triggers:
|
|
715
|
+
|
|
716
|
+
- `pnpm install` / `npm install` updates the CLI mtime + size →
|
|
717
|
+
cache key differs → next fire takes the full path and writes a
|
|
718
|
+
fresh entry.
|
|
719
|
+
- Hard 3600s (1h) TTL ceiling enforced inside `shim_run` even when
|
|
720
|
+
mtime + size match — bounds staleness on long-lived sessions.
|
|
721
|
+
- Schema version bump (future `v2`) → every `v1` entry becomes a
|
|
722
|
+
cache-miss (no migration).
|
|
723
|
+
- Cross-user read attempts refused via owner check.
|
|
724
|
+
|
|
725
|
+
### Forcing a cache rebuild
|
|
726
|
+
|
|
727
|
+
Use the env var or wipe the directory:
|
|
728
|
+
|
|
729
|
+
```bash
|
|
730
|
+
# One invocation, uncached
|
|
731
|
+
REA_SHIM_CACHE=0 git commit -m "..."
|
|
732
|
+
|
|
733
|
+
# Wipe the entire cache for the current user
|
|
734
|
+
rm -rf "$TMPDIR/rea-shim-cache.$(id -u)"
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
There is intentionally no `rea cache clear` CLI in 0.48.0 — the
|
|
738
|
+
env-var and rm patterns handle the realistic cases without adding
|
|
739
|
+
surface area.
|
|
740
|
+
|
|
741
|
+
### Why this matters
|
|
742
|
+
|
|
743
|
+
Before 0.48.0 every shim fire paid the full sandbox + probe cost on
|
|
744
|
+
every Bash/Edit/Write/MultiEdit/NotebookEdit tool call. With 8+
|
|
745
|
+
hooks firing per Bash event, the cumulative latency was felt by the
|
|
746
|
+
operator on every single command. The cache brings warm-session
|
|
747
|
+
hot-path latency down to roughly the cache-read overhead, which is
|
|
748
|
+
the dominant cost ceiling that motivated 0.45.0's profiling work.
|
|
749
|
+
|
|
750
|
+
See `docs/shim-session-cache-design.md` for the security contract
|
|
751
|
+
and `docs/hook-perf-baseline.md` §"Per-session shim cache and the
|
|
752
|
+
baseline" for the perf methodology note.
|
package/THREAT_MODEL.md
CHANGED
|
@@ -1277,3 +1277,143 @@ The bash gate explicitly does NOT defend against:
|
|
|
1277
1277
|
to an attacker binary on PATH, the gate is defeated. Production
|
|
1278
1278
|
deployments pin PATH via the harness; `rea doctor` verifies PATH
|
|
1279
1279
|
integrity at install time but does not enforce it at runtime.
|
|
1280
|
+
|
|
1281
|
+
---
|
|
1282
|
+
|
|
1283
|
+
## 9. Per-session shim cache (0.48.0+)
|
|
1284
|
+
|
|
1285
|
+
The `hooks/_lib/shim-cache.sh` helper sits between the relevance
|
|
1286
|
+
pre-gate and the sandbox check inside `hooks/_lib/shim-runtime.sh`.
|
|
1287
|
+
On a cache HIT it short-circuits steps 5 (sandbox check) and 8
|
|
1288
|
+
(version probe) — those answers were validated when the entry was
|
|
1289
|
+
written, the cache key invalidates if any field changes, and the
|
|
1290
|
+
entry has a 3600-second TTL. On a cache MISS, corrupt file, or any
|
|
1291
|
+
error condition, the runtime falls through to the existing uncached
|
|
1292
|
+
hot path. The cache is an **optimization, not a security boundary**.
|
|
1293
|
+
|
|
1294
|
+
### 9.1 Cache key construction
|
|
1295
|
+
|
|
1296
|
+
The cache key is `sha256(NUL-joined-tuple)[0:32]` of:
|
|
1297
|
+
|
|
1298
|
+
1. `schema_version` (`"v1"`)
|
|
1299
|
+
2. `session_token` (claude-ancestor PID+start-time hash, or
|
|
1300
|
+
tty/login-shell/boot-id fallback, or "cache disabled" if
|
|
1301
|
+
neither is derivable)
|
|
1302
|
+
3. `project_root_realpath` (post-symlink-resolution)
|
|
1303
|
+
4. `cli_realpath` (post-symlink-resolution)
|
|
1304
|
+
5. `cli_mtime` (nanosecond precision uniformly across macOS and
|
|
1305
|
+
Linux — see §9.4 for the portability rationale; closes the
|
|
1306
|
+
same-second-same-size rebuild class flagged by codex round-2)
|
|
1307
|
+
6. `cli_size_bytes` (defeats `touch -r` mtime-preserving swap)
|
|
1308
|
+
7. `euid` (refuses cross-user reuse)
|
|
1309
|
+
8. `SHIM_ENFORCE_CLI_SHAPE` (whether the `dist/cli/index.js`
|
|
1310
|
+
shape was required for this fire)
|
|
1311
|
+
9. `SHIM_NAME` (0.48.0 codex round-1 P1 — hook-scoped key prevents
|
|
1312
|
+
a cache-warm shim letting a sibling skip its own probe)
|
|
1313
|
+
10. `pkg_mtime` + `pkg_size` of the ancestor `package.json` the
|
|
1314
|
+
sandbox check found (codex round-3 P2 — same-session edit /
|
|
1315
|
+
rename of that file invalidates the entry)
|
|
1316
|
+
11. `dist_mtime` of `dist/cli/` directory (codex round-3 P1 — a
|
|
1317
|
+
same-session rebuild that adds/removes ANY file in that
|
|
1318
|
+
directory invalidates the entry, the dominant signal for the
|
|
1319
|
+
rea-dev workflow where `tsc` rewrites many siblings of
|
|
1320
|
+
`index.js`)
|
|
1321
|
+
12. `node_realpath` + `node_mtime` (codex round-4 P1 — a
|
|
1322
|
+
same-session `nvm use` / `volta pin` / PATH-prepended wrapper
|
|
1323
|
+
that swaps the node interpreter invalidates the entry; warm
|
|
1324
|
+
hits otherwise would skip both node-availability AND the
|
|
1325
|
+
version probe and forward through a different interpreter
|
|
1326
|
+
whose JS engine version may not match what the probe
|
|
1327
|
+
validated)
|
|
1328
|
+
|
|
1329
|
+
### 9.2 Storage discipline
|
|
1330
|
+
|
|
1331
|
+
- Per-user directory at `$TMPDIR/rea-shim-cache.<euid>/`, created
|
|
1332
|
+
mode `0700` via `umask 077`. Reads refuse if mode is wider or
|
|
1333
|
+
owner != euid.
|
|
1334
|
+
- Per-entry file at `<dir>/<key>.json`, written mode `0600` via
|
|
1335
|
+
atomic `mv` from `.tmp.$$`. Reads refuse if mode is wider or
|
|
1336
|
+
owner != euid.
|
|
1337
|
+
- JSON entry shape includes `schema_version`, `cli_realpath`,
|
|
1338
|
+
`cli_mtime`, `cli_size_bytes`, `pkg_mtime`, `pkg_size_bytes`,
|
|
1339
|
+
`dist_mtime`, `node_realpath`, `node_mtime`, `sandbox_ok`,
|
|
1340
|
+
`shape_ok`, `cached_at_unix`, `ttl_seconds`. Any unknown / missing
|
|
1341
|
+
required
|
|
1342
|
+
field → ignore entry. Schema version bump → all v(n-1) entries
|
|
1343
|
+
become unreadable cache-misses (no migration).
|
|
1344
|
+
|
|
1345
|
+
### 9.3 Attack enumeration
|
|
1346
|
+
|
|
1347
|
+
| # | Attack | Defense |
|
|
1348
|
+
|---|--------|---------|
|
|
1349
|
+
| 1 | Symlink swap of cached CLI between cache-write and cache-read | `cli_realpath` recomputed at every read and compared to entry; any drift → miss |
|
|
1350
|
+
| 2 | Two concurrent projects sharing `$TMPDIR` | `project_root_realpath` in key + per-user `0700` dir; cross-project read produces different key |
|
|
1351
|
+
| 3 | Cache file race on parallel hook fires | One file per key; identical inputs produce identical content; atomic `mv` from `.tmp.$$` ensures no partial reads |
|
|
1352
|
+
| 4 | Cross-user TOCTOU (other local user plants entry) | `0700` dir + `0600` file + owner check refuse foreign-owned reads |
|
|
1353
|
+
| 5 | mtime-preserving binary swap (attacker `touch -r`s after replacing CLI) | `cli_size_bytes` in key; size change forces miss even with preserved mtime |
|
|
1354
|
+
| 6 | Cached `sandbox_ok=true` after consumer moves project | `project_root_realpath` in key; CLAUDE_PROJECT_DIR drift → different key |
|
|
1355
|
+
| 7 | Schema rollback (older shim reads newer entry) | `schema_version` in key; cross-version reads miss cleanly |
|
|
1356
|
+
| 8 | Stale cache during a 0.x → 0.y upgrade mid-session | `cli_version` field + `cli_mtime` (ns precision) + `cli_size_bytes` — three independent invalidators |
|
|
1357
|
+
| 8b | Rebuild within the same wall-clock second + same byte length | `cli_mtime` recorded at nanosecond precision (codex round-2 P2) — APFS / ext4 / xfs all store ns mtime; a same-second rebuild moves the ns suffix so the key differs |
|
|
1358
|
+
| 9 | Long-lived session accumulating staleness past acceptable bound | Hard 3600s TTL enforced inside `shim-runtime.sh` even if mtime+size match |
|
|
1359
|
+
| 10 | Session-token spoofing on stripped containers (no `/proc`, no `ps -o lstart=`) | Final fallback is **cache disabled** (return 1), NOT "use PPID alone" — the contract is never silently weakened |
|
|
1360
|
+
| 11 | Same-session rebuild of CLI's hook surface that does not touch `dist/cli/index.js` itself (codex round-3 P1) | `dist_mtime` of the parent dir in the key — any add/remove in `dist/cli/` shifts the dir mtime and invalidates every entry under that CLI |
|
|
1361
|
+
| 12 | Same-session edit of the ancestor `package.json` that the sandbox check trusted (codex round-3 P2) | `pkg_mtime` + `pkg_size_bytes` in the key — any change forces a fresh sandbox + probe pass |
|
|
1362
|
+
| 13 | Same-session `node` interpreter swap (nvm use / volta pin / PATH-prepended wrapper) (codex round-4 P1 + round-7 P2) | `node_realpath` is derived from `process.execPath` (node's own path to itself), NOT from resolving the PATH-visible `node`. Stable version-manager shims like `~/.volta/bin/node` resolve to themselves; only `execPath` reveals which concrete Node binary the shim launched (e.g. `~/.volta/tools/image/node/22.x.x/bin/node`). Swapping versions changes execPath → different key |
|
|
1363
|
+
| 14 | Same-session rebuild that rewrites any module in `dist/**/*.js` without changing dist/cli/ — including transitively imported files like `dist/hooks/**`, `dist/policy/**`, `dist/audit/**` (codex round-5 P1 + round-9 P1) | `dist_mtime` is a sha256 of every `*.js` file's `ns-mtime / size / name` across the WHOLE dist tree (not just dist/cli/), built via `find -exec stat +` for ~15ms total cost. Any in-place rewrite of any imported module invalidates the entry. Falls back to single-dir mtime if `find`/`stat`/`shasum`/`sha256sum` are all unavailable. Hasher detection: `shasum -a 256` on macOS, `sha256sum` on GNU coreutils Linux (codex round-6 P2) |
|
|
1364
|
+
| 15 | Cache silently disabled on non-interactive subprocess launches (CI, vitest spawn, editor wrappers) where no claude/claude-code ancestor exists AND stdin is piped (codex round-6 P1) | Intermediate fallback: `(PPID basename + PPID start_time + boot_id)`. NOT "PPID alone" — start-time defeats PID reuse across reboots, boot-id confines to the current boot. Scopes the cache to "this specific parent process invocation, on this boot". A different parent / a reboot → different token |
|
|
1365
|
+
| 16 | Cache silently disabled on sandboxed macOS / locked-down CI where `/proc` is absent AND `ps`/`sysctl` are denied (codex round-8 P1) | Final fallback before disabled: `(euid + REA_ROOT)`. Coarser session scope than the process-anchored paths above, but cache poisoning still prevented by the per-user 0700 dir + 0600 file (foreign-owned reads refused) and cross-install reuse still prevented by REA_ROOT in the token PLUS every other key field (project, CLI mtime, dist hash, node execPath). The trade-off honors the spirit of design memo concern #2 (NEVER PPID alone) while letting the cache function in environments where the design's primary anchors are unavailable. Truly hostile environments (no euid AND no REA_ROOT) still fall through to "cache disabled" |
|
|
1366
|
+
|
|
1367
|
+
### 9.4 Portability — mtime precision
|
|
1368
|
+
|
|
1369
|
+
macOS supports fractional-second mtime via `stat -f %Fm`; GNU
|
|
1370
|
+
coreutils supports the same via `stat -c %.Y`. Both produce the
|
|
1371
|
+
same `1779052861.082677123` string shape — string-equal across
|
|
1372
|
+
platforms for the same physical mtime. **Both are used** at
|
|
1373
|
+
nanosecond precision (codex round-2 P2 closure). The design memo
|
|
1374
|
+
concern #1 was conditional ("if you can't get ns on one platform,
|
|
1375
|
+
downgrade both") — since both CAN, we use ns and close the
|
|
1376
|
+
same-second-same-size rebuild collision class.
|
|
1377
|
+
|
|
1378
|
+
`cli_size_bytes` remains in the key as defense-in-depth for
|
|
1379
|
+
filesystems that truncate nanosecond mtime to seconds (some
|
|
1380
|
+
FAT/NTFS mounts). On those filesystems the same-second-same-size
|
|
1381
|
+
rebuild would still hit the cache for up to 3600s — the TTL is the
|
|
1382
|
+
backstop, plus `pnpm install` typically changes the file size.
|
|
1383
|
+
The realistic exposure is "consumer runs `npm run build` twice
|
|
1384
|
+
within the same wall-clock second on a FAT volume" which is a
|
|
1385
|
+
corner case for a dev tool.
|
|
1386
|
+
|
|
1387
|
+
### 9.5 Disable switches
|
|
1388
|
+
|
|
1389
|
+
- `REA_SHIM_CACHE=0` in env disables both reads and writes. Used
|
|
1390
|
+
by `pnpm perf:hooks` so steady-state measurements reflect the
|
|
1391
|
+
uncached hot path (a warmed cache would silently mask
|
|
1392
|
+
regressions in the underlying resolve / sandbox / probe layers).
|
|
1393
|
+
- `policy.shim_cache.enabled: false` disables the cache via the
|
|
1394
|
+
policy file. The bash-tier helper consults this via a narrow
|
|
1395
|
+
inline YAML grep (the cache runs BEFORE the canonical 4-tier
|
|
1396
|
+
policy reader is available). The zod schema in
|
|
1397
|
+
`src/policy/loader.ts` validates the field at CLI load time so
|
|
1398
|
+
typos / wrong types are caught at the load boundary.
|
|
1399
|
+
|
|
1400
|
+
### 9.6 Out of scope
|
|
1401
|
+
|
|
1402
|
+
The cache explicitly does NOT defend against:
|
|
1403
|
+
|
|
1404
|
+
- **A poisoned cache entry that happens to collide with a
|
|
1405
|
+
legitimate key.** The 32-hex-char (128-bit) key space and the
|
|
1406
|
+
fail-safe validate-on-read pass (mtime/size/realpath rechecked
|
|
1407
|
+
against disk, JSON shape verified, TTL enforced) make this
|
|
1408
|
+
economically infeasible without already having euid + tmpdir
|
|
1409
|
+
write access — which is a higher privilege than the gate
|
|
1410
|
+
protects against to begin with.
|
|
1411
|
+
- **An attacker with the same euid who has already compromised
|
|
1412
|
+
the shim runtime.** At that point the cache layer is moot —
|
|
1413
|
+
every code path is attacker-controlled.
|
|
1414
|
+
- **A long-running session where the operator wants to force a
|
|
1415
|
+
cache rebuild without the TTL expiring.** Set `REA_SHIM_CACHE=0`
|
|
1416
|
+
in the env, or `rm -rf $TMPDIR/rea-shim-cache.$(id -u)/`. There
|
|
1417
|
+
is intentionally no `rea cache clear` CLI surface in 0.48.0 —
|
|
1418
|
+
the disable switch + on-reboot tmpfs wipe handle the realistic
|
|
1419
|
+
cases.
|
package/dist/policy/loader.d.ts
CHANGED
|
@@ -301,6 +301,13 @@ declare const PolicySchema: z.ZodObject<{
|
|
|
301
301
|
threshold?: number | undefined;
|
|
302
302
|
exempt_subagents?: string[] | undefined;
|
|
303
303
|
}>>;
|
|
304
|
+
shim_cache: z.ZodOptional<z.ZodObject<{
|
|
305
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
306
|
+
}, "strict", z.ZodTypeAny, {
|
|
307
|
+
enabled: boolean;
|
|
308
|
+
}, {
|
|
309
|
+
enabled?: boolean | undefined;
|
|
310
|
+
}>>;
|
|
304
311
|
}, "strict", z.ZodTypeAny, {
|
|
305
312
|
version: string;
|
|
306
313
|
profile: string;
|
|
@@ -379,6 +386,9 @@ declare const PolicySchema: z.ZodObject<{
|
|
|
379
386
|
threshold: number;
|
|
380
387
|
exempt_subagents: string[];
|
|
381
388
|
} | undefined;
|
|
389
|
+
shim_cache?: {
|
|
390
|
+
enabled: boolean;
|
|
391
|
+
} | undefined;
|
|
382
392
|
}, {
|
|
383
393
|
version: string;
|
|
384
394
|
profile: string;
|
|
@@ -457,6 +467,9 @@ declare const PolicySchema: z.ZodObject<{
|
|
|
457
467
|
threshold?: number | undefined;
|
|
458
468
|
exempt_subagents?: string[] | undefined;
|
|
459
469
|
} | undefined;
|
|
470
|
+
shim_cache?: {
|
|
471
|
+
enabled?: boolean | undefined;
|
|
472
|
+
} | undefined;
|
|
460
473
|
}>;
|
|
461
474
|
/**
|
|
462
475
|
* Async policy loader with TTL cache and mtime-based invalidation.
|
package/dist/policy/loader.js
CHANGED
|
@@ -328,6 +328,32 @@ const DelegationAdvisoryPolicySchema = z
|
|
|
328
328
|
]),
|
|
329
329
|
})
|
|
330
330
|
.strict();
|
|
331
|
+
/**
|
|
332
|
+
* 0.48.0 — per-session shim cache policy. The
|
|
333
|
+
* `hooks/_lib/shim-cache.sh` helper, sourced by every Node-binary
|
|
334
|
+
* shim via `hooks/_lib/shim-runtime.sh`, caches the (sandbox-ok,
|
|
335
|
+
* shape-ok) tuple for a given (session, project, CLI realpath,
|
|
336
|
+
* mtime, size, euid, enforce_shape) key, with a 3600s TTL ceiling.
|
|
337
|
+
* The cache is an OPTIMIZATION — every cache-miss path falls through
|
|
338
|
+
* to the existing uncached hot path. See
|
|
339
|
+
* `docs/shim-session-cache-design.md` for the full contract.
|
|
340
|
+
*
|
|
341
|
+
* Strict mode rejects unknown keys so a typo (`enabld`, `enable`)
|
|
342
|
+
* fails loudly at policy load. The block is optional — vanilla
|
|
343
|
+
* installs with no `shim_cache:` block get the default behavior
|
|
344
|
+
* (cache enabled). To disable: `shim_cache: { enabled: false }`.
|
|
345
|
+
*
|
|
346
|
+
* The bash-tier helper does a narrow YAML grep for the field
|
|
347
|
+
* BEFORE the canonical 4-tier policy reader is available (cache
|
|
348
|
+
* runs in the shim's pre-CLI section). This zod schema validates
|
|
349
|
+
* the field at CLI load time so wrong types / typos are caught at
|
|
350
|
+
* the load boundary.
|
|
351
|
+
*/
|
|
352
|
+
const ShimCachePolicySchema = z
|
|
353
|
+
.object({
|
|
354
|
+
enabled: z.boolean().default(true),
|
|
355
|
+
})
|
|
356
|
+
.strict();
|
|
331
357
|
const PolicySchema = z
|
|
332
358
|
.object({
|
|
333
359
|
version: z.string(),
|
|
@@ -387,6 +413,16 @@ const PolicySchema = z
|
|
|
387
413
|
// when unset/false). When the block IS present the inner schema
|
|
388
414
|
// supplies defaults for any omitted field.
|
|
389
415
|
delegation_advisory: DelegationAdvisoryPolicySchema.optional(),
|
|
416
|
+
// 0.48.0 per-session shim cache — drives `hooks/_lib/shim-cache.sh`
|
|
417
|
+
// which short-circuits the sandbox check + version probe in
|
|
418
|
+
// `hooks/_lib/shim-runtime.sh` on session-warm fires of the same
|
|
419
|
+
// shim. Optional — vanilla installs get the default behavior
|
|
420
|
+
// (cache enabled). The bash-tier `shim_cache_disabled` helper
|
|
421
|
+
// honors `enabled: false` via a narrow inline YAML grep before
|
|
422
|
+
// the canonical policy reader is reachable. `REA_SHIM_CACHE=0`
|
|
423
|
+
// in env overrides this to `false` for the current invocation
|
|
424
|
+
// regardless of policy.
|
|
425
|
+
shim_cache: ShimCachePolicySchema.optional(),
|
|
390
426
|
})
|
|
391
427
|
.strict();
|
|
392
428
|
const DEFAULT_CACHE_TTL_MS = 30_000;
|
package/dist/policy/types.d.ts
CHANGED
|
@@ -533,4 +533,56 @@ export interface Policy {
|
|
|
533
533
|
* blocks. See `DelegationAdvisoryPolicy` for the full contract.
|
|
534
534
|
*/
|
|
535
535
|
delegation_advisory?: DelegationAdvisoryPolicy;
|
|
536
|
+
/**
|
|
537
|
+
* Per-session shim cache (0.48.0+).
|
|
538
|
+
*
|
|
539
|
+
* The `hooks/_lib/shim-cache.sh` helper, sourced by every Node-binary
|
|
540
|
+
* shim via `hooks/_lib/shim-runtime.sh`, records the answers to the
|
|
541
|
+
* sandbox check + version probe under a per-user, per-session,
|
|
542
|
+
* per-CLI key. Subsequent shim fires within the same Claude Code
|
|
543
|
+
* session against the same CLI (mtime + size unchanged) skip
|
|
544
|
+
* straight to the forward step.
|
|
545
|
+
*
|
|
546
|
+
* The cache is an OPTIMIZATION, not a security boundary. Cache miss
|
|
547
|
+
* / disabled / corruption all fall through to the existing uncached
|
|
548
|
+
* hot path — never fail closed.
|
|
549
|
+
*
|
|
550
|
+
* `enabled` default: `true`. Set `false` to disable the cache layer
|
|
551
|
+
* at the policy tier (equivalent effect to setting the
|
|
552
|
+
* `REA_SHIM_CACHE=0` env var on every invocation). Operators who
|
|
553
|
+
* want to measure unconditional steady-state latency should use the
|
|
554
|
+
* env-var form so the cache stays off only for the measurement
|
|
555
|
+
* window. See `docs/shim-session-cache-design.md` for the security
|
|
556
|
+
* contract and `docs/hook-perf-baseline.md` for the perf
|
|
557
|
+
* methodology note.
|
|
558
|
+
*/
|
|
559
|
+
shim_cache?: ShimCachePolicy;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Per-session shim cache policy (0.48.0+).
|
|
563
|
+
*
|
|
564
|
+
* The cache short-circuits the sandbox check + version probe in
|
|
565
|
+
* `hooks/_lib/shim-runtime.sh` on session-warm fires of the same
|
|
566
|
+
* shim. The on-disk entry shape is bound to `schema_version: "v1"`
|
|
567
|
+
* — a schema bump (future cache field additions) invalidates every
|
|
568
|
+
* existing entry. TTL is hard-capped at 3600s (1h) inside the
|
|
569
|
+
* runtime; this block does not expose a TTL knob in 0.48.0 because
|
|
570
|
+
* the optimization is steady-state-bound and a longer TTL would
|
|
571
|
+
* extend staleness without measurable benefit.
|
|
572
|
+
*/
|
|
573
|
+
export interface ShimCachePolicy {
|
|
574
|
+
/**
|
|
575
|
+
* Master switch. `true` (default) enables the cache. `false`
|
|
576
|
+
* disables both reads and writes — the runtime falls through to
|
|
577
|
+
* the existing uncached hot path on every fire. `REA_SHIM_CACHE=0`
|
|
578
|
+
* in env overrides this to `false` for the current invocation
|
|
579
|
+
* regardless of policy.
|
|
580
|
+
*
|
|
581
|
+
* NOTE 0.48.0: the bash-tier `shim_cache_disabled` helper consults
|
|
582
|
+
* this field via a narrow YAML grep BEFORE the canonical 4-tier
|
|
583
|
+
* policy reader is available (cache runs in the shim's pre-CLI
|
|
584
|
+
* section). The TS loader's schema validation runs at full CLI
|
|
585
|
+
* load time and catches typos / wrong types.
|
|
586
|
+
*/
|
|
587
|
+
enabled?: boolean;
|
|
536
588
|
}
|