fix(guardrails): block heredoc-opener stdout redirect in block-hook-bypass - #136
Conversation
…k-bypass strip_literals truncated the opener line at '<<', dropping a trailing stdout redirect (cat <<EOF > file) before the redirect scan — a real file-write bypass leaked (exit 0). Preserve the text after the heredoc delimiter token so the redirect still reaches _cat_redir/_echo_redir. Regression tests: opener redirect blocks (exit 2); plain heredoc with no redirect stays allowed (exit 0). Patch guardrails 0.3.1 -> 0.3.2.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_9ae6746f-af56-4baa-a1d7-b8262683a343) |
There was a problem hiding this comment.
Stale comment
Risk: medium. Cursor Bugbot did not complete successfully (check skipped; usage limit reached), so automated review is incomplete and human review is needed before approval. No reviewers were assigned — the only human contributor is the PR author.
Sent by Cursor Approval Agent: Pull Request Router and Approver
|
Claude finished @kyle-sexton's task in 1m 50s —— View job PR Review
SummaryThe fix is correct and well-scoped. The bug was real: Fix logic —
|
| Case | Expected |
|---|---|
cat <<EOF > real.txt + body + EOF |
exit 2 (blocked) ✓ |
cat <<EOF | cat + body + EOF |
exit 0 (allowed) ✓ |
Minor gap to be aware of (not blocking): echo <<EOF > file (the echo+heredoc+redirect path) isn't covered by an explicit test. Given that echo <<EOF > file is syntactically unusual in practice and the echo redirect path already has thorough coverage elsewhere in the suite, this is a low-priority omission — but worth a follow-up if this plugin gets more adversarial testing.
Version bump
0.3.1 → 0.3.2 is the correct semver for a patch bug fix. ✓
Verdict
LGTM. The fix is minimal, mechanically correct, well-commented, and regression-tested. No changes requested.
A redirect glued to the delimiter (cat <<EOF>real.txt, no space) was swallowed — the greedy delimiter capture absorbed EOF>real.txt, so no > survived for the redirect scan (leaked, exit 0). Exclude > (keep < excluded) from the delimiter body so bash's own word-boundary at > is honored. Adds glued-form, tab-strip (<<-EOF), and quoted-delimiter (<<'EOF') opener redirect regressions (37 cases, 0 fail).
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_2623cb5b-45b0-4d1b-81fc-a5e84447160c) |
|
Claude finished @kyle-sexton's task in 1m 36s —— View job PR Review
SummaryThis PR now spans two commits, each closing a distinct heredoc-redirect bypass variant. Both fixes are correct and well-targeted.
First commit (00b59d9) — spaced redirectAlready reviewed in depth previously. Fix: replace Second commit (9d871a8) — glued redirectThe bug: The old delimiter character class The fix: Change the character class to Trace through
Edge case verification:
TestsThree new cases added in
37 total cases, 0 failures per commit message. Suite now covers the full matrix of spaced/glued/tab-strip/quoted opener redirect forms. Still untested (non-blocking, same note as before): Version bump
VerdictLGTM. Both commits are minimal, mechanically correct, and well-tested. The glued-redirect variant ( |
There was a problem hiding this comment.
Stale comment
Risk: medium. Cursor Bugbot did not complete successfully (check skipped), so automated review is incomplete and human review is needed before approval. No reviewers were assigned because the only human contributor is the PR author.
Sent by Cursor Approval Agent: Pull Request Router and Approver
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_22d9685e-5054-451e-b0f8-3ec55a9dbafc) |
|
Claude finished @kyle-sexton's task in 2m 2s —— View job PR Review
SummaryTwo related but distinct heredoc-redirect bypass paths are closed in this PR. Both fixes are correct, minimal, and directly targeted at the real bypass vectors. No issues requiring changes. Fix 1 — Delimiter regex (
|
| Input form | Result | Verdict |
|---|---|---|
cat <<-EOF > file |
cat > file |
blocked ✓ |
cat <<'EOF' > file |
cat > file |
blocked ✓ |
cat <<\EOF > file |
cat > file |
blocked ✓ |
cat <<EOF>file |
cat >file |
blocked ✓ |
cat <<EOF 2>err.txt |
cat 2>err.txt |
allowed ✓ (_cat_redir requires cat[[:space:]]*> — the 2 between spaces and > breaks the match) |
Test coverage (block-hook-bypass.test.sh L100–116)
Five new cases cover the regression matrix:
| Test case | Expected | Regression for |
|---|---|---|
cat <<EOF > real.txt |
exit 2 | spaced redirect on opener (Fix 2) |
cat <<EOF | cat |
exit 0 | control — no redirect |
cat <<EOF>real.txt |
exit 2 | glued redirect (Fix 1 + 2 combined) |
cat <<-EOF > real.txt |
exit 2 | tab-strip operator |
cat <<'EOF' > real.txt |
exit 2 | quoted delimiter |
One non-blocking gap: echo <<EOF > file (echo + heredoc + redirect) has no explicit test. In practice, echo <<EOF is syntactically unusual and the echo-redirect path already has thorough standalone coverage. Low priority, but worth noting for adversarial-test completeness.
Version bump
0.3.1 → 0.3.2 is the correct semver increment for a patch bug fix. ✓
Verdict
LGTM. Both bypass paths are closed with the minimum necessary changes, the regex change correctly mirrors Bash's actual shell word boundary rules, the string extraction logic is mechanically sound, and the new tests directly exercise the regression cases. No changes requested.



What
block-hook-bypass'sstrip_literalstruncated the heredoc opener line at<<(line="${line%%<<*}"), so a stdout redirect carried on the same line —cat <<EOF > generated.txt— was dropped before the redirect scan, letting a real file-write bypass through (hook exited 0).Fix: drop only the heredoc operator + delimiter token, preserving the text before
<<and any text after the delimiter, so a trailing> filestill reaches_cat_redir/_echo_redir.Tests
Added two contract cases:
cat <<EOF > real.txt(+ body + terminator) → exit 2 (blocked)cat <<EOF | cat(heredoc, no redirect) → exit 0 (allowed)Full suite green (34/34);
shellcheckclean;claude plugin validate plugins/guardrailspasses. Patched guardrails0.3.1→0.3.2.Scope
Only the redirect-after-heredoc-operator opener form. The documented friction-guard floor (explicit-fd redirects, combined
&>, args-before-redirect, etc.) stays accepted per the issue.Refs melodic-software/medley#1461
Note
Medium Risk
Security-sensitive PreToolUse hook logic changed to close a real bypass; scope is narrow with new contract tests, but incorrect stripping could still miss or over-block Bash commands.
Overview
Closes a Bash hook-bypass hole in
strip_literals: truncating the heredoc opener at<<dropped same-line stdout redirects (cat <<EOF > file), socat-redirect detection never ran.strip_literalsnow removes only the<<operator and delimiter token and keeps any suffix on that line (e.g.> file). The heredoc delimiter regex stops at>so forms likecat <<EOF>filestill expose the redirect to the scan. Guardrails plugin version 0.3.1 → 0.3.2.Contract tests cover opener redirects (spaced, glued, tab-strip, quoted delimiter) and confirm plain heredocs without redirects stay allowed.
Reviewed by Cursor Bugbot for commit 9d871a8. Bugbot is set up for automated code reviews on this repo. Configure here.