Skip to content

fix(guardrails): fail closed on over-length Bash before tokenize; linear parse (#4528) - #4699

Merged
cursor[bot] merged 12 commits into
mainfrom
cursor/4528-guard-overlength-fastpath-37e9
Sep 27, 2026
Merged

cursor[bot] merged 12 commits into
mainfrom
cursor/4528-guard-overlength-fastpath-37e9

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Fixes #4528

Summary

Bash|PowerShell PreToolUse fails closed on commands over 16384 chars before any guard tokenizes; parse-once share across the row; tokenizer made linear. 70KB heredoc: main ~133s (fail-open past 60s timeout) → branch ~61ms block.

Vendors hook-utils into carrier plugins (patch bumps). guardrails → 0.37.3.

Sources

Test plan

  • run-guards / tokenizer corpus / row timings (per implementer)

Notes

Large multi-plugin bump — serialize vs other open guardrails/hook-utils PRs. Related follow-ups #4684 et al. in flight.

Open in Web Open in Cursor 

cursoragent and others added 5 commits September 27, 2026 21:32
…and parse once, linearly (#4528)

The Bash|PowerShell PreToolUse row ran past its 60-second hooks.json
timeout on a long command, and Claude Code lets a timed-out PreToolUse
command hook's tool call proceed, so the command passed every guard
unchecked. On main: 7.23 s at 10 KB, 12.6 s at 16 KB, still running at
120 s for ~70 KB. Now 132 ms, 203 ms and 64 ms.

- lib/hook-utils.sh: hook::bash_parse_segments split the command with
  one ${cmd:i:1} per character, which bash answers by measuring the whole
  string, so every parse was quadratic. It now splits in 4096- and 64-byte
  blocks under the C locale; output is byte-identical. The body moves to
  hook::bash_parse_segments_uncached behind a thin wrapper (the
  hook::jq_fields split). Synced to the 17 carriers, each patch-bumped.
- run-guards.sh: --max-command-len <n> ends the chain at the first guard
  that blocks a command longer than <n>. The Bash row passes 16384, the
  MAX_COMMAND_LEN five of its guards already refuse unread; block-no-verify
  runs first. Kill switches are unchanged: a disabled guard exits before
  its ceiling and the next one blocks.
- run-guards.sh: the event's command is tokenized once and the record is
  replayed to every later guard that parses it; a parse cut short by a
  guard's exit is not kept.

Tests: run-guards.test.sh covers the short-circuit (stub and shipped row,
kill switch, unprimed payload, boundary, bad value), the row/guard ceiling
parity, and the shared parse (equality with a standalone parse, one walk,
cut-short parse). lib/hook-utils.test.sh covers the byte walk, locale
restore, and an 8x-size cost ratio (7x now, 45x on main).

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
…rlength-fastpath-37e9

# Conflicts:
#	plugins/source-control/CHANGELOG.md

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
validate-plugin-contracts forbids the autonomy plugin from naming the
org or fleet repos.

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
… command (#4528)

The 0.37.3 entry said four guards with no ceiling each tokenized the
whole command; flag-commit-pr-skill-bypass never calls the tokenizer and
is off by default. A per-guard profile of main on a 70 KB command shows
block-hook-bypass, block-noncanonical-commit and
block-convention-violation at about 44 s each.

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
…rlength-fastpath-37e9

Resolve the source-control 0.61.3 collision by keeping main's changelog
entry and re-heading this branch's hook-utils change as 0.61.4.

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
@cursor
cursor Bot marked this pull request as ready for review September 27, 2026 22:07
@github-actions

Copy link
Copy Markdown
Contributor

PR body contract — issue linkage

This PR body does not yet satisfy the issue-linkage contract:

  • Missing a "## Fix" section. State the concrete change and how it addresses the problem.
  • Missing a "## Verification" section. Record concrete evidence the change works (commands, gates, output).
  • Missing a "## Related" section. List related PRs, ADRs, or decision-log entries this PR does not close.

Edit the body and this comment updates itself on the next run.

cursoragent and others added 2 commits September 27, 2026 22:50
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
Shellcheck info SC2030/SC2031 flags LC_ALL assignments whose subshell is the
point of the test, and the portability gate flags a grep -F fixture token.

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
cursor Bot pushed a commit that referenced this pull request Sep 27, 2026
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
cursor Bot pushed a commit that referenced this pull request Sep 27, 2026
)

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
cursor Bot pushed a commit that referenced this pull request Sep 27, 2026
…in and open PR #4699 (#4651)

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
cursoragent and others added 4 commits September 27, 2026 23:07
The DLSS selftest's user-only Set-Acl deny still lets an elevated runner
list the folder, and Windows often leaves the error TargetObject empty, so
the four unreadable-folder assertions never match.

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
DirectoryInfo.SetAccessControl is absent in PowerShell 7, so the Windows
selftest threw before the deny cases ran. Write the same user,
Administrators, and Everyone list deny through FileSystemAclExtensions.

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
A Deny ACE, including one for Everyone, still lets the elevated Windows
runner list the folder. Hold the directory open with no sharing so the
next Get-ChildItem fails with a sharing violation, and recover a quoted
path from the error message when TargetObject is empty.

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
The runner's pwsh has no FileOptions.BackupSemantics, so the selftest
threw before the sharing lock was taken. Open the directory with
CreateFileW and FILE_FLAG_BACKUP_SEMANTICS instead.

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
cursor Bot pushed a commit that referenced this pull request Sep 27, 2026
… act on (#4679) (#4711)

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
Closes #4679

**Stacked on #4699** (`cursor/4528-guard-overlength-fastpath-37e9`).
Until that merges, the diff against `main` includes its commits; review
the commits after `d6963e5c1`.

## Summary

`block-hook-bypass` refusals drop operator lever spam and wrong
scratch-root advice on PowerShell/python. stderr carries only actionable
lines; levers go to one `systemMessage` per session/agent (latched via
`HOOK_NOTICE_KIND == full`). README keeps the write-forms list. One-line
README pointer remains until a human confirms exit-2 `systemMessage`
renders.

`guardrails` → 0.38.0 (stacked above 0.37.3 from #4699).

## Test plan

- [x] block-hook-bypass 699/0 outside `/tmp` (per implementer)
- [ ] Interactive: confirm `systemMessage` shows on exit-2 block
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-ab53da24-b89d-4314-a060-0da474e837e9?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-ab53da24-b89d-4314-a060-0da474e837e9&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
# Conflicts:
#	plugins/guardrails/.claude-plugin/plugin.json
#	plugins/guardrails/CHANGELOG.md

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
@cursor
cursor Bot merged commit cb0eb02 into main Sep 27, 2026
19 checks passed
@cursor
cursor Bot deleted the cursor/4528-guard-overlength-fastpath-37e9 branch September 27, 2026 23:57
cursor Bot pushed a commit that referenced this pull request Sep 28, 2026
…eck (#4680) (#4709)

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
Closes #4680

## Summary

`hardcoded-path-check` never scanned real `NotebookEdit` calls (reads
`file_path`; Claude Code sends `notebook_path`). Mirrors #4486. Shared
test helper now builds the real payload shape.

`guardrails` → 0.37.3 (serialize vs #4699 / other guardrails bumps).

## Test plan

- [x] hardcoded-path-check 153/0; new cases fail on main (per
implementer)
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-ab53da24-b89d-4314-a060-0da474e837e9?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-ab53da24-b89d-4314-a060-0da474e837e9&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
cursor Bot pushed a commit that referenced this pull request Sep 28, 2026
…e chain (#4684) (#4714)

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
Closes #4684

## Summary

Bash/PowerShell PreToolUse refuses (exit 2) commands with more than 256
command/process substitutions before any guard is sourced.
`run-guards.sh --max-substitutions 256` (text-only count). Timed-out
hooks fail open ([hooks
timeouts](https://code.claude.com/docs/en/hooks#timeouts)).

`guardrails` → **0.37.4** (serialize above #4699 0.37.3; series
4684→4682→4685→4678).

## Test plan

- [x] Issue payloads ~39 ms rc 2; at-cap benign still runs (per
implementer)
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-ab53da24-b89d-4314-a060-0da474e837e9?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-ab53da24-b89d-4314-a060-0da474e837e9&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

guardrails: Bash|PowerShell PreToolUse row takes 30+s on a ~70 KB command; its 60s timeout fails open

2 participants