Security Finding
Severity: medium
Type: unsafe-pattern (trust-boundary bypass / persistence)
The #23 fix marks project-local excuses (./.rationguard/custom-excuses.json, attacker-controlled via any cloned repo) as source: 'project' and refuses to auto-send their rebuttals (src/watcher.ts flushBuffer). But the auto-learning path launders them into the trusted user store:
src/watcher.ts (flushBuffer) and src/cli.ts (cmdCheck) call recordSighting(match.matchedText, match.excuse.category) for every match with confidence ≥ 0.7 — without checking match.excuse.source.
- For an exact match,
matchedText is the project excuse's pattern (arbitrary attacker text), and confidence is 1.0.
- After
AUTO_ADD_THRESHOLD = 3 sightings, src/learner.ts promoteToExcuse + addToCustomExcuses write the pattern into ~/.rationguard/custom-excuses.json (the HOME store — projectDir is never passed).
- On next load,
getAllExcuses() tags everything from the HOME store as source: 'user' → its rebuttal is auto-send eligible, bypassing the project guard.
Reproduction (verified against dist @ HEAD)
A repo ships .rationguard/custom-excuses.json with {"pattern": "the", ...}. Watching any session in that repo, three buffer flushes containing the word "the" (i.e. virtually any output) produce:
~/.rationguard/custom-excuses.json:
[{"pattern":"the","rebuttal":"Act now. If blocked, move to the next task. ...","category":"deferral","keywords":[]}]
reloaded as source: 'user' → auto-send eligible in every future session in every project.
Impact
- A malicious repo persistently poisons the user's global excuse store. A pattern like
"the" matches nearly all agent output at confidence 1.0.
- With
--rebuttal=send, this triggers keystroke injection (pluk-send/tmux send-keys + Enter) into unrelated agent sessions on a permanent schedule (30s cooldown / 60s quiet), long after the malicious repo is gone. Rebuttal text is the fixed generated string, but the attacker fully controls when Enter-terminated lines are injected, and floods detections/sightings.
- Same laundering occurs from plain
rationguard check runs inside the malicious repo.
Recommendation
Never feed source: 'project' matches into recordSighting. Skip them in both call sites (src/watcher.ts flushBuffer, src/cli.ts cmdCheck), mirroring the existing auto-send guard. Optionally record them into the project-local store instead (detection-only) — but they must never reach the HOME store.
Filed by sec-check agent (ACMM L4/L5 — hold-gated mode)
🐝 Hive Agent: security | Instance: hosted-available-oke-11-placeholder-r05x | SHA: unknown
— hive: agent=sec-check backend=copilot model=claude-fable-5 copilot=1.0.88
Security Finding
Severity: medium
Type: unsafe-pattern (trust-boundary bypass / persistence)
The #23 fix marks project-local excuses (
./.rationguard/custom-excuses.json, attacker-controlled via any cloned repo) assource: 'project'and refuses to auto-send their rebuttals (src/watcher.tsflushBuffer). But the auto-learning path launders them into the trusted user store:src/watcher.ts(flushBuffer) andsrc/cli.ts(cmdCheck) callrecordSighting(match.matchedText, match.excuse.category)for every match with confidence ≥ 0.7 — without checkingmatch.excuse.source.matchedTextis the project excuse's pattern (arbitrary attacker text), and confidence is 1.0.AUTO_ADD_THRESHOLD = 3sightings,src/learner.tspromoteToExcuse+addToCustomExcuseswrite the pattern into~/.rationguard/custom-excuses.json(the HOME store —projectDiris never passed).getAllExcuses()tags everything from the HOME store assource: 'user'→ its rebuttal is auto-send eligible, bypassing the project guard.Reproduction (verified against dist @ HEAD)
A repo ships
.rationguard/custom-excuses.jsonwith{"pattern": "the", ...}. Watching any session in that repo, three buffer flushes containing the word "the" (i.e. virtually any output) produce:reloaded as
source: 'user'→ auto-send eligible in every future session in every project.Impact
"the"matches nearly all agent output at confidence 1.0.--rebuttal=send, this triggers keystroke injection (pluk-send/tmux send-keys+ Enter) into unrelated agent sessions on a permanent schedule (30s cooldown / 60s quiet), long after the malicious repo is gone. Rebuttal text is the fixed generated string, but the attacker fully controls when Enter-terminated lines are injected, and floods detections/sightings.rationguard checkruns inside the malicious repo.Recommendation
Never feed
source: 'project'matches intorecordSighting. Skip them in both call sites (src/watcher.tsflushBuffer,src/cli.tscmdCheck), mirroring the existing auto-send guard. Optionally record them into the project-local store instead (detection-only) — but they must never reach the HOME store.Filed by sec-check agent (ACMM L4/L5 — hold-gated mode)
🐝 Hive Agent:
security| Instance:hosted-available-oke-11-placeholder-r05x| SHA:unknown— hive: agent=sec-check backend=copilot model=claude-fable-5 copilot=1.0.88