@bookedsolid/rea 0.9.4 → 0.10.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 +105 -0
- package/THREAT_MODEL.md +19 -1
- package/dist/audit/append.d.ts +35 -1
- package/dist/audit/append.js +79 -11
- package/dist/cli/audit.d.ts +31 -0
- package/dist/cli/audit.js +197 -30
- package/dist/cli/cache.d.ts +33 -1
- package/dist/cli/cache.js +40 -2
- package/dist/cli/doctor.js +1 -1
- package/dist/cli/index.js +58 -2
- package/dist/cli/tofu.d.ts +57 -0
- package/dist/cli/tofu.js +134 -0
- package/dist/config/tier-map.d.ts +1 -0
- package/dist/config/tier-map.js +210 -0
- package/dist/gateway/audit/rotator.js +4 -0
- package/dist/gateway/middleware/audit-types.d.ts +35 -0
- package/dist/gateway/middleware/audit.js +6 -0
- package/dist/gateway/middleware/blocked-paths.js +38 -0
- package/dist/gateway/middleware/policy.js +68 -3
- package/dist/registry/tofu-gate.js +4 -1
- package/hooks/_lib/push-review-core.sh +159 -26
- package/hooks/commit-review-gate.sh +25 -1
- package/hooks/settings-protection.sh +297 -64
- package/package.json +1 -1
|
@@ -59,89 +59,322 @@ normalize_path() {
|
|
|
59
59
|
p="${p#$root/}"
|
|
60
60
|
fi
|
|
61
61
|
|
|
62
|
-
# URL decode common sequences
|
|
63
|
-
|
|
62
|
+
# URL decode common sequences. Include %5C (`\`) so Windows-style or
|
|
63
|
+
# percent-encoded back-slash traversal (`..%5C`, `\..\`) normalizes to the
|
|
64
|
+
# forward-slash form the §5a detector sees.
|
|
65
|
+
p=$(printf '%s' "$p" \
|
|
66
|
+
| sed 's/%2[Ff]/\//g; s/%2[Ee]/./g; s/%20/ /g; s/%5[Cc]/\\/g')
|
|
64
67
|
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
p=$(printf '%s' "$p" |
|
|
68
|
+
# Translate any backslash separators to forward slashes. Keeps the traversal
|
|
69
|
+
# check in §5a working for `.claude\hooks\..\settings.json`-style inputs.
|
|
70
|
+
p=$(printf '%s' "$p" | tr '\\\\' '/')
|
|
68
71
|
|
|
69
|
-
#
|
|
70
|
-
|
|
72
|
+
# Strip leading ./ components only. We intentionally do NOT strip interior
|
|
73
|
+
# ./ sequences — that transformation corrupts `..` traversals (e.g. `.../`
|
|
74
|
+
# collapsed to `../`, or `../` collapsed to `./`) and hides traversal from
|
|
75
|
+
# the §5a detector.
|
|
76
|
+
while [[ "$p" == ./* ]]; do
|
|
77
|
+
p="${p#./}"
|
|
78
|
+
done
|
|
71
79
|
|
|
72
80
|
printf '%s' "$p"
|
|
73
81
|
}
|
|
74
82
|
|
|
83
|
+
# Strip C0/C1 control characters from a string to prevent terminal escape
|
|
84
|
+
# injection when we echo protected paths back to the operator. Escape sequences
|
|
85
|
+
# in file names could otherwise rewrite lines above the deny message.
|
|
86
|
+
#
|
|
87
|
+
# Byte ranges stripped:
|
|
88
|
+
# \000-\037 — C0 controls (BEL, BS, HT, LF, CR, ESC, …)
|
|
89
|
+
# \177 — DEL
|
|
90
|
+
# \200-\237 — C1 controls (CSI 0x9B, OSC 0x9D, …). Many terminals still
|
|
91
|
+
# interpret these as single-byte CSI introducers; without
|
|
92
|
+
# stripping, a UTF-8 file name whose bytes fall in this range
|
|
93
|
+
# could still drive the cursor on older emulators.
|
|
94
|
+
sanitize_for_stderr() {
|
|
95
|
+
printf '%s' "$1" | LC_ALL=C tr -d '\000-\037\177\200-\237'
|
|
96
|
+
}
|
|
97
|
+
|
|
75
98
|
NORMALIZED=$(normalize_path "$FILE_PATH")
|
|
99
|
+
SAFE_FILE_PATH=$(sanitize_for_stderr "$FILE_PATH")
|
|
100
|
+
SAFE_NORMALIZED=$(sanitize_for_stderr "$NORMALIZED")
|
|
101
|
+
|
|
102
|
+
# ── 5a. Reject path traversal segments (Codex HIGH: Defect I bypass) ─────────
|
|
103
|
+
# A path containing `..` segments can be used to bypass the protected-path
|
|
104
|
+
# globs in §6 — e.g. `.claude/hooks/../settings.json` would pass the
|
|
105
|
+
# `.claude/hooks/*` case-glob in the patch-session allowlist but actually
|
|
106
|
+
# refers to `.claude/settings.json`. We refuse any path that contains a `..`
|
|
107
|
+
# segment in either the raw input OR the normalized form. The request must
|
|
108
|
+
# be reissued with a canonical path.
|
|
109
|
+
#
|
|
110
|
+
# For the raw-input check, translate backslashes first so a Windows-style
|
|
111
|
+
# `.claude\hooks\..\settings.json` is rejected at the raw stage too (the
|
|
112
|
+
# normalized form also catches it — this is defense in depth).
|
|
113
|
+
RAW_PATH_SLASHED=$(printf '%s' "$FILE_PATH" | tr '\\\\' '/')
|
|
114
|
+
raw_has_traversal=0
|
|
115
|
+
case "/$RAW_PATH_SLASHED/" in
|
|
116
|
+
*/../*) raw_has_traversal=1 ;;
|
|
117
|
+
esac
|
|
118
|
+
norm_has_traversal=0
|
|
119
|
+
case "/$NORMALIZED/" in
|
|
120
|
+
*/../*) norm_has_traversal=1 ;;
|
|
121
|
+
esac
|
|
122
|
+
if [[ "$raw_has_traversal" -eq 1 ]] || [[ "$norm_has_traversal" -eq 1 ]]; then
|
|
123
|
+
{
|
|
124
|
+
printf 'SETTINGS PROTECTION: path traversal rejected\n'
|
|
125
|
+
printf '\n'
|
|
126
|
+
printf ' File: %s\n' "$SAFE_FILE_PATH"
|
|
127
|
+
printf " Rule: path contains a '..' segment; rewrite to a canonical\n"
|
|
128
|
+
printf ' project-relative path without traversal.\n'
|
|
129
|
+
} >&2
|
|
130
|
+
exit 2
|
|
131
|
+
fi
|
|
76
132
|
|
|
77
133
|
# ── 6. Protected path patterns ────────────────────────────────────────────────
|
|
134
|
+
# §6 runs BEFORE the patch-session allowlist so hook-patch sessions cannot
|
|
135
|
+
# reach .rea/policy.yaml, .rea/HALT, or .claude/settings.json via any glob
|
|
136
|
+
# creativity.
|
|
78
137
|
PROTECTED_PATTERNS=(
|
|
79
138
|
'.claude/settings.json'
|
|
80
139
|
'.claude/settings.local.json'
|
|
81
|
-
'.claude/hooks/'
|
|
82
140
|
'.husky/'
|
|
83
141
|
'.rea/policy.yaml'
|
|
84
142
|
'.rea/HALT'
|
|
85
143
|
)
|
|
86
144
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
printf ' File: %s\n' "$FILE_PATH"
|
|
94
|
-
printf ' Rule: This file is protected from agent modification.\n'
|
|
95
|
-
printf '\n'
|
|
96
|
-
printf ' Protected files include hook scripts, settings, policy,\n'
|
|
97
|
-
printf ' and kill switch files. These must be modified by humans\n'
|
|
98
|
-
printf ' via rea CLI or direct editing.\n'
|
|
99
|
-
printf '\n'
|
|
100
|
-
printf ' Use: rea init (to update hooks/settings)\n'
|
|
101
|
-
printf ' rea freeze/unfreeze (for HALT file)\n'
|
|
102
|
-
printf ' Edit .rea/policy.yaml manually\n'
|
|
103
|
-
} >&2
|
|
104
|
-
exit 2
|
|
105
|
-
fi
|
|
106
|
-
|
|
107
|
-
# Directory prefix match (patterns ending in /)
|
|
108
|
-
if [[ "$pattern" == */ ]] && [[ "$NORMALIZED" == "$pattern"* ]]; then
|
|
109
|
-
{
|
|
110
|
-
printf 'SETTINGS PROTECTION: Modification blocked\n'
|
|
111
|
-
printf '\n'
|
|
112
|
-
printf ' File: %s\n' "$FILE_PATH"
|
|
113
|
-
printf ' Rule: Files under %s are protected from agent modification.\n' "$pattern"
|
|
114
|
-
printf '\n'
|
|
115
|
-
printf ' These files control the hook safety layer and must be\n'
|
|
116
|
-
printf ' modified by humans via rea CLI or direct editing.\n'
|
|
117
|
-
} >&2
|
|
118
|
-
exit 2
|
|
119
|
-
fi
|
|
120
|
-
done
|
|
145
|
+
# Patterns that are protected from general agent edits but can be unlocked by
|
|
146
|
+
# REA_HOOK_PATCH_SESSION. Kept separate from the hard-protected list above so
|
|
147
|
+
# the patch-session gate in §6b only applies to these directories.
|
|
148
|
+
PATCH_SESSION_PATTERNS=(
|
|
149
|
+
'.claude/hooks/'
|
|
150
|
+
)
|
|
121
151
|
|
|
122
|
-
# ── 7. Case-insensitive fallback check ────────────────────────────────────────
|
|
123
|
-
# Catch case-manipulation bypass attempts (e.g., .Claude/Settings.json)
|
|
124
152
|
LOWER_NORM=$(printf '%s' "$NORMALIZED" | tr '[:upper:]' '[:lower:]')
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
153
|
+
|
|
154
|
+
# Match $NORMALIZED against PROTECTED_PATTERNS (exact or prefix for patterns
|
|
155
|
+
# ending in '/'). Sets $PROTECTED_MATCH to the matched pattern; exit 0 on hit.
|
|
156
|
+
match_protected() {
|
|
157
|
+
local pattern
|
|
158
|
+
PROTECTED_MATCH=""
|
|
159
|
+
for pattern in "${PROTECTED_PATTERNS[@]}"; do
|
|
160
|
+
if [[ "$NORMALIZED" == "$pattern" ]]; then
|
|
161
|
+
PROTECTED_MATCH="$pattern"
|
|
162
|
+
return 0
|
|
163
|
+
fi
|
|
164
|
+
if [[ "$pattern" == */ ]] && [[ "$NORMALIZED" == "$pattern"* ]]; then
|
|
165
|
+
PROTECTED_MATCH="$pattern"
|
|
166
|
+
return 0
|
|
167
|
+
fi
|
|
168
|
+
done
|
|
169
|
+
return 1
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
match_protected_ci() {
|
|
173
|
+
local pattern lp
|
|
174
|
+
PROTECTED_MATCH=""
|
|
175
|
+
for pattern in "${PROTECTED_PATTERNS[@]}"; do
|
|
176
|
+
lp=$(printf '%s' "$pattern" | tr '[:upper:]' '[:lower:]')
|
|
177
|
+
if [[ "$LOWER_NORM" == "$lp" ]]; then
|
|
178
|
+
PROTECTED_MATCH="$pattern"
|
|
179
|
+
return 0
|
|
180
|
+
fi
|
|
181
|
+
if [[ "$lp" == */ ]] && [[ "$LOWER_NORM" == "$lp"* ]]; then
|
|
182
|
+
PROTECTED_MATCH="$pattern"
|
|
183
|
+
return 0
|
|
184
|
+
fi
|
|
185
|
+
done
|
|
186
|
+
return 1
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
match_patch_session() {
|
|
190
|
+
local pattern
|
|
191
|
+
PROTECTED_MATCH=""
|
|
192
|
+
for pattern in "${PATCH_SESSION_PATTERNS[@]}"; do
|
|
193
|
+
if [[ "$NORMALIZED" == "$pattern" ]]; then
|
|
194
|
+
PROTECTED_MATCH="$pattern"
|
|
195
|
+
return 0
|
|
196
|
+
fi
|
|
197
|
+
if [[ "$pattern" == */ ]] && [[ "$NORMALIZED" == "$pattern"* ]]; then
|
|
198
|
+
PROTECTED_MATCH="$pattern"
|
|
199
|
+
return 0
|
|
200
|
+
fi
|
|
201
|
+
done
|
|
202
|
+
return 1
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
match_patch_session_ci() {
|
|
206
|
+
local pattern lp
|
|
207
|
+
PROTECTED_MATCH=""
|
|
208
|
+
for pattern in "${PATCH_SESSION_PATTERNS[@]}"; do
|
|
209
|
+
lp=$(printf '%s' "$pattern" | tr '[:upper:]' '[:lower:]')
|
|
210
|
+
if [[ "$LOWER_NORM" == "$lp" ]]; then
|
|
211
|
+
PROTECTED_MATCH="$pattern"
|
|
212
|
+
return 0
|
|
213
|
+
fi
|
|
214
|
+
if [[ "$lp" == */ ]] && [[ "$LOWER_NORM" == "$lp"* ]]; then
|
|
215
|
+
PROTECTED_MATCH="$pattern"
|
|
216
|
+
return 0
|
|
217
|
+
fi
|
|
218
|
+
done
|
|
219
|
+
return 1
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if match_protected; then
|
|
223
|
+
{
|
|
224
|
+
printf 'SETTINGS PROTECTION: Modification blocked\n'
|
|
225
|
+
printf '\n'
|
|
226
|
+
printf ' File: %s\n' "$SAFE_FILE_PATH"
|
|
227
|
+
printf ' Matched: %s\n' "$PROTECTED_MATCH"
|
|
228
|
+
printf ' Rule: This file is protected from agent modification, including\n'
|
|
229
|
+
printf ' sessions with REA_HOOK_PATCH_SESSION set.\n'
|
|
230
|
+
} >&2
|
|
231
|
+
exit 2
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
if match_protected_ci; then
|
|
235
|
+
{
|
|
236
|
+
printf 'SETTINGS PROTECTION: Modification blocked (case-insensitive match)\n'
|
|
237
|
+
printf '\n'
|
|
238
|
+
printf ' File: %s\n' "$SAFE_FILE_PATH"
|
|
239
|
+
printf ' Matched: %s\n' "$PROTECTED_MATCH"
|
|
240
|
+
} >&2
|
|
241
|
+
exit 2
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
# ── 6b. Hook-patch session (Defect I / rea#76) ───────────────────────────────
|
|
245
|
+
# When REA_HOOK_PATCH_SESSION is set to a non-empty reason, allow edits under
|
|
246
|
+
# .claude/hooks/ and hooks/ for this session. The session boundary IS the
|
|
247
|
+
# expiry — a new shell requires a fresh opt-in. Every allowed edit is audited
|
|
248
|
+
# as hooks.patch.session so the bypass is never silent.
|
|
249
|
+
#
|
|
250
|
+
# SECURITY: runs AFTER §5a (traversal reject) and §6 (hard-protected denies),
|
|
251
|
+
# so no glob creativity can reach policy/HALT/settings files from here.
|
|
252
|
+
if [[ -n "${REA_HOOK_PATCH_SESSION:-}" ]]; then
|
|
253
|
+
if match_patch_session; then
|
|
254
|
+
SAFE_REASON=$(sanitize_for_stderr "${REA_HOOK_PATCH_SESSION}")
|
|
255
|
+
# Audit record via the TypeScript chain so the hash chain stays intact.
|
|
256
|
+
# If the append fails, block the edit — silent failure would let an
|
|
257
|
+
# attacker disable audit logging and then patch hooks unobserved.
|
|
258
|
+
SHA_BEFORE=""
|
|
259
|
+
if [[ -f "$FILE_PATH" ]]; then
|
|
260
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
261
|
+
SHA_BEFORE=$(sha256sum "$FILE_PATH" 2>/dev/null | awk '{print $1}')
|
|
262
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
263
|
+
SHA_BEFORE=$(shasum -a 256 "$FILE_PATH" 2>/dev/null | awk '{print $1}')
|
|
264
|
+
elif command -v openssl >/dev/null 2>&1; then
|
|
265
|
+
SHA_BEFORE=$(openssl dgst -sha256 "$FILE_PATH" 2>/dev/null | awk '{print $NF}')
|
|
266
|
+
fi
|
|
267
|
+
fi
|
|
268
|
+
ACTOR_NAME=$(git -C "$REA_ROOT" config user.name 2>/dev/null || printf 'unknown')
|
|
269
|
+
ACTOR_EMAIL=$(git -C "$REA_ROOT" config user.email 2>/dev/null || printf 'unknown')
|
|
270
|
+
|
|
271
|
+
AUDIT_PAYLOAD=$(
|
|
272
|
+
cd "$REA_ROOT" 2>/dev/null || true
|
|
273
|
+
REA_AUDIT_REASON="${REA_HOOK_PATCH_SESSION}" \
|
|
274
|
+
REA_AUDIT_FILE="$NORMALIZED" \
|
|
275
|
+
REA_AUDIT_SHA="$SHA_BEFORE" \
|
|
276
|
+
REA_AUDIT_ACTOR_NAME="$ACTOR_NAME" \
|
|
277
|
+
REA_AUDIT_ACTOR_EMAIL="$ACTOR_EMAIL" \
|
|
278
|
+
REA_AUDIT_PID="$$" \
|
|
279
|
+
REA_AUDIT_PPID="$PPID" \
|
|
280
|
+
REA_AUDIT_SESSION="${CLAUDE_SESSION_ID:-external}" \
|
|
281
|
+
REA_AUDIT_ROOT="$REA_ROOT" \
|
|
282
|
+
node --input-type=module -e '
|
|
283
|
+
const root = process.env.REA_AUDIT_ROOT;
|
|
284
|
+
async function loadMod() {
|
|
285
|
+
// Consumer path: `@bookedsolid/rea` resolvable via node_modules
|
|
286
|
+
// (how `rea init`-installed consumers reach the published package)
|
|
287
|
+
// or via package self-reference when the hook runs inside the rea
|
|
288
|
+
// source repo itself.
|
|
289
|
+
try {
|
|
290
|
+
return await import("@bookedsolid/rea/audit");
|
|
291
|
+
} catch (e1) {
|
|
292
|
+
// Dev path: direct file import from the source repos dist/.
|
|
293
|
+
try {
|
|
294
|
+
return await import(root + "/dist/audit/append.js");
|
|
295
|
+
} catch (e2) {
|
|
296
|
+
process.stderr.write(
|
|
297
|
+
"audit import failed: package=" + (e1 && e1.message ? e1.message : e1) +
|
|
298
|
+
"; dist=" + (e2 && e2.message ? e2.message : e2) + "\n");
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
(async () => {
|
|
304
|
+
const mod = await loadMod();
|
|
305
|
+
try {
|
|
306
|
+
await mod.appendAuditRecord(root, {
|
|
307
|
+
session_id: process.env.REA_AUDIT_SESSION,
|
|
308
|
+
tool_name: "hooks.patch.session",
|
|
309
|
+
server_name: "rea",
|
|
310
|
+
tier: "write",
|
|
311
|
+
status: "allowed",
|
|
312
|
+
autonomy_level: "unknown",
|
|
313
|
+
duration_ms: 0,
|
|
314
|
+
metadata: {
|
|
315
|
+
reason: process.env.REA_AUDIT_REASON,
|
|
316
|
+
file: process.env.REA_AUDIT_FILE,
|
|
317
|
+
sha_before: process.env.REA_AUDIT_SHA,
|
|
318
|
+
actor: {
|
|
319
|
+
name: process.env.REA_AUDIT_ACTOR_NAME,
|
|
320
|
+
email: process.env.REA_AUDIT_ACTOR_EMAIL,
|
|
321
|
+
},
|
|
322
|
+
pid: Number(process.env.REA_AUDIT_PID),
|
|
323
|
+
ppid: Number(process.env.REA_AUDIT_PPID),
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
process.exit(0);
|
|
327
|
+
} catch (e) {
|
|
328
|
+
process.stderr.write("audit append failed: " + (e && e.message ? e.message : e) + "\n");
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
})();
|
|
332
|
+
' 2>&1
|
|
333
|
+
)
|
|
334
|
+
AUDIT_EXIT=$?
|
|
335
|
+
if [[ "$AUDIT_EXIT" -ne 0 ]]; then
|
|
336
|
+
# Fail closed. We deliberately do NOT fall back to a raw `jq … >> audit`
|
|
337
|
+
# write: that path skips prev_hash/hash computation and would silently
|
|
338
|
+
# degrade the hash-chain integrity the rest of REA (and `rea audit verify`)
|
|
339
|
+
# relies on. If the TypeScript chain is unavailable (no `dist/`, missing
|
|
340
|
+
# Node, broken import), refuse the hook-patch edit and surface why. The
|
|
341
|
+
# operator resolves by building the package (`pnpm build`) or running
|
|
342
|
+
# against a published install that ships `dist/`.
|
|
343
|
+
{
|
|
344
|
+
printf 'SETTINGS PROTECTION: audit-append failed; refusing hook-patch edit\n'
|
|
345
|
+
printf ' File: %s\n' "$SAFE_FILE_PATH"
|
|
346
|
+
printf ' Rule: hash-chained audit is required; no raw-jq fallback.\n'
|
|
347
|
+
printf ' Detail: %s\n' "$(sanitize_for_stderr "$AUDIT_PAYLOAD")"
|
|
348
|
+
} >&2
|
|
349
|
+
exit 2
|
|
350
|
+
fi
|
|
351
|
+
printf 'REA_HOOK_PATCH_SESSION: allowing edit to %s (reason: %s)\n' \
|
|
352
|
+
"$SAFE_NORMALIZED" "$SAFE_REASON" >&2
|
|
353
|
+
exit 0
|
|
144
354
|
fi
|
|
145
|
-
|
|
355
|
+
fi
|
|
356
|
+
|
|
357
|
+
# ── 6c. Patch-session patterns are still blocked when env var is NOT set ─────
|
|
358
|
+
if match_patch_session; then
|
|
359
|
+
{
|
|
360
|
+
printf 'SETTINGS PROTECTION: Modification blocked\n'
|
|
361
|
+
printf '\n'
|
|
362
|
+
printf ' File: %s\n' "$SAFE_FILE_PATH"
|
|
363
|
+
printf ' Matched: %s\n' "$PROTECTED_MATCH"
|
|
364
|
+
printf ' Rule: Files under this path are protected. To apply an upstream\n'
|
|
365
|
+
printf ' hook finding, set REA_HOOK_PATCH_SESSION=<reason> and retry.\n'
|
|
366
|
+
} >&2
|
|
367
|
+
exit 2
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
if match_patch_session_ci; then
|
|
371
|
+
{
|
|
372
|
+
printf 'SETTINGS PROTECTION: Modification blocked (case-insensitive match)\n'
|
|
373
|
+
printf '\n'
|
|
374
|
+
printf ' File: %s\n' "$SAFE_FILE_PATH"
|
|
375
|
+
printf ' Matched: %s\n' "$PROTECTED_MATCH"
|
|
376
|
+
} >&2
|
|
377
|
+
exit 2
|
|
378
|
+
fi
|
|
146
379
|
|
|
147
380
|
exit 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bookedsolid/rea",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
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)",
|