fix(security): resolve findings from the Fable adversarial bug hunt#231
Open
systemslibrarian wants to merge 3 commits into
Open
fix(security): resolve findings from the Fable adversarial bug hunt#231systemslibrarian wants to merge 3 commits into
systemslibrarian wants to merge 3 commits into
Conversation
Fixes the findings documented in Fable-Bug-Hunt-Results.md (1 critical, 2 high, 8 medium, 17 low) across the Rust crates and the Python pipeline, with backward-compatible on-disk/wire formats and executable PoC regression tests for the top findings. Highlights: - C1: encode.py/crypto.py no longer emit a PQ-mode manifest without a PQ ciphertext; pack_manifest fails closed, so --high-security can no longer wipe the source after producing a permanently undecodable artifact. - H1: FountainDecoder (Rust try_new + Python) and SchrodingerManifest.unpack now bounds-check attacker-controlled k_blocks/block_count/block_size before allocating (mirrors the encoder's guards). WASM/PyO3 callers surface a clean error instead of an unbounded allocation. - H2: master-ratchet state KEK is now Argon2id-derived (format MRCV3; legacy MRCV2 still readable, migrated on next save). - Crypto correctness/hardening: X25519 low-order (all-zero) shared-secret rejection in pure_crypto + wasm; effective zeroization of key material across crypto_core, rust_crypto handles, and the Python ratchet/x25519 paths; stream-chunk MAC now binds byte offset (anti reorder/replay); Argon2id at-rest KDFs for X25519 key storage; fail-closed stego/signing gates; OQS ML-KEM decapsulation fix; palette Lehmer-code overflow caps; Verus/doc overclaim corrections; ratchet handle-leak fixes. Two items are intentionally partial (see SECURITY TODO markers + doc): L8 (pk-commitment HMAC binding needs a coordinated decode_gif.py change) and M4 (legacy frame-MAC fail-closed default needs a decode_gif.py flag). Verification: crypto_core (native/pq/wasm) and rust_crypto build clean; all Rust tests pass; Python suite green except pre-existing Windows-only environmental failures (missing pyzbar/libzbar DLL, cp1252 codec on emoji, Unix-only resource module). New tests/test_fable_bug_hunt_poc.py asserts the C1/H1 fixes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l-closed legacy frame-MAC Finishes the two items left partial in the Fable bug-hunt branch. L8 (INV-041): bind the signer's in-band public key to the password-derived key material. The encoder MACs compute_public_key_commitment(pk) (previously dead code) with a key derived from the same handle that frame-MACs the signature-chunk transport, and appends the 32-byte tag to the signature blob. The decoder recomputes it over the transported pk and rejects a mismatch (fail-closed), defeating in-band key substitution. The tag is detected purely by blob length, so there is no signature/manifest format-version break — old artifacts carry no tag and old decoders ignore it. (Literal binding into the manifest HMAC is infeasible: the signing pubkey isn't available at manifest-HMAC-check time on decode.) M4: decode_gif now fails closed by default on the legacy fast-SHA-256 frame-MAC KDF (a captured legacy frame is an offline password oracle). If the modern Argon2id-derived frame MAC fails, the legacy fallback is refused unless the caller passes the existing --allow-legacy opt-in, which decodes pre-v2 files with a loud warning. Tests: tests/test_fable_bug_hunt_poc.py adds test_l8_pk_commitment_binds_ signing_key_to_password (legit key accepted, substituted key rejected) and test_m4_legacy_frame_mac_is_fail_closed_by_default. Existing encode/signing (test_encode.py, test_phase5_modules.py: 143) and crypto/ratchet/security suites (237) pass with no regressions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve the CI gate failures on the branch: - lint (cargo fmt): reformat the use-block in rust_crypto/src/fountain.rs and the touched crypto_core files to satisfy `cargo fmt --check`. - preflight (mypy): wrap struct.unpack(...)[0] in int() in master_ratchet.py so _read_generation_watermark returns int, not Any (no-any-return). - security (pytest): test_decode_gif_frame_mac_legacy_valid now passes allow_legacy=True — the M4 change made the legacy fast-SHA-256 frame-MAC fallback fail-closed by default, so the test must opt in like the CLI's --allow-legacy. Local: test_decode_gif.py + test_adversarial.py (73) pass; mypy clean on meow_decoder/; cargo fmt --check clean for both crates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Fixes the 28 findings in
Fable-Bug-Hunt-Results.md(1 critical, 2 high, 8 medium, 17 low) surfaced by the multi-agent Fable bug hunt. On-disk/wire formats changed backward-compatibly; PoC regression tests added for the top findings.Top fixes
encode.py/crypto.pyno longer emit a PQ-mode manifest with no PQ ciphertext.pack_manifestfails closed, so--high-securitycan't wipe the source after producing a permanently undecodable artifact.FountainDecoder(Rusttry_new+ Python) andSchrodingerManifest.unpackbounds-check attacker-controlledk_blocks/block_count/block_sizebefore allocating. WASM/PyO3 callers surface a clean error instead of an unbounded (~100 GB) allocation.Medium/low themes
X25519 low-order (all-zero) shared-secret rejection in
pure_crypto+wasm; effective zeroization acrosscrypto_core,rust_cryptohandles, and Python ratchet/x25519; stream-chunk MAC binds byte offset (anti reorder/replay); Argon2id at-rest KDF for X25519 key storage; fail-closed stego/signing gates; OQS ML-KEM decapsulation fix; palette Lehmer-code overflow caps; Verus/doc overclaim corrections; ratchet handle-leak fixes.Intentionally partial (SECURITY TODO markers + doc)
compute_public_key_commitmentis invoked, but fully binding the pk-commitment into the manifest HMAC needs a coordinateddecode_gif.py+ manifest-version change (follow-up).derive_frame_master_key_legacywarns loudly and supportsallow_legacy=False, but defaults toTruefor compat; fail-closed default needs adecode_gif.py --allow-legacy-frame-macflag.Verification
crypto_core(native/pq/wasm) andrust_cryptobuild clean; all Rust tests pass.pyzbar/libzbarQR DLL, cp1252 codec on emoji in templates/Cargo.toml, Unix-onlyresourcemodule) — these are unrelated to the changes and should pass in Linux CI.tests/test_fable_bug_hunt_poc.pyexecutes the C1/H1 fixes (3 passing).🤖 Generated with Claude Code