Skip to content

Commit 1b973ef

Browse files
Maleickclaude
andcommitted
security: fix TOCTOU temp files + add path canonicalization in verify pipeline
Finding 8: chmod 600 temp files immediately after mktemp in beacon-init.sh and update-state.sh — closes the TOCTOU window between file creation and write on shared systems (CI runners, multi-user hosts). Finding 9: add Step 0.5 pre-verification guards to beacon-verify skill: - Assert BEACON_RESULT_PATH is canonically inside WORKTREE_PATH (symlink escape) - Assert git diff main...HEAD is non-empty before spawning reviewer — mandatory FAIL if agent reported COMPLETE without committing any changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4b4d44f commit 1b973ef

3 files changed

Lines changed: 33 additions & 0 deletions

File tree

hooks/beacon-init.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ init_token_ledger() {
205205
# without quoting/escaping issues.
206206
local session_tmp
207207
session_tmp=$(mktemp)
208+
chmod 600 "$session_tmp" # close TOCTOU window before writing session data
208209
printf '%s' "$new_session" > "$session_tmp"
209210
# Pass paths as positional args ($1, $2) to avoid injection from special chars in paths
210211
lockf -k "$lock" bash -c '

hooks/update-state.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ append_ledger_record() {
165165
elif command -v lockf >/dev/null 2>&1; then
166166
local record_tmp
167167
record_tmp=$(mktemp)
168+
chmod 600 "$record_tmp" # close TOCTOU window before writing record data
168169
printf '%s' "$record" > "$record_tmp"
169170
# Pass paths as positional args ($1, $2) to avoid shell injection from special chars in paths
170171
lockf -k "$lock" bash -c '

skills/beacon-verify/SKILL.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,37 @@ Before verification, determine the repo's test command. Check in order:
2222

2323
Cache the discovered command in `.beacon/state.json` under `test_command` so subsequent verifications skip discovery.
2424

25+
## Step 0.5: Pre-Verification Guards
26+
27+
Before spawning the reviewer, run these assertions. Any failure → immediate FAIL, no reviewer spawned.
28+
29+
**1. Path canonicalization** — assert `BEACON_RESULT_PATH` is inside `WORKTREE_PATH`:
30+
31+
```bash
32+
REAL_RESULT=$(realpath "$BEACON_RESULT_PATH" 2>/dev/null)
33+
REAL_WORKTREE=$(realpath "$WORKTREE_PATH" 2>/dev/null)
34+
if [[ -z "$REAL_RESULT" || "$REAL_RESULT" != "$REAL_WORKTREE"/* ]]; then
35+
echo "VERDICT: FAIL — BEACON_RESULT_PATH is outside worktree (possible symlink escape)"
36+
# Mark issue blocked, skip to escalation
37+
fi
38+
```
39+
40+
This prevents a worker agent from writing `BEACON_RESULT.md` as a symlink pointing outside the worktree.
41+
42+
**2. Non-empty diff** — assert the agent actually made changes:
43+
44+
```bash
45+
DIFF_OUTPUT=$(git -C "$WORKTREE_PATH" diff main...HEAD 2>/dev/null)
46+
if [[ -z "$DIFF_OUTPUT" ]]; then
47+
echo "VERDICT: FAIL — git diff main...HEAD is empty. No changes were committed."
48+
# Mark issue blocked, skip to escalation
49+
fi
50+
```
51+
52+
An empty diff means the agent reported COMPLETE without committing anything. The reviewer **must not** be allowed to PASS an empty diff — this guard is mandatory and cannot be overridden by the result file contents.
53+
54+
---
55+
2556
## Step 1: Initial Verification
2657

2758
Spawn the `beacon-reviewer` agent with these structured variables:

0 commit comments

Comments
 (0)