Skip to content

fix: Opus 4.8 bug sweep (timelock data-loss, fountain DoS, cat-mode decode, GIF tamper index) - #201

Merged
systemslibrarian merged 3 commits into
mainfrom
fix/opus48-bug-sweep
Jun 4, 2026
Merged

fix: Opus 4.8 bug sweep (timelock data-loss, fountain DoS, cat-mode decode, GIF tamper index)#201
systemslibrarian merged 3 commits into
mainfrom
fix/opus48-bug-sweep

Conversation

@systemslibrarian

Copy link
Copy Markdown
Owner

Cross-cutting correctness/security bug sweep across the Rust crypto core, Python decode pipeline, and cat-mode web video protocol. 3 commits, +286/-55 across 14 files. Touched-module test suites pass (crypto_core 180, rust_crypto 67, plus Python timelock/air_gap/env/forensic/metadata/deadman suites).

Highlights

CRITICAL — timelock_duress data loss: memory-hard puzzles were permanently undecryptable (solve_puzzle had no memory-hard branch and the flag wasn't carried). puzzle_data now carries a 1-byte use_memory_hard flag (40→41 bytes, backward compatible) and solve_puzzle mirrors the construction. Added a real round-trip regression test.

Rust fountain decoder DoS: a droplet with an out-of-range block index or wrong-width data panicked (OOB / wasm abort) via the public PyO3/wasm add_droplet. Added a geometry guard + regression tests. Also made timing_encode use saturating arithmetic (u16 overflow).

decode reliability:

  • decode_gif: legacy ML-KEM-1024 GIFs were un-decodable (missing 1715/1747 length+MAC sizes); also fixed tamper-report frame-index mixing two index spaces.
  • cat mode refine_bp: collapsed a correct blink period on compressed (VP9/camera) video, inflating the bitstream ~1.5x. Added a plausible-band filter + clamp.

other fixes: nonce GCM-reuse via empty context (domain-separated + reuse guard), web_demo download_tokens concurrency (missing lock), env_safety macOS debugger detection silently disabled (wrong method), deadman's-switch renew dropping grace period, forensic_cleanup fail-open success reporting, size_normalizer validation bypass, metadata_obfuscation clearer error.

🤖 Generated with Claude Code

systemslibrarian and others added 3 commits June 4, 2026 10:18
Rust crypto core:
- Fountain decoder rejected nothing on geometry mismatch: a droplet with an
  out-of-range block index or wrong-width data panicked (OOB index / abort in
  wasm = DoS) via the public PyO3/wasm add_droplet. Added a geometry guard so
  such droplets are ignored, plus regression tests.
- timing_encode: base_delay + jitter + value could overflow u16; use saturating
  arithmetic.

Python:
- decode_gif: legacy ML-KEM-1024 GIFs were un-decodable — expected_lengths and
  mac_sizes omitted 1715/1747 (and MAC'd 1723/1755) that unpack_manifest accepts.
  Added them to match the pattern used for every other mode.
- nonce: generate_for_transfer with empty context produced a nonce identical to
  generate() (GCM reuse) and bypassed the reuse guard. Domain-separated it and
  added a (counter, context) reuse guard. +regression tests.
- size_normalizer: explicit size_class validation ignored the 8-byte length
  header, passing validation then crashing in pad_to_size_class. Mirror the
  auto-select branch.

Cat mode (web video protocol):
- refine_bp collapsed a correct preamble-derived blink period to a far smaller
  value on compressed (VP9/camera) video, because transition artifacts polluted
  the single-blink median — inflating the decoded bitstream ~1.5x and breaking
  decode. Added a plausible-band filter and a clamp to the reliable preamble
  estimate. Verified against a realistic VFR-WebM ffmpeg pipeline; no regression
  on clean video.

Rust suites (crypto_core 180, rust_crypto 67) and touched Python tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pen reporting

timelock_duress (CRITICAL data loss): memory-hard puzzles were permanently
undecryptable — create_puzzle ran a counter-mixed SHA-256 chain over
iterations//1000 rounds, but solve_puzzle had no memory-hard branch and
puzzle_data didn't carry the flag, so the target could never be reproduced.
Now puzzle_data carries a 1-byte use_memory_hard flag (40->41 bytes, backward
compatible: legacy 40-byte puzzles parse as standard) and solve_puzzle mirrors
the construction. Added a real round-trip regression test (>=1000 iters; the
old test used 500 -> 0 rounds and never exercised the bug).

web_demo/app.py (concurrency): the four download_tokens[...] = {...} inserts
ran without download_tokens_lock while cleanup_old_files() iterates the dict
under that lock on every request (Flask threaded), so a concurrent insert could
raise "dictionary changed size during iteration". Wrapped all four in the lock.

env_safety: _check_macos_debugger called report.add_warning(), which doesn't
exist — the AttributeError was swallowed by the caller's broad except, silently
disabling the macOS ptrace detection. Use add_risk(Risk(...)).

deadmans_switch_cli: cmd_renew computed the deadline from checkin_interval only,
dropping grace_period_seconds (which save() includes), so renew silently
shortened the window and the switch could fire early. Add the grace period.

forensic_cleanup: _clean_thumbnails and clean_clipboard reported success=True
even after recording an error — for a cleanup feature, masking failure is the
dangerous direction. Report success based on whether an error occurred.

metadata_obfuscation: pad_frame_count raised an opaque IndexError on frames[0]
for an empty frame list; raise a clear ValueError instead.

air_gap: clarified (docstring only) that air_gapped is intentionally vacuously
true and callers must consult `unknown` — a flagged "fail-open" was actually
deliberate, tested behavior; reverted the logic change.

Touched-module tests pass (timelock+air_gap 54, env/forensic/metadata/deadman 59).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…frames

The valid-frame path recorded tamper_report.record(idx + 1, ...) (the QR-list
position) while the invalid-frame path used actual_frame_idx (the true GIF
frame index from qr_frame_indices). When stego extraction skips unreadable
frames the two diverge, so the per-frame tamper timeline mixed two index
spaces. Use actual_frame_idx consistently; also align the ratchet-fail verbose
log to the same index.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@systemslibrarian
systemslibrarian merged commit f3cce9e into main Jun 4, 2026
1 check passed
@systemslibrarian
systemslibrarian deleted the fix/opus48-bug-sweep branch June 4, 2026 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant