@bookedsolid/rea 0.46.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 +177 -0
- package/THREAT_MODEL.md +140 -0
- package/dist/cli/audit-timeline.d.ts +20 -0
- package/dist/cli/audit-timeline.js +262 -20
- package/dist/cli/audit-top-blocks.d.ts +154 -0
- package/dist/cli/audit-top-blocks.js +419 -0
- package/dist/cli/index.js +5 -0
- package/dist/config/tier-map.js +32 -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
|
@@ -528,6 +528,95 @@ under `--since` and lets the per-record timestamp filter drop the
|
|
|
528
528
|
out-of-window entries. Correctness over micro-optimization;
|
|
529
529
|
`rea audit summary` performance is unchanged in practice.
|
|
530
530
|
|
|
531
|
+
## Audit observability completion (added in 0.47.0)
|
|
532
|
+
|
|
533
|
+
0.46.0 shipped `rea audit by-tool` and `rea audit timeline`. 0.47.0
|
|
534
|
+
rounds out the observability surface with two timeline ergonomics fixes
|
|
535
|
+
and a new refusal-debugging reader:
|
|
536
|
+
|
|
537
|
+
### `rea audit timeline` — helpful MAX_BUCKETS errors + auto-clamp
|
|
538
|
+
|
|
539
|
+
Pre-0.47.0, `rea audit timeline --bucket=15m --since=21d` (= 2016
|
|
540
|
+
buckets, just past the 2000-bucket ceiling) rejected with a generic
|
|
541
|
+
"use a larger --bucket or narrower --since" message. The 0.47.0 error
|
|
542
|
+
now carries concrete remediation:
|
|
543
|
+
|
|
544
|
+
```text
|
|
545
|
+
rea audit timeline: --bucket=15m × --since=21d = 2016 buckets exceeds
|
|
546
|
+
MAX_BUCKETS=2000. Try --bucket=1h (504 buckets) or --since=20d 20h
|
|
547
|
+
(1999 buckets).
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
For the related "I omitted `--since` and the audit log spans a year"
|
|
551
|
+
case, the timeline now AUTO-CLAMPS to the widest window that fits at
|
|
552
|
+
the requested cadence rather than throwing. The clamp is surfaced
|
|
553
|
+
inline in human output:
|
|
554
|
+
|
|
555
|
+
```text
|
|
556
|
+
rea audit timeline (clamped to ~1999h of newest activity, hourly)
|
|
557
|
+
────────────────────────────────────────
|
|
558
|
+
note: --since not specified; auto-clamped to newest 2000 buckets
|
|
559
|
+
(~1999h span at --bucket=1h). Pass --since=DUR to anchor at
|
|
560
|
+
now, or rerun with a WIDER --bucket (current 1h) to fit the
|
|
561
|
+
full log.
|
|
562
|
+
…
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
JSON consumers see the clamp as a new `clamped_since` field — `null`
|
|
566
|
+
in the common case, a duration string (e.g. `"1999h"`) when the
|
|
567
|
+
clamp fired. The field is informational, not reproducible: `--since`
|
|
568
|
+
always anchors at `now`, so a clamp anchored at an older record
|
|
569
|
+
cannot be round-tripped through `--since=<clamped_since>`. Use the
|
|
570
|
+
field to detect that clamping occurred and to size the rendered
|
|
571
|
+
window in dashboards. For a fully reproducible view, pass `--since`
|
|
572
|
+
or `--bucket` explicitly. Schema version is unchanged (still v1) —
|
|
573
|
+
the field is purely additive. `window.start/end/seconds` is also
|
|
574
|
+
nulled out on sparse-log clamps where the kept buckets don't form a
|
|
575
|
+
contiguous time lattice, so `total_events / window.seconds` never
|
|
576
|
+
derives a misleading rate.
|
|
577
|
+
|
|
578
|
+
### `rea audit top-blocks` — debugging "why was that refused?"
|
|
579
|
+
|
|
580
|
+
A new subcommand surfaces the most recent refusal events (any record
|
|
581
|
+
whose `status` is `denied` or `error`) from the audit log:
|
|
582
|
+
|
|
583
|
+
```bash
|
|
584
|
+
rea audit top-blocks # last 20 refusals, all time
|
|
585
|
+
rea audit top-blocks --since=24h # last 24h
|
|
586
|
+
rea audit top-blocks --since=7d --limit=50 # last week, top 50
|
|
587
|
+
rea audit top-blocks --json # dashboard shape
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
Each row carries the short hash (first 8 chars), full timestamp, tool
|
|
591
|
+
name, and the refusal reason (sourced from the record's `error` field;
|
|
592
|
+
truncated to ~80 chars in human output, full text in JSON). Sorted
|
|
593
|
+
newest-first so the most recent refusals are at the top.
|
|
594
|
+
|
|
595
|
+
Use this when an agent reports "the hook blocked my push" or "the
|
|
596
|
+
write was refused" and you need the exact reason without grepping
|
|
597
|
+
`.rea/audit.jsonl` by hand.
|
|
598
|
+
|
|
599
|
+
JSON shape (stable, v1):
|
|
600
|
+
|
|
601
|
+
```json
|
|
602
|
+
{
|
|
603
|
+
"schema_version": 1,
|
|
604
|
+
"since": "24h",
|
|
605
|
+
"limit": 20,
|
|
606
|
+
"window": { "seconds": 86400, "start": "...", "end": "..." },
|
|
607
|
+
"total_matched": 4,
|
|
608
|
+
"events": [
|
|
609
|
+
{ "hash": "...", "timestamp": "...", "tool": "Bash",
|
|
610
|
+
"status": "denied", "reason": "...", "session_id": "..." }
|
|
611
|
+
],
|
|
612
|
+
"files_scanned": ["/abs/path/.rea/audit.jsonl"]
|
|
613
|
+
}
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
`total_matched` is the pre-limit count, so dashboards can show
|
|
617
|
+
"20 of 47 refusals in window". Walk scope mirrors the sibling audit
|
|
618
|
+
readers — current `.rea/audit.jsonl` PLUS every rotated segment.
|
|
619
|
+
|
|
531
620
|
## Policy knobs worth setting
|
|
532
621
|
|
|
533
622
|
For consumers with a long-running migration branch (>30 commits since
|
|
@@ -573,3 +662,91 @@ verdict that triggered it is.
|
|
|
573
662
|
document the ambivalence.
|
|
574
663
|
- **My pre-commit hook breaks on push** → not rea (rea ships no
|
|
575
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.
|
|
@@ -112,6 +112,16 @@ export interface AuditTimelineResult {
|
|
|
112
112
|
/** Index of the bucket with the highest count. `-1` when no events. */
|
|
113
113
|
peak_index: number;
|
|
114
114
|
files_scanned: string[];
|
|
115
|
+
/**
|
|
116
|
+
* 0.47.0 charter item 2: when `--since` was NOT specified and the
|
|
117
|
+
* audit log spans more than `MAX_BUCKETS` buckets at the requested
|
|
118
|
+
* cadence, the timeline auto-clamps the window to the widest duration
|
|
119
|
+
* that fits. This field carries the duration string that was actually
|
|
120
|
+
* applied (e.g. `"7d"`) — `null` when no clamping fired (the common
|
|
121
|
+
* case). Dashboard consumers use this to flag "the window you saw is
|
|
122
|
+
* not the whole log" in their UI.
|
|
123
|
+
*/
|
|
124
|
+
clamped_since: string | null;
|
|
115
125
|
}
|
|
116
126
|
export interface ComputeAuditTimelineOptions {
|
|
117
127
|
/** Override CWD. Tests set this; production uses `process.cwd()`. */
|
|
@@ -134,6 +144,16 @@ export interface ComputeAuditTimelineOptions {
|
|
|
134
144
|
* value but `MAX_BUCKETS` will bound the rendered output.
|
|
135
145
|
*/
|
|
136
146
|
export declare function resolveBucketSeconds(raw: string): number;
|
|
147
|
+
/**
|
|
148
|
+
* Format a duration in seconds as the coarsest single-unit compact
|
|
149
|
+
* string that round-trips through `parseDurationSeconds`. Mirrors the
|
|
150
|
+
* shape `--since` accepts (`s`/`m`/`h`/`d`/`w`).
|
|
151
|
+
*
|
|
152
|
+
* 0.47.0 charter item 1: powers the helpful-error suggestion + the
|
|
153
|
+
* auto-clamp `clamped_since` field. The largest-unit pass keeps the
|
|
154
|
+
* suggestion readable — `"21d"` not `"1814400s"`.
|
|
155
|
+
*/
|
|
156
|
+
export declare function formatDurationCompact(seconds: number): string;
|
|
137
157
|
/**
|
|
138
158
|
* Compute the bucketed timeline. Pure (read-only). Throws
|
|
139
159
|
* `AuditTimelineOptionError` on bad `--since` / `--bucket`; throws on
|