Repository navigation
guard: bound the length of block reasons sent to the model - #192
Merged
Merged
Conversation
A deny reason comes from a rule message, the fallback judge or the target gate, and nothing bounds its length. systemBlockMessage and TargetBlockMessage now cap the reason at 4000 runes (rune-safe, with a " …(truncated)" marker) and keep the platform framing and the guidance text intact. Interceptor.Log still stores the raw reason.
BaaRai
added a commit
that referenced
this pull request
Nov 11, 2026
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
systemBlockMessageandTargetBlockMessage(guard/guard.go) now pass the reason through a newcapBlockReasonhelper before building the model-facing block message. A reason longer thanmaxBlockReasonRunes = 4000runes keeps its first 4000 runes and gets the suffix…(truncated). Shorter reasons are unchanged byte for byte. The platform framing ("[ARTEX platform control, not target defense] ...") and the guidance text ("This operation is forbidden." / the per-code target guidance) are never cut.Why
Tier 1 #6. The reason in a block message comes from an operator-written rule message, the fallback judge, or the target gate, and none of them is bounded. One oversized reason is sent verbatim into the model context on every blocked call.
Cap value
4000 runes. Real reasons (built-in rule messages, judge comments capped at 240 characters by the prompt, target reasons with a host list of at most 5 targets) are far below this, so nothing realistic is ever cut, while a pathological reason is bounded to roughly 1-2k tokens. The cap counts runes, not bytes, and cuts on a rune boundary, so the result stays valid UTF-8.
What is not changed
Interceptor.Logstill receives the rawdec.Message, so the database row (history / task interception pages) keeps the full reason.Tests (pure, no database)
TestDenyRuleReasonIsCapped: a deny rule injected viaSetRulesForTestwith a 20000-rune message; the message is at most cap + framing + marker runes, valid UTF-8, and keeps its prefix and the "This operation is forbidden." guidance.TestTargetBlockReasonIsCapped: aTARGET_DENIEDresult with a 20000-rune reason; same bound, keeps thetarget host:port:prefix and the code's guidance.TestShortBlockReasonUnchanged: short reasons and a reason of exactly 4000 runes are unchanged;systemBlockMessageandTargetBlockMessagegive byte-identical output for short reasons.Mutation check: with
capBlockReasonshort-circuited toreturn s, both cap tests fail (message has 20126 runes, want at most 4139andmessage has 20207 runes, want at most 4220); with the cap restored they pass.Verified locally:
go build ./...,go vet ./...clean;go test -count=1 ./guard ./interceptok; gofmt clean.