@bookedsolid/rea 0.37.0 → 0.38.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/hooks/_lib/shim-runtime.sh +405 -0
- package/hooks/architecture-review-gate.sh +11 -103
- package/hooks/attribution-advisory.sh +38 -209
- package/hooks/blocked-paths-bash-gate.sh +32 -146
- package/hooks/blocked-paths-enforcer.sh +32 -137
- package/hooks/changeset-security-gate.sh +26 -119
- package/hooks/dangerous-bash-interceptor.sh +46 -170
- package/hooks/delegation-advisory.sh +26 -144
- package/hooks/delegation-capture.sh +33 -139
- package/hooks/dependency-audit-gate.sh +29 -121
- package/hooks/env-file-protection.sh +30 -141
- package/hooks/local-review-gate.sh +117 -352
- package/hooks/pr-issue-link-gate.sh +16 -118
- package/hooks/protected-paths-bash-gate.sh +53 -152
- package/hooks/secret-scanner.sh +90 -213
- package/hooks/security-disclosure-gate.sh +32 -155
- package/hooks/settings-protection.sh +56 -176
- package/package.json +1 -1
- package/templates/_lib_shim-runtime.dogfood-staged.sh +405 -0
- package/templates/architecture-review-gate.dogfood-staged.sh +11 -103
- package/templates/attribution-advisory.dogfood-staged.sh +38 -209
- package/templates/blocked-paths-bash-gate.dogfood-staged.sh +32 -146
- package/templates/blocked-paths-enforcer.dogfood-staged.sh +32 -137
- package/templates/changeset-security-gate.dogfood-staged.sh +26 -119
- package/templates/dangerous-bash-interceptor.dogfood-staged.sh +46 -170
- package/templates/delegation-advisory.dogfood-staged.sh +44 -0
- package/templates/delegation-capture.dogfood-staged.sh +52 -0
- package/templates/dependency-audit-gate.dogfood-staged.sh +29 -121
- package/templates/env-file-protection.dogfood-staged.sh +30 -141
- package/templates/local-review-gate.dogfood-staged.sh +117 -352
- package/templates/pr-issue-link-gate.dogfood-staged.sh +16 -118
- package/templates/protected-paths-bash-gate.dogfood-staged.sh +53 -152
- package/templates/secret-scanner.dogfood-staged.sh +90 -213
- package/templates/security-disclosure-gate.dogfood-staged.sh +32 -155
- package/templates/settings-protection.dogfood-staged.sh +56 -176
|
@@ -1,201 +1,81 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# PreToolUse hook: settings-protection.sh
|
|
3
3
|
# 0.35.0+ — Node-binary shim for `rea hook settings-protection`.
|
|
4
|
+
# 0.38.0+ — migrated to `_lib/shim-runtime.sh` (shared runtime).
|
|
4
5
|
#
|
|
5
|
-
# Pre-0.35.0 this was the LARGEST hook in the repo at 582 LOC of bash
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# (PROTECTED_PATTERNS sourced from `_lib/protected-paths.sh` with
|
|
10
|
-
# `protected_writes` override + `protected_paths_relax` subtractor),
|
|
11
|
-
# §6c intermediate-symlink resolution against the hard-protected list,
|
|
12
|
-
# §6b REA_HOOK_PATCH_SESSION unlock for .claude/hooks/ with hash-
|
|
13
|
-
# chained audit append (fail-closed). The full bash body is preserved
|
|
14
|
-
# at `__tests__/hooks/parity/baselines/settings-protection.sh.pre-0.35.0`.
|
|
6
|
+
# Pre-0.35.0 this was the LARGEST hook in the repo at 582 LOC of bash;
|
|
7
|
+
# the full bash body is preserved at
|
|
8
|
+
# `__tests__/hooks/parity/baselines/settings-protection.sh.pre-0.35.0`.
|
|
9
|
+
# Migration in `src/hooks/settings-protection/index.ts`.
|
|
15
10
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# exits with whatever the CLI returns.
|
|
11
|
+
# THE gate protecting the entire governance layer from agent self-
|
|
12
|
+
# disable. SHIM_ENFORCE_CLI_SHAPE=1 closes the 0.35.0 codex round-1 P1
|
|
13
|
+
# (forged in-project JS as the trusted gate CLI).
|
|
20
14
|
#
|
|
21
|
-
#
|
|
22
|
-
# exit 2 on HALT / traversal-reject / interior-dot-reject / protected
|
|
23
|
-
# match / patch-session-mismatch / malformed payload.
|
|
15
|
+
# # Relevance pre-gate (CLI-missing only)
|
|
24
16
|
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# settings-protection is THE gate protecting the entire governance layer
|
|
32
|
-
# from agent self-disable. Pre-0.35.0 the bash body enforced refusal
|
|
33
|
-
# without any compiled CLI; the Node-binary port preserves that — early-
|
|
34
|
-
# exit branches fail closed AFTER the relevance pre-gate passes.
|
|
35
|
-
#
|
|
36
|
-
# # Relevance pre-gate
|
|
37
|
-
#
|
|
38
|
-
# Substring scan over the extracted file_path / notebook_path for the
|
|
39
|
-
# protected-path markers (.claude/, .husky/, .rea/policy.yaml, .rea/HALT,
|
|
40
|
-
# the verdict cache paths, plus any policy.blocked_paths entry). When
|
|
41
|
-
# CLI is missing AND none of these substrings appear in the payload's
|
|
42
|
-
# file path, exit 0. The pre-0.35.0 bash body would have allowed.
|
|
43
|
-
#
|
|
44
|
-
# # Bootstrap safety
|
|
45
|
-
#
|
|
46
|
-
# This shim is ITSELF protected by `settings-protection.sh`. The new
|
|
47
|
-
# shim must not block legitimate writes — the `bash -n` syntax check
|
|
48
|
-
# in the test:bash-syntax script catches parse errors BEFORE the
|
|
49
|
-
# install lands them. The relevance pre-gate keeps benign writes (like
|
|
50
|
-
# editing `src/foo.ts`) exiting 0 even when the CLI is missing.
|
|
17
|
+
# Substring scan over file_path / notebook_path for protected-path
|
|
18
|
+
# markers (.claude/, .husky/, .rea/policy.yaml, .rea/HALT, the
|
|
19
|
+
# verdict cache paths), plus any policy.protected_writes entry. Empty
|
|
20
|
+
# / missing policy is OK — the static marker set still catches the
|
|
21
|
+
# canonical protected paths.
|
|
51
22
|
|
|
52
23
|
set -uo pipefail
|
|
53
24
|
|
|
54
|
-
# 1. HALT check.
|
|
55
25
|
# shellcheck source=_lib/halt-check.sh
|
|
56
26
|
source "$(dirname "$0")/_lib/halt-check.sh"
|
|
57
27
|
check_halt
|
|
58
28
|
REA_ROOT=$(rea_root)
|
|
59
29
|
|
|
60
|
-
|
|
30
|
+
SHIM_NAME="settings-protection"
|
|
31
|
+
SHIM_INTRODUCED_IN="0.35.0"
|
|
32
|
+
SHIM_FAIL_OPEN=0
|
|
33
|
+
SHIM_ENFORCE_CLI_SHAPE=1
|
|
34
|
+
SHIM_REFUSAL_NOUN="protected-path refusal"
|
|
61
35
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# 3. Resolve the rea CLI through the fixed 2-tier sandboxed order.
|
|
66
|
-
REA_ARGV=()
|
|
67
|
-
RESOLVED_CLI_PATH=""
|
|
68
|
-
if [ -f "$proj/node_modules/@bookedsolid/rea/dist/cli/index.js" ]; then
|
|
69
|
-
REA_ARGV=(node "$proj/node_modules/@bookedsolid/rea/dist/cli/index.js")
|
|
70
|
-
RESOLVED_CLI_PATH="$proj/node_modules/@bookedsolid/rea/dist/cli/index.js"
|
|
71
|
-
elif [ -f "$proj/dist/cli/index.js" ]; then
|
|
72
|
-
REA_ARGV=(node "$proj/dist/cli/index.js")
|
|
73
|
-
RESOLVED_CLI_PATH="$proj/dist/cli/index.js"
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
# 3b. Relevance pre-gate. Only used when the CLI is missing.
|
|
77
|
-
if [ "${#REA_ARGV[@]}" -eq 0 ]; then
|
|
78
|
-
CLI_MISSING_FILE_PATH=""
|
|
36
|
+
shim_cli_missing_relevant() {
|
|
37
|
+
local cli_missing_file_path=""
|
|
79
38
|
if command -v jq >/dev/null 2>&1; then
|
|
80
|
-
|
|
39
|
+
cli_missing_file_path=$(printf '%s' "$INPUT" | jq -r '
|
|
81
40
|
(.tool_input.file_path // .tool_input.notebook_path // "") | tostring
|
|
82
41
|
' 2>/dev/null || true)
|
|
83
42
|
else
|
|
84
|
-
|
|
43
|
+
cli_missing_file_path="$INPUT"
|
|
85
44
|
fi
|
|
86
|
-
if [ -z "$
|
|
87
|
-
|
|
45
|
+
if [ -z "$cli_missing_file_path" ]; then
|
|
46
|
+
return 1
|
|
88
47
|
fi
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
*".claude/
|
|
92
|
-
*".
|
|
93
|
-
*".
|
|
94
|
-
*".rea/
|
|
95
|
-
*".rea/
|
|
96
|
-
*".rea
|
|
97
|
-
*"
|
|
98
|
-
*"..%2F"*|*"%2E%2E"*) CLI_MISSING_RELEVANT=1 ;;
|
|
48
|
+
case "$cli_missing_file_path" in
|
|
49
|
+
*".claude/settings"*) return 0 ;;
|
|
50
|
+
*".claude/hooks/"*) return 0 ;;
|
|
51
|
+
*".husky/"*) return 0 ;;
|
|
52
|
+
*".rea/policy.yaml"*) return 0 ;;
|
|
53
|
+
*".rea/HALT"*) return 0 ;;
|
|
54
|
+
*".rea/last-review"*) return 0 ;;
|
|
55
|
+
*".claude\\"*|*".husky\\"*|*".rea\\"*) return 0 ;;
|
|
56
|
+
*"..%2F"*|*"%2E%2E"*) return 0 ;;
|
|
99
57
|
esac
|
|
100
|
-
# 0.37.0: route protected_writes reads through the unified
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
*/) base="${base%/}" ;;
|
|
118
|
-
esac
|
|
119
|
-
[ -z "$base" ] && continue
|
|
120
|
-
case "$CLI_MISSING_FILE_PATH" in
|
|
121
|
-
*"$base"*) CLI_MISSING_RELEVANT=1; break ;;
|
|
122
|
-
esac
|
|
123
|
-
done < <(policy_reader_get_list protected_writes 2>/dev/null)
|
|
124
|
-
fi
|
|
58
|
+
# 0.37.0: route protected_writes reads through the unified policy-reader.
|
|
59
|
+
local policy_file="${REA_ROOT}/.rea/policy.yaml"
|
|
60
|
+
if [ -f "$policy_file" ]; then
|
|
61
|
+
# shellcheck source=_lib/policy-reader.sh
|
|
62
|
+
source "$(dirname "$0")/_lib/policy-reader.sh"
|
|
63
|
+
local entry base
|
|
64
|
+
while IFS= read -r entry; do
|
|
65
|
+
[ -z "$entry" ] && continue
|
|
66
|
+
base="$entry"
|
|
67
|
+
case "$base" in
|
|
68
|
+
*/) base="${base%/}" ;;
|
|
69
|
+
esac
|
|
70
|
+
[ -z "$base" ] && continue
|
|
71
|
+
case "$cli_missing_file_path" in
|
|
72
|
+
*"$base"*) return 0 ;;
|
|
73
|
+
esac
|
|
74
|
+
done < <(policy_reader_get_list protected_writes 2>/dev/null)
|
|
125
75
|
fi
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
fi
|
|
129
|
-
printf 'rea: settings-protection cannot run — the rea CLI is not built.\n' >&2
|
|
130
|
-
printf 'Run `pnpm install && pnpm build` (or `npm install` for a consumer install) to restore protection.\n' >&2
|
|
131
|
-
printf 'This shim fails closed because the pre-0.35.0 bash body enforced protected-path refusal without a CLI.\n' >&2
|
|
132
|
-
exit 2
|
|
133
|
-
fi
|
|
134
|
-
|
|
135
|
-
# 4. Realpath sandbox check.
|
|
136
|
-
if ! command -v node >/dev/null 2>&1; then
|
|
137
|
-
printf 'rea: settings-protection cannot run — `node` is not on PATH.\n' >&2
|
|
138
|
-
printf 'Install Node 22+ (engines.node) to restore protected-path refusal.\n' >&2
|
|
139
|
-
exit 2
|
|
140
|
-
fi
|
|
141
|
-
|
|
142
|
-
sandbox_check=$(node -e '
|
|
143
|
-
const fs = require("fs");
|
|
144
|
-
const path = require("path");
|
|
145
|
-
const cli = process.argv[1];
|
|
146
|
-
const projDir = process.argv[2];
|
|
147
|
-
let real, realProj;
|
|
148
|
-
try { real = fs.realpathSync(cli); } catch (e) {
|
|
149
|
-
process.stdout.write("bad:realpath"); process.exit(1);
|
|
150
|
-
}
|
|
151
|
-
try { realProj = fs.realpathSync(projDir); } catch (e) {
|
|
152
|
-
process.stdout.write("bad:realpath-proj"); process.exit(1);
|
|
153
|
-
}
|
|
154
|
-
const sep = path.sep;
|
|
155
|
-
const projWithSep = realProj.endsWith(sep) ? realProj : realProj + sep;
|
|
156
|
-
if (!(real === realProj || real.startsWith(projWithSep))) {
|
|
157
|
-
process.stdout.write("bad:cli-escapes-project"); process.exit(1);
|
|
158
|
-
}
|
|
159
|
-
// Codex round-1 P1 fix: enforce dist/cli/index.js shape so a
|
|
160
|
-
// workspace attacker who repoints node_modules/@bookedsolid/rea or
|
|
161
|
-
// dist at an arbitrary in-project JS file cannot execute it as the
|
|
162
|
-
// trusted gate CLI. Pre-0.35.0 shims had this check; the 0.34.0
|
|
163
|
-
// round-8 template dropped it; restored here.
|
|
164
|
-
const expectedEnd = path.join("dist", "cli", "index.js");
|
|
165
|
-
if (!real.endsWith(path.sep + expectedEnd) && real !== "/" + expectedEnd) {
|
|
166
|
-
process.stdout.write("bad:cli-shape"); process.exit(1);
|
|
167
|
-
}
|
|
168
|
-
let cur = path.dirname(path.dirname(path.dirname(real)));
|
|
169
|
-
let found = false;
|
|
170
|
-
for (let i = 0; i < 20 && cur && cur !== path.dirname(cur); i += 1) {
|
|
171
|
-
const pj = path.join(cur, "package.json");
|
|
172
|
-
if (fs.existsSync(pj)) {
|
|
173
|
-
try {
|
|
174
|
-
const data = JSON.parse(fs.readFileSync(pj, "utf8"));
|
|
175
|
-
if (data && data.name === "@bookedsolid/rea") { found = true; break; }
|
|
176
|
-
} catch (e) { /* keep walking */ }
|
|
177
|
-
}
|
|
178
|
-
cur = path.dirname(cur);
|
|
179
|
-
}
|
|
180
|
-
if (!found) { process.stdout.write("bad:no-rea-pkg-json"); process.exit(1); }
|
|
181
|
-
process.stdout.write("ok");
|
|
182
|
-
' -- "$RESOLVED_CLI_PATH" "$proj" 2>/dev/null)
|
|
183
|
-
|
|
184
|
-
if [ "$sandbox_check" != "ok" ]; then
|
|
185
|
-
printf 'rea: settings-protection FAILED sandbox check (%s) — refusing.\n' "$sandbox_check" >&2
|
|
186
|
-
exit 2
|
|
187
|
-
fi
|
|
188
|
-
|
|
189
|
-
# 5. Version-probe.
|
|
190
|
-
probe_out=$("${REA_ARGV[@]}" hook settings-protection --help 2>&1)
|
|
191
|
-
probe_status=$?
|
|
192
|
-
if [ "$probe_status" -ne 0 ] || ! printf '%s' "$probe_out" | grep -q -e 'settings-protection'; then
|
|
193
|
-
printf 'rea: this shim requires the `rea hook settings-protection` subcommand (introduced in 0.35.0).\n' >&2
|
|
194
|
-
printf 'The resolved CLI at %s does not implement it.\n' "$RESOLVED_CLI_PATH" >&2
|
|
195
|
-
printf 'Run `pnpm install` (or `npm install`) to sync the CLI; refusing in the meantime to preserve enforcement.\n' >&2
|
|
196
|
-
exit 2
|
|
197
|
-
fi
|
|
76
|
+
return 1
|
|
77
|
+
}
|
|
198
78
|
|
|
199
|
-
#
|
|
200
|
-
|
|
201
|
-
|
|
79
|
+
# shellcheck source=_lib/shim-runtime.sh
|
|
80
|
+
source "$(dirname "$0")/_lib/shim-runtime.sh"
|
|
81
|
+
shim_run
|