Repository navigation
fix(quote-guard): refuse a shell command or code run that writes a bad quotation into a draft - #13
Merged
Merged
Conversation
…tion into a draft is refused On 2026-11-10 an agent asked to append one sentence to the practice draft used the terminal (printf ... >> Drafts/...) instead of patch, twice, and both writes went through: the quote guard read only write_file and patch calls. The guard now also reads terminal and execute_code calls. A call is checked when it looks like it changes files (the source guard's own test, now one helper both guards use) and names a text file in a case that is not a source or the store. Its text is read as the text it writes: shell quotes and escapes undone, each quoted word and here-document set off as its own paragraph, python -c and python here-documents read by their string values, sh -c read again as a command. A quotation or page cite the record does not support blocks the call, and the message sends the agent to patch or write_file. Departure from the plan: the plan asked only for escapes to be undone. That did not catch the two real commands (the redirect after the cite was read into the cite's sentence) and refused exact quotations inside escaped shell quotes and next to quoted Python file names, so the command is read by a small shell and Python reader instead. Known limits, stated in the README: text built in pieces or encoded, or written through a file the command does not name, is not caught. Known false blocks: a sed or perl that removes a bad quotation, a grep of the bad sentence redirected to a file, and strings in languages other than shell and Python. Gate on the tree that became this commit: 2238 tests OK (1 skipped) on Python 3.14.7, 3.10.20 and 3.12.13; hermes plugins validate passed; both leak scans 0 findings (no case patterns configured). Implemented by an Opus subagent from a written plan; the diff was read and probed afterwards. Not independently reviewed. Not yet seen in a live Hermes. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…rd only asks The first fix on this branch let the quote check read terminal commands and execute_code runs. Run live on 2026-11-10 it still let a made-up quotation into the practice draft. The agent's command was cd <case folder> && printf '\n\n<sentence>\n' >> Drafts/... && tail -n 3 Drafts/... It names the case folder, which holds registered sources, so the source guard answered first with an approval question and the hook returned that. The quote check never ran. An approval is a question a person may answer yes, and with approvals turned off in Hermes nobody is asked, so the command ran. The hook now keeps the source guard's answer and still runs the quote check when that answer is only a question. Order of results: - a refusal from the source guard is final, as before; - otherwise a refusal from the quote check wins; - otherwise the source guard's question stands, as before. The same holds after an error inside the source guard (its fallback question no longer skips the quote check), and an error inside the quote check leaves the source guard's answer in place. Choice made here, not asked of ty: the refusal wins over the question. The other way round a person saying yes to "this command names a folder that holds sources" would also be saying yes to a quotation the record does not hold, without being told about it. Tests first: test_a_bad_quotation_is_refused_even_when_the_command_also_ needs_approval failed with 'approve' != 'block' on the live command before the change. test_the_source_guard_still_comes_first is replaced by tests that say what now holds: a command naming a source with no bad quotation still goes to approval; a source guard refusal is not replaced; an error in either guard does not turn the other off. Seen live after the change (capture profile, Opus 5.5, approvals off): the agent's printf append was refused with the quote check's message and the draft's hash did not change. Not independently reviewed. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
The quote guard refused a
write_fileorpatchthat would put a quotation or record page cite into a draft that the case record does not support. It did not readterminalcommands orexecute_coderuns. On 2026-11-10 an agent asked to append one sentence to the practice case's draft used the terminal three times, and all three writes went through:Doc. 12 holds no such words.
What changes
terminalcommand orexecute_coderun that looks like it changes files and names a draft in a case is read as the text it would write (shell quotes and escapes undone, here-documents,python -c, Python string literals) and checked the same way. A not found or wrong page refuses the command before it runs and sends the agent topatchorwrite_file.The
write_fileandpatchmessage is unchanged.matterkit/versions.pyand the verify count axes are untouched.Choices made here, not asked for
Known limits
sedorperlcommand that removes a bad quotation, agrepof the bad sentence redirected into a draft, string literals in other languages, a relative name outside the case that also exists under the case folder setting.write_file.Tests
tests/test_quote_guard_shell.py(22 tests): the real commands, 15 ways of writing the sentence, exact quotations passing in every form, wrong pages named, read-only commands and writes outside a case passing, paths, the order of the two guards, errors in either guard.13777a8): 2243 tests OK (1 skipped) on Python 3.14.7, 3.10.20 and 3.12.13;hermes plugins validate .passed; both leak scans 0 findings, with no case patterns configured.printf … >>append was refused with the quote check's message and the draft's hash did not change.Not independently reviewed.
🤖 Generated with Claude Code