Skip to content

fix: close the residual bypasses behind issues #52 and #49 - #56

Merged
ppgranger merged 1 commit into
mainfrom
fix/issue-52-marker-boundary
Aug 9, 2026
Merged

fix: close the residual bypasses behind issues #52 and #49#56
ppgranger merged 1 commit into
mainfrom
fix/issue-52-marker-boundary

Conversation

@ppgranger

@ppgranger ppgranger commented Aug 9, 2026

Copy link
Copy Markdown
Owner

e28d083 patched the literal PoC strings from both issues but not the underlying bug class. Both were still exploitable with a one-character variation on the original reports.

#52: _MARKER_LEFT_BOUNDARY in wrap.py used (?<=\S), which still glued the next chain marker onto a segment ending in whitespace (trailing space, tab, or a blank/indent-only line), leaking the raw marker into output and collapsing segment attribution exactly as originally reported. Changed to (?<=[^\n]) to cover every same-line character.

#49: iter_unquoted (src/shell_syntax.py) only honored backslash escapes inside double-quoted regions. An unquoted " or ' — an ordinary, well-formed shell token — was misread as opening a quoted region that never closes, so the scanner swallowed everything after it, including a smuggled newline or &. This blinded has_unquoted_newline, has_unquoted_background_operator, has_output_redirection, and the dangerous-construct check simultaneously, since they all share this scanner. Fixed at the source: a backslash outside quotes is now consumed as a literal escape pair before quote detection.

Also closes the issue's three remaining sub-claims:

  • re.MULTILINE added to both exclusion pattern lists (defense in depth)
  • wrap.py now re-validates is_compressible() itself before applying its chain-rewrite, instead of trusting the hook's verdict unconditionally
  • a new is_destructive() check withholds permissionDecision: allow for irreversible commands (force-push, git reset --hard, kubectl delete, terraform apply/destroy, docker rm/rmi/system prune, rm -rf), so they fall through to Claude Code's normal permission flow instead of being auto-approved

New tests/test_shell_syntax.py covers iter_unquoted and its consumers directly. tests/test_hooks.py gains regression coverage for the marker boundary, the classifier bypass, wrap.py's re-validation, and is_destructive(). Full suite: 1364 passed, 0 failed.

What

Why

How

Checklist

  • ruff check . passes
  • ruff format --check . passes
  • python3 -m pytest tests/ -v passes (all 207+ tests)
  • New code has tests (if applicable)
  • No secrets or credentials in the diff

e28d083 patched the literal PoC strings from both issues but not the
underlying bug class. Both were still exploitable with a one-character
variation on the original reports.

#52: _MARKER_LEFT_BOUNDARY in wrap.py used (?<=\S), which still glued the
next chain marker onto a segment ending in whitespace (trailing space, tab,
or a blank/indent-only line), leaking the raw marker into output and
collapsing segment attribution exactly as originally reported. Changed to
(?<=[^\n]) to cover every same-line character.

#49: iter_unquoted (src/shell_syntax.py) only honored backslash escapes
inside double-quoted regions. An unquoted " or \' — an ordinary, well-formed
shell token — was misread as opening a quoted region that never closes, so
the scanner swallowed everything after it, including a smuggled newline or
&. This blinded has_unquoted_newline, has_unquoted_background_operator,
has_output_redirection, and the dangerous-construct check simultaneously,
since they all share this scanner. Fixed at the source: a backslash outside
quotes is now consumed as a literal escape pair before quote detection.

Also closes the issue's three remaining sub-claims:
- re.MULTILINE added to both exclusion pattern lists (defense in depth)
- wrap.py now re-validates is_compressible() itself before applying its
  chain-rewrite, instead of trusting the hook's verdict unconditionally
- a new is_destructive() check withholds permissionDecision: allow for
  irreversible commands (force-push, git reset --hard, kubectl delete,
  terraform apply/destroy, docker rm/rmi/system prune, rm -rf), so they
  fall through to Claude Code's normal permission flow instead of being
  auto-approved

New tests/test_shell_syntax.py covers iter_unquoted and its consumers
directly. tests/test_hooks.py gains regression coverage for the marker
boundary, the classifier bypass, wrap.py's re-validation, and
is_destructive(). Full suite: 1364 passed, 0 failed.
@ppgranger
ppgranger merged commit 1d4cd69 into main Aug 9, 2026
7 checks passed
@ppgranger
ppgranger deleted the fix/issue-52-marker-boundary branch August 9, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant