Skip to content

Block all unix file reads when code-context is installed - #40

Open
ekechinwokah wants to merge 4 commits into
mainfrom
hook/block-grep
Open

Block all unix file reads when code-context is installed#40
ekechinwokah wants to merge 4 commits into
mainfrom
hook/block-grep

Conversation

@ekechinwokah

@ekechinwokah ekechinwokah commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Claude consistently tries to get around code context. Force it. The quality/speed is much better and it hallucinates less.

What's blocked now — everything that puts file bytes in front of me through a shell:

grep family: grep, egrep, fgrep, rg, ag, ack
stream editors: awk, gawk, mawk, nawk, sed, ed
readers and pagers: cat, tac, head, tail, nl, less, more, strings, od, xxd, hexdump
text filters: cut, tr, sort, uniq, paste, join, comm, column, fold, rev, expand, unexpand, jq, yq
interpreters run inline: python3 -c, python3 -, heredoc-to-stdin, node -e, perl -pe, ruby -e

Caught through pipes, wrappers (xargs, env, timeout), env prefixes, absolute paths, and \ alias escapes — so cargo test | tail -20 and TMPDIR=/x awk … both die.

What still works: cargo, make, git (including --grep= flags and commit -F), bash script.sh, python3 script.py, node build.js, ls, cp, mv, mkdir, pgrep, and redirects into files. Building, benching, committing — untouched.

47 cases green (30 denied, 17 allowed), mirrored into the branch's vitest suite. The denial message now points at Read with its offset argument and at the index, and says outright that size is never a reason to filter.

An agent that greps a codebase reasons from match fragments, and
fragments produce confident wrong claims about code it never read. The
plugin now ships a guard: a PreToolUse hook (cx guard --hook) that
denies the Grep tool and grep/egrep/fgrep/rg/git-grep at command
position in Bash - through pipes, env prefixes, wrappers (env, xargs,
command, timeout...), paths, and alias escapes - while leaving argument
uses like 'git log --grep=' alone. bm25_search covers the pinpoint
exact-identifier case with chunks that carry their content.

The decision is a pure function (src/core/guard.ts) with the tokenizer
deliberately coarse: unmodeled quoting can only inspect MORE segments,
so quoting cannot smuggle a grep past it. The hook never fails the
session: unparseable events allow silently, matching cx usage --hook.

Docs updated to match: the 'grep wins pinpoint lookups' guidance is
replaced by the block-and-why. The hooks.json pin (@0.4.0) tracks
.mcp.json and needs the same release-prep bump.
With grep blocked by the guard, log spelunking has to go through the
index too, and the allowlist excluded exactly those files. They chunk
as fixed windows (no grammar), which is what a log wants; the 1MB
CX_MAX_FILE_BYTES cap still applies, so a giant trace is skipped and
flagged partial rather than swallowing the index.
@ekechinwokah ekechinwokah changed the title Block grep when code-context is installed Block grep/awk/sed when code-context is installed Sep 1, 2026
Blocking grep alone moves the behavior rather than stopping it. With
grep denied, an agent reaches for the next line-filter within reach —
awk, sed — prints three matching lines, and answers from those instead
of reading the file or asking the index. The failure being blocked is
reasoning from an extracted fragment, not the name of the program that
extracted it, so the stream editors that make it easy are blocked too.

Editing a file in place belongs to the Edit tool and reading one belongs
to Read or to the ranked index; neither use of awk or sed is lost.
Filtering flags such as the one on git log are unaffected, since
matching is still on the executed program at command position.

Adds the same cases to the local standalone hook, plus a small CommonJS
checker for it: the vitest suite needs a newer Node than some dev boxes
run, and the guard is worth being able to verify anywhere.
@ekechinwokah ekechinwokah changed the title Block grep/awk/sed when code-context is installed Block all unix file reads when code-context is installed Sep 1, 2026
Blocking one program at a time does not hold. With the grep family
denied, the next reach is awk, then sed, then cut, then a one-line
python, and the agent still answers from the handful of lines whichever
one printed. The failure is reasoning from an extracted fragment, not
the name of the program that extracted it, so the line to hold is that
no file contents arrive through a shell at all.

Denies the whole family together: the readers and pagers (cat, head,
tail, tac, nl, less, strings, od, xxd), the text filters (cut, tr,
sort, uniq, paste, join, comm, column, jq, yq), and interpreters run as
one-liners, where python3 -c, node -e, perl -pe and a heredoc piped to
stdin are a text filter under another name.

Running a script from a file stays allowed, as does everything that
does work rather than reads: builds, tests, benchmarks, version
control, file moves, redirects into files. Only "show me the bytes"
moves to Read and to the index, and the denial message now says so.
Neither is a downgrade, since Read takes an offset and the index
answers over a corpus of any size, so size is never a reason to filter.

The local standalone hook carries the same rules, checked by 47 cases:
30 denied across pipes, wrappers, env prefixes, absolute paths and
alias escapes, and 17 allowed covering the ordinary working commands.
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