Repository navigation
fix(context-source-guard): fail-closed on exception inside gated section (security finding #4) - #1985
Conversation
…ion (security audit finding sonichi#4) Previously the outer `except Exception` in `__main__` would fail-open on any exception — including those raised after a non-empty contextNotFrom blacklist was confirmed. An unexpected runtime error inside the channel-check loop (guild resolution, token lookup, blacklist membership test) could silently allow a blacklisted read through. Fix: wrap the critical section (non-empty blacklist confirmed) in its own try/except that calls `_deny()` on any non-SystemExit exception. Pre-check errors (JSON parsing, tool detection, serving/blacklist lookup) still fail-open via the outer handler — correct, because no guard is active. Add test 9: monkey-patch `_resolve_guild` to throw RuntimeError while serving a channel with a non-empty blacklist; assert the hook denies (fail-closed) rather than allowing. Security audit: workspace/notes/security-audit-2026-07-06.md finding sonichi#4. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QB3qZQrwponfum2JFNvHeh
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 48%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changesNo lines with coverage information in this diff. |
qingyun-wu
left a comment
There was a problem hiding this comment.
Cold-reviewed — fix is correct; one non-blocking test-precision note.
Fix (hooks/context-source-guard.py) — correct and important. Wrapping the post-blacklist-confirmed critical section in its own try/except closes the fail-open bypass: previously any exception from _token()/_resolve_guild() after a non-empty blacklist was confirmed unwound to the top-level except → sys.exit(0) (fail-open = allow a potentially-blacklisted read). Now it fails closed via _deny(). ✅
except SystemExit: raisecorrectly lets_deny()s exit propagate — and sinceSystemExitderives fromBaseException(notException), theexcept Exceptionarm couldnt swallow a deny even without the explicit re-raise. Nicely double-safe.- Splitting fail-open (pre-check: parsing/serving lookup) from fail-closed (guard-active) is the right boundary, and the docstring update matches.
Test note (non-blocking): test #9 asserts SystemExit.code == 0 was raised. Sound for this setup — _resolve_guild throws on the first target, so exit(0) can only come from _deny(), and the old code would escape main() as a RuntimeError (not SystemExit) → assertion fails → regression caught. But exit(0) is also what a fail-open allow emits, so the assertion is indirect. Consider redirecting stdout and asserting the deny payload is actually emitted — makes it assert deny-content rather than an exit happened. Nice-to-have.
No blockers — recommend merge. Merge stays your call.
|
Automated mergeability review (2026-07-09) Signal: NOT MERGEABLE Current blocker(s):
This is a conservative backlog triage signal. After the blockers are cleared, please rerun checks and request a focused review. |
liususan091219
left a comment
There was a problem hiding this comment.
Codex full-review sweep: checked against the 7 triage CR classes (source-grep tests, incomplete fix, band-aid, naming, false claim, live-test policy, hardcoded paths) - none fire. Approving.
review-reason: codex-clean per full-review sweep
Approval withheld pending owner go (resequenced: CRs first). Will re-approve.
|
LGTM. I diffed this against Qingyun's Personal Codex Agent |
|
Independent before/after verification (maintainer): imported the hook as a module, seeded a serving channel with a non-empty BEFORE ( The exception escapes the (unwrapped) gated block → hits the outer AFTER (this PR): The scoped Confirms the security claim: an exception after a non-empty blacklist is confirmed now fails closed instead of open. Verified working. |
|
@cla-assistant check |
Summary
workspace/notes/security-audit-2026-07-06.md: the outerexcept Exceptionin__main__ofhooks/context-source-guard.pywould fail-open on any unhandled exception, including one that occurs after a non-emptycontextNotFromblacklist has been confirmed.…/channels/<id>/messagesread through.Fix in
hooks/context-source-guard.py:blacklistis confirmed non-empty) in a scopedtry/exceptthat calls_deny()on any non-SystemExitexception.except SystemExit: raiseensures_deny()'ssys.exit(0)propagates normally through the inner block.New test 9 in
tests/context-source-guard.test.py:_resolve_guildto raiseRuntimeError, feeds a matching channel-read payload, and asserts the hook denies (fails-closed) rather than allowing.Test plan
python3 tests/context-source-guard.test.py— all 9 tests pass (including new test 9 which explicitly exercises the RuntimeError → deny path)SUTANDO_WORKSPACEignored (feat(discord-bridge): contextNotFrom gate — bar a channel from pulling context from blacklisted channels/guilds #1698), per-hostCLAUDE_CONFIG_DIRwalk (feat(discord-bridge): contextNotFrom gate — bar a channel from pulling context from blacklisted channels/guilds #1698)🤖 Generated with Claude Code
https://claude.ai/code/session_01QB3qZQrwponfum2JFNvHeh