@bookedsolid/rea 0.16.0 → 0.16.2
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/.husky/commit-msg +8 -2
- package/agents/codex-adversarial.md +2 -0
- package/hooks/_lib/cmd-segments.sh +61 -1
- package/hooks/attribution-advisory.sh +5 -1
- package/hooks/dangerous-bash-interceptor.sh +12 -5
- package/hooks/dependency-audit-gate.sh +22 -2
- package/hooks/env-file-protection.sh +11 -14
- package/package.json +1 -1
package/.husky/commit-msg
CHANGED
|
@@ -99,9 +99,15 @@ if grep -qiE '^\s*(Generated|Created|Built|Powered|Authored|Written|Produced)\s+
|
|
|
99
99
|
fi
|
|
100
100
|
|
|
101
101
|
# Pattern 4: Markdown-linked attribution
|
|
102
|
-
|
|
102
|
+
# 0.16.2 helix-017 P3 #4: anchor on the markdown link shape `[Text](url)`
|
|
103
|
+
# rather than bare `[Text]`. Pre-fix the regex matched ANY bracketed
|
|
104
|
+
# mention — `feat: support [Claude Code] hook output format` would block
|
|
105
|
+
# a perfectly legitimate commit. The markdown-link form requires the `(`
|
|
106
|
+
# immediately after the closing bracket, which is the actual structural
|
|
107
|
+
# attribution we want to catch.
|
|
108
|
+
if grep -qiE '\[Claude Code\]\(|\[GitHub Copilot\]\(|\[ChatGPT\]\(|\[Gemini\]\(|\[Cursor\]\(' "$COMMIT_MSG_FILE" 2>/dev/null; then
|
|
103
109
|
BLOCKED=1
|
|
104
|
-
MATCHES="${MATCHES}$(grep -niE '\[Claude Code\]|\[GitHub Copilot\]|\[ChatGPT\]|\[Gemini\]|\[Cursor\]' "$COMMIT_MSG_FILE" 2>/dev/null)
|
|
110
|
+
MATCHES="${MATCHES}$(grep -niE '\[Claude Code\]\(|\[GitHub Copilot\]\(|\[ChatGPT\]\(|\[Gemini\]\(|\[Cursor\]\(' "$COMMIT_MSG_FILE" 2>/dev/null)
|
|
105
111
|
"
|
|
106
112
|
fi
|
|
107
113
|
|
|
@@ -33,6 +33,8 @@ You may read additional files in the repo if needed for context, but do so read-
|
|
|
33
33
|
2. **Validate Codex availability** — if `/codex` is not installed, report and stop. Do not silently fall back to another reviewer.
|
|
34
34
|
3. **Prepare the Codex invocation** — construct the adversarial-review prompt with the diff, commit log, and any relevant context files.
|
|
35
35
|
4. **Invoke `/codex:adversarial-review`** — this call flows through the REA middleware chain (audit → kill-switch → tier → policy → redact → injection → execute → result-size-cap).
|
|
36
|
+
|
|
37
|
+
**Model pinning (0.16.1+):** when the codex plugin's adversarial-review supports model overrides, request `gpt-5.4` with `model_reasoning_effort: high` to match the push-gate's iron-gate defaults. Pre-0.16.1, in-session adversarial reviews ran on whatever the plugin defaulted to (likely `codex-auto-review` at medium reasoning) — meaningfully WEAKER than the push-gate's `gpt-5.4` + `high`. This caused a "in-session review passes, push-gate review fails" pattern reported by helix across 014 / 015 / 016. If the plugin call accepts model parameters, pass them. If it does not, fall back to invoking `codex exec review --base <ref> --json --ephemeral -c model="gpt-5.4" -c model_reasoning_effort="high"` directly via `Bash` — same shape the push-gate uses (see `src/hooks/push-gate/codex-runner.ts::runCodexReview`). The cost of the stronger model is small relative to the cost of shipping a release with a P1 bypass that gets caught at consumer push time.
|
|
36
38
|
5. **Parse the Codex output** — extract structured findings.
|
|
37
39
|
6. **Classify findings** by category: security, correctness, edge cases, test gaps, API design, performance.
|
|
38
40
|
7. **Assign verdict**: `pass` (no material findings), `concerns` (findings worth addressing but not blocking), `blocking` (findings that must be fixed before merge).
|
|
@@ -71,9 +71,17 @@ _rea_split_segments() {
|
|
|
71
71
|
# under any encoding we care about (any agent that intentionally
|
|
72
72
|
# included this string would already be obviously trying to confuse
|
|
73
73
|
# the splitter — and even then, the worst case is fail-closed).
|
|
74
|
+
# 0.16.1 helix-016 P1 fix: also split on single `&` (background-process
|
|
75
|
+
# operator). Pre-fix the splitter only broke on `&&|||;|`; a command like
|
|
76
|
+
# `sleep 1 & git push --force` was treated as ONE segment whose first
|
|
77
|
+
# token is `sleep`, and `any_segment_starts_with($CMD, 'git push')`
|
|
78
|
+
# missed the force-push entirely. Add `&` to the separator set, but
|
|
79
|
+
# AFTER `&&` is already swapped out so we don't break it apart.
|
|
74
80
|
printf '%s\n' "$cmd" \
|
|
75
81
|
| sed -E 's/>\|/__REA_GTPIPE_a8f2c1__/g' \
|
|
76
|
-
| sed -E 's/
|
|
82
|
+
| sed -E 's/&&/__REA_LOGAND_a8f2c1__/g' \
|
|
83
|
+
| sed -E 's/(\|\||;|\||&)/\n/g' \
|
|
84
|
+
| sed -E 's/__REA_LOGAND_a8f2c1__/\n/g' \
|
|
77
85
|
| sed -E 's/__REA_GTPIPE_a8f2c1__/>|/g'
|
|
78
86
|
}
|
|
79
87
|
|
|
@@ -151,6 +159,58 @@ any_segment_matches() {
|
|
|
151
159
|
return 1
|
|
152
160
|
}
|
|
153
161
|
|
|
162
|
+
# Return 0 if any segment of $1 (RAW — no prefix-stripping) matches the
|
|
163
|
+
# extended regex $2. Use this for checks where the prefix itself IS the
|
|
164
|
+
# signal — e.g. H10's `HUSKY=0 git commit` detection (the prefix-stripper
|
|
165
|
+
# would strip the `HUSKY=0` before any_segment_matches sees it). Also
|
|
166
|
+
# right for H15 (`REA_BYPASS=...`) and H16 (alias/function defs).
|
|
167
|
+
#
|
|
168
|
+
# 0.16.1 helix-016 sibling fix: H10 baseline corpus regressed from
|
|
169
|
+
# 0.15.0 because it migrated to `any_segment_matches` which strips
|
|
170
|
+
# env-var prefixes. The check needs the raw segment to fire.
|
|
171
|
+
any_segment_raw_matches() {
|
|
172
|
+
local cmd="$1"
|
|
173
|
+
local pattern="$2"
|
|
174
|
+
local segment
|
|
175
|
+
while IFS= read -r segment; do
|
|
176
|
+
# Trim leading whitespace for clean anchor matching, but otherwise
|
|
177
|
+
# leave the segment intact (env-var assignments preserved).
|
|
178
|
+
segment="${segment#"${segment%%[![:space:]]*}"}"
|
|
179
|
+
if printf '%s' "$segment" | grep -qiE "$pattern"; then
|
|
180
|
+
return 0
|
|
181
|
+
fi
|
|
182
|
+
done < <(_rea_split_segments "$cmd")
|
|
183
|
+
return 1
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Return 0 if any single segment of $1 (after prefix-stripping) matches
|
|
187
|
+
# BOTH extended regex $2 AND extended regex $3. Case-insensitive. Returns
|
|
188
|
+
# 1 if no single segment matches both patterns.
|
|
189
|
+
#
|
|
190
|
+
# Use this when two patterns must co-occur within the SAME shell command
|
|
191
|
+
# segment to constitute a detection — e.g. env-file-protection's
|
|
192
|
+
# "utility + .env-filename" rule. Pre-fix env-file-protection used two
|
|
193
|
+
# independent `any_segment_matches` calls and OR-combined the booleans,
|
|
194
|
+
# which mis-fires across multi-segment constructions like
|
|
195
|
+
# `echo "log: cat is broken" ; touch foo.env` (utility in segment 1,
|
|
196
|
+
# .env name in segment 2 — both flags set, false-positive block).
|
|
197
|
+
#
|
|
198
|
+
# 0.16.2 helix-017 P2 #2 fix.
|
|
199
|
+
any_segment_matches_both() {
|
|
200
|
+
local cmd="$1"
|
|
201
|
+
local pattern_a="$2"
|
|
202
|
+
local pattern_b="$3"
|
|
203
|
+
local segment stripped
|
|
204
|
+
while IFS= read -r segment; do
|
|
205
|
+
stripped=$(_rea_strip_prefix "$segment")
|
|
206
|
+
if printf '%s' "$stripped" | grep -qiE "$pattern_a" \
|
|
207
|
+
&& printf '%s' "$stripped" | grep -qiE "$pattern_b"; then
|
|
208
|
+
return 0
|
|
209
|
+
fi
|
|
210
|
+
done < <(_rea_split_segments "$cmd")
|
|
211
|
+
return 1
|
|
212
|
+
}
|
|
213
|
+
|
|
154
214
|
# Return 0 if any segment of $1 (after prefix-stripping) STARTS WITH
|
|
155
215
|
# the extended regex $2. Case-insensitive. Returns 1 if no segment
|
|
156
216
|
# starts with the pattern.
|
|
@@ -92,7 +92,11 @@ if any_segment_matches "$CMD" '(Generated|Created|Built|Powered|Authored|Written
|
|
|
92
92
|
fi
|
|
93
93
|
|
|
94
94
|
# Markdown-linked attribution
|
|
95
|
-
|
|
95
|
+
# 0.16.2 helix-017 P3 #4: anchor on `[Text](` (markdown link shape) so
|
|
96
|
+
# legitimate bracketed mentions like `gh pr edit --body "support [Claude
|
|
97
|
+
# Code] hook output"` don't false-positive. The actual attribution we
|
|
98
|
+
# care about is structural — `Generated with [Claude Code](https://...)`.
|
|
99
|
+
if any_segment_matches "$CMD" '\[Claude Code\]\(|\[GitHub Copilot\]\(|\[ChatGPT\]\(|\[Gemini\]\(|\[Cursor\]\('; then
|
|
96
100
|
FOUND=1
|
|
97
101
|
fi
|
|
98
102
|
|
|
@@ -216,7 +216,7 @@ if any_segment_starts_with "$CMD" 'git[[:space:]]+commit.*--no-verify'; then
|
|
|
216
216
|
fi
|
|
217
217
|
|
|
218
218
|
# H10: HUSKY=0 bypass — suppresses all git hooks without --no-verify
|
|
219
|
-
if
|
|
219
|
+
if any_segment_raw_matches "$CMD" '^HUSKY=0[[:space:]]+git[[:space:]]+(commit|push|tag)'; then
|
|
220
220
|
add_high \
|
|
221
221
|
"HUSKY=0 — bypasses all husky git hooks" \
|
|
222
222
|
"Setting HUSKY=0 disables pre-commit, commit-msg, and pre-push safety gates without --no-verify." \
|
|
@@ -243,8 +243,15 @@ if any_segment_starts_with "$CMD" "rm[[:space:]]+-[a-zA-Z]*r[a-zA-Z]*f[[:space:]
|
|
|
243
243
|
"Alt: Move to a temp location first, or use 'rm -ri' for interactive deletion."
|
|
244
244
|
fi
|
|
245
245
|
|
|
246
|
-
# H12: curl/wget piped directly to shell (supply chain attack vector)
|
|
247
|
-
|
|
246
|
+
# H12: curl/wget piped directly to shell (supply chain attack vector).
|
|
247
|
+
# 0.16.1 helix-016 P1 fix: this check requires BOTH the curl/wget call
|
|
248
|
+
# AND the `| sh` to appear in the same shell pipeline. The 0.16.0
|
|
249
|
+
# refactor moved this into `any_segment_matches`, but the segmenter
|
|
250
|
+
# splits on `|` first — so `curl https://x | sh` decomposed into two
|
|
251
|
+
# segments (`curl https://x`, `sh`) and the regex (which requires both
|
|
252
|
+
# in one segment) never matched. Pipe-RCE is fundamentally a
|
|
253
|
+
# multi-segment property and must be checked against the raw command.
|
|
254
|
+
if printf '%s' "$CMD" | grep -qiE '(curl|wget)[^|]*\|[[:space:]]*(sudo[[:space:]]+)?(bash|sh|zsh|fish)'; then
|
|
248
255
|
add_high \
|
|
249
256
|
"curl/wget piped to shell — remote code execution" \
|
|
250
257
|
"Executing remote scripts without inspection is a major supply chain risk." \
|
|
@@ -268,7 +275,7 @@ if any_segment_starts_with "$CMD" 'git[[:space:]]+-c[[:space:]]+core\.hookspath'
|
|
|
268
275
|
fi
|
|
269
276
|
|
|
270
277
|
# H15: REA_BYPASS env var — attempted escape hatch
|
|
271
|
-
if
|
|
278
|
+
if any_segment_raw_matches "$CMD" '^REA_BYPASS[[:space:]]*='; then
|
|
272
279
|
add_high \
|
|
273
280
|
"REA_BYPASS env var — unauthorized bypass attempt" \
|
|
274
281
|
"Setting REA_BYPASS is not a supported escape mechanism and indicates a bypass attempt." \
|
|
@@ -276,7 +283,7 @@ if any_segment_matches "$CMD" '(^|[[:space:];]|&&|\|\|)REA_BYPASS[[:space:]]*=';
|
|
|
276
283
|
fi
|
|
277
284
|
|
|
278
285
|
# H16: alias/function definitions containing bypass strings
|
|
279
|
-
if
|
|
286
|
+
if any_segment_raw_matches "$CMD" '^(alias|function)[[:space:]]+[a-zA-Z_]+.*(--(no-verify|force)|HUSKY=0|core\.hookspath)'; then
|
|
280
287
|
add_high \
|
|
281
288
|
"Alias/function definition with bypass — circumventing safety gates" \
|
|
282
289
|
"Defining aliases or functions that embed bypass flags defeats safety hooks." \
|
|
@@ -73,15 +73,35 @@ extract_packages() {
|
|
|
73
73
|
# Anchor to start: only match when the install command is the FIRST
|
|
74
74
|
# thing on the segment, optionally preceded by `sudo` / `exec` /
|
|
75
75
|
# `time` / etc.
|
|
76
|
-
|
|
76
|
+
#
|
|
77
|
+
# 0.16.1 helix-016 P2 fix: also strip leading KEY=VALUE env-var
|
|
78
|
+
# assignments. Pre-fix the prefix allow-list only permitted
|
|
79
|
+
# sudo/exec/time, so `CI=1 pnpm add foo` and
|
|
80
|
+
# `NODE_ENV=development npm install bar` bypassed the audit
|
|
81
|
+
# entirely. POSIX shell allows any number of leading KEY=VALUE
|
|
82
|
+
# assignments before the command word; we strip them the same
|
|
83
|
+
# way the shell does.
|
|
84
|
+
local stripped_segment
|
|
85
|
+
stripped_segment=$(printf '%s' "$segment" | sed -E 's/^([[:space:]]*[A-Za-z_][A-Za-z0-9_]*=[^[:space:]]+[[:space:]]+)+//')
|
|
86
|
+
|
|
87
|
+
if printf '%s' "$stripped_segment" | grep -qiE '^(sudo[[:space:]]+|exec[[:space:]]+|time[[:space:]]+)*(npm[[:space:]]+(install|i|add)|pnpm[[:space:]]+(add|install|i)|yarn[[:space:]]+add)[[:space:]]+'; then
|
|
77
88
|
# Strip the leading prefix wrappers + install command, leaving args.
|
|
78
89
|
local after_cmd
|
|
79
|
-
after_cmd=$(printf '%s' "$
|
|
90
|
+
after_cmd=$(printf '%s' "$stripped_segment" | sed -E 's/^(sudo[[:space:]]+|exec[[:space:]]+|time[[:space:]]+)*(npm[[:space:]]+(install|i|add)|pnpm[[:space:]]+(add|install|i)|yarn[[:space:]]+add)[[:space:]]+//')
|
|
80
91
|
|
|
81
92
|
for token in $after_cmd; do
|
|
82
93
|
if [[ "$token" == -* ]]; then continue; fi
|
|
83
94
|
if [[ "$token" == ./* || "$token" == /* || "$token" == ../* ]]; then continue; fi
|
|
84
95
|
if [[ -z "$token" ]]; then continue; fi
|
|
96
|
+
# 0.16.1: tighten token classification (helix-016 sibling concern).
|
|
97
|
+
# A "package name" is something that doesn't contain shell
|
|
98
|
+
# metacharacters — `2>&1`, `$VAR`, etc. are never valid npm
|
|
99
|
+
# package names. Skip any token containing `=`, `>`, `<`, `&`,
|
|
100
|
+
# `|`, `;`, `$`, backtick, or quotes.
|
|
101
|
+
if [[ "$token" == *=* || "$token" == *">"* || "$token" == *"<"* ||
|
|
102
|
+
"$token" == *"&"* || "$token" == *"|"* || "$token" == *";"* ||
|
|
103
|
+
"$token" == *'$'* || "$token" == *'`'* ||
|
|
104
|
+
"$token" == *'"'* || "$token" == *"'"* ]]; then continue; fi
|
|
85
105
|
# `npm view` can't validate `@workspace:*` / `link:` / `file:`
|
|
86
106
|
# prefixes (workspace protocols). Skip them — they're never npm
|
|
87
107
|
# registry packages.
|
|
@@ -71,19 +71,16 @@ PATTERN_CP_ENV='cp[[:space:]]+[^;|&]*\.env'
|
|
|
71
71
|
# .env* files or .envrc (direnv)
|
|
72
72
|
PATTERN_ENV_FILE='(\.env[a-zA-Z0-9._-]*|\.envrc)([[:space:]]|"|'"'"'|$)'
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if any_segment_matches "$CMD" "$PATTERN_ENV_FILE"; then
|
|
86
|
-
MATCHES_ENV_FILE=1
|
|
74
|
+
# 0.16.2 helix-017 P2 #2: utility AND env-filename must co-occur within
|
|
75
|
+
# the SAME shell segment. Pre-fix this set two independent booleans
|
|
76
|
+
# (any segment with utility OR any segment with .env) and AND'd them,
|
|
77
|
+
# which false-positived across multi-segment constructions like
|
|
78
|
+
# `echo "log: cat is broken" ; touch foo.env` (utility in segment 1,
|
|
79
|
+
# .env name in segment 2). Detection is fundamentally a same-segment
|
|
80
|
+
# co-occurrence property.
|
|
81
|
+
MATCHES_BOTH_SAME_SEGMENT=0
|
|
82
|
+
if any_segment_matches_both "$CMD" "$PATTERN_UTILITY" "$PATTERN_ENV_FILE"; then
|
|
83
|
+
MATCHES_BOTH_SAME_SEGMENT=1
|
|
87
84
|
fi
|
|
88
85
|
|
|
89
86
|
# Direct source/cp of .env files — always block
|
|
@@ -101,7 +98,7 @@ if any_segment_matches "$CMD" "$PATTERN_SOURCE" || \
|
|
|
101
98
|
exit 2
|
|
102
99
|
fi
|
|
103
100
|
|
|
104
|
-
if [[ $
|
|
101
|
+
if [[ $MATCHES_BOTH_SAME_SEGMENT -eq 1 ]]; then
|
|
105
102
|
TRUNCATED_CMD=$(truncate_cmd "$CMD")
|
|
106
103
|
{
|
|
107
104
|
printf 'ENV FILE PROTECTION: Reading .env files via Bash is blocked.\n'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bookedsolid/rea",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"description": "Agentic governance layer for Claude Code — policy enforcement, hook-based safety gates, audit logging, and Codex-integrated adversarial review for AI-assisted projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Booked Solid Technology <oss@bookedsolid.tech> (https://bookedsolid.tech)",
|