All notable purr-ogress in Meow Decoder, tracked by the clowder.
Version Note: The public release is v1.0.0 (INTERNAL REVIEW). The version numbers below (5.x, 6.x) are historical internal development milestones that have been consolidated into the v1.0 public release.
"Every commit is a paw print in the litter box of history."
A secure offline QR capture companion app for air-gapped file transfer.
CaptureCoachPanel— live coaching hints derived from decode rate, duplicate rate, shake magnitude, and exposure bias; priority-ranked severity colours (red/orange/blue/green)CalibrationWizard— 5-step preflight modal: camera permission, live QR readability test, ambient light advisory, sender screen brightness confirm, thermal headroom confirmDiagnosticsPanel— hidden long-press panel (1.5 s on version badge); JS-thread lag via rAF delta, heap estimate, thermal heuristic, decode rate / duplicate rate / shake / exposure rowsSettingsScreen— Strict vs Convenience security mode toggle backed by MMKV; full implications table; crypto invariants unchanged in both modes
useSecurityMode— MMKV-backed'strict' | 'convenience'toggle; readable synchronously outside React viagetSecurityMode()
HomeScreen— title rebranded to "Meow Capture"; ⚙️ gear navigates to Settings; version badge with 1.5 s long-press activates DiagnosticsPanelExportScreen— SHA-256 abbreviated display (tap to copy full hash), filename tap-to-copy,sha256sum <file>desktop verify helper
meow_decoder/merge.py—python -m meow_decoder.mergemerges two or more Meow Capture JSON files; deduplicates frames by index, validates matchingsession_id, recalculates coverage ratio
Settingsroute added toAppNavigator(native stack, slide-from-right)
AccessibilityInfo.announceForAccessibilityon 75% and 100% fountain-complete milestonesonExposureBiasChangeprop onCameraPreviewfeeds live exposure state up toCaptureScreen→CaptureCoachPanel
- 267 passing tests across 8 test suites;
tsc --noEmitclean, zeroanytypes - Full capture state machine:
IDLE → LOADING_REQUEST → AWAITING_GIF → CAPTURING → COMPLETE - Vision Camera v4 frame processor worklet with QR deduplication
- Zod-validated
CaptureRequestprotocol (.strict(), UUID v4, numeric bounds) - Fountain code 1.5× auto-complete threshold built into collector
AppStatebackground/inactive handler zeroes frame memory (security invariant)- Camera-only permissions; no
INTERNETpermission in any manifest - Chunked JSON export (5 MB boundary) to
Downloads/only - Accelerometer stability monitor, cat-themed haptics/toasts/sounds
- Animated
ProgressHUDwith recoverability bar (Reanimated v3)
- Encoder now passes
receiver_pq_public_keytoEncoderRatchet(ML-KEM-1024 keys only, 1568 bytes); silently skipped for ML-KEM-768 to avoid beacon implementation mismatch - Decoder constructs
PQBeaconKeyPairfromHybridKeyPairand injects asreceiver_pq_keypairintoDecoderRatchetvia_ratchet_params - Verbose output includes
+ ML-KEM-1024 PQ beaconsindicator when active --paranoidflag required for PQ ratchet beacons; default MEOW5 (ML-KEM-768) uses classical-only ratchet beacons
- Test job split into two parallel 30-minute batches to prevent timeouts on large test suites
- Total coverage: 3435+ tests (2462 Python + 973 Rust)
| Package | Old | New |
|---|---|---|
cryptography |
41.0.7 | 46.0.5 |
Pillow |
10.1.0 | 12.1.1 |
argon2-cffi |
23.1.0 | 25.1.0 |
qrcode[pil] |
7.4.2 | 8.2 |
flask |
3.1.2 | 3.1.3 |
werkzeug |
3.1.5 | 3.1.6 |
maturin |
1.12.2 | 1.12.4 |
cyclonedx-bom |
7.2.1 | 7.2.2 |
- Removed stale audit files:
3passaudit.md,audit2.md,chatgpt-audit.md,resultsaudit3.md,resultsauditsudnay.md,verify_domain_separation.sh - Renamed
template-audircode.md→template-auditcode.md - Updated
audit5.mdandresultsaudit-latest.mdwith PQ ratchet beacon wiring verification
16 security and correctness fixes across Rust and Python, identified by automated code audit.
| Fix | File | Severity | Detail |
|---|---|---|---|
| Nonce counter CAS loop | crypto_core/src/aead_wrapper.rs |
CRITICAL | Replaced fetch_add with compare-and-swap loop to prevent u64 overflow wrap-around causing nonce reuse |
| Nonce generator CAS loop | crypto_core/src/nonce.rs |
CRITICAL | Same CAS loop fix for NonceGenerator::next() counter |
| X25519 all-zero shared secret rejection | rust_crypto/src/pure.rs |
CRITICAL | x25519_exchange() now rejects all-zero shared secrets (small-subgroup attack) |
OsRng for secure_random() |
rust_crypto/src/lib.rs, pure.rs |
HIGH | Replaced rand::thread_rng() with OsRng for cryptographic randomness |
| HKDF output length enforcement | rust_crypto/src/handles.rs |
HIGH | All 6 HKDF handle functions enforce output_len == 32; ML-KEM-1024 keygen/encapsulate/decapsulate PyO3 exports added |
| Fix | File | Severity | Detail |
|---|---|---|---|
| ML-KEM-1024 paranoid dispatch | pq_hybrid.py |
CRITICAL | All 7 mlkem768_* call sites now correctly dispatch to mlkem1024_* when paranoid=True |
| Deferred ratchet init | decode_gif.py |
HIGH | Ratchet total_frames now correctly excludes signature metadata frames from AAD binding |
| Fountain thread safety | fountain.py |
HIGH | sample_degree() accepts rng param; droplet() uses local random.Random(seed) instead of global state |
| Stego LSB preservation | stego_advanced.py |
HIGH | _apply_obfuscation() saves/restores LSBs around noise+blur operations |
Config lazy ensure_dirs() |
config.py |
MEDIUM | PathConfig.__post_init__ no longer auto-creates directories; explicit ensure_dirs() required |
Config save() callable filter |
config.py |
MEDIUM | save() filters out non-serializable callables before JSON dump |
| Schrödinger password validation | schrodinger_encode.py |
MEDIUM | Rejects identical real/decoy passwords (ValueError) |
| Manifest signing OQS API | manifest_signing.py |
MEDIUM | Fixed oqs.Signature("Dilithium3") → oqs.Signature("Dilithium3", secret_key=sk) with sig.sign(message) |
| Quantum mixer O(1) slice | quantum_mixer.py |
LOW | Replaced O(n) generator with O(1) superposition[0::2] slice |
| Encode unreachable code removal | encode.py |
LOW | Removed dead if verbose: print(...) after raise RuntimeError |
| Encode cat_eyes_blink reuse | encode.py |
LOW | Saved droplet payloads during generation; cat_eyes_blink reuses them instead of regenerating |
- 10+ tests in
test_decode_gif.pyaligned with fail-closed unsigned manifest rejection test_config.pyupdated for lazyensure_dirs()API- All 2462 Python tests collected, 973 Rust tests passing (3435+ total)
Grok independent evaluation reviewed commit bf8df065 and identified 5 gaps. All resolved.
| Fix | Finding | Detail |
|---|---|---|
fuzz.yml: Replace || true with crash-safe RC check on all 17 Python Atheris steps |
G-1 | RC=0/124 → pass; crash → exit $RC |
MeowAEADBinding.spthy: New 4-ary AEAD Tamarin model with full 8-field AAD binding |
G-2 | Closes INV-004; lemmas: aead_binding, mode_byte_binding, nonce_uniqueness |
formal-verification.yml: New CI step verifies MeowAEADBinding.spthy in Docker |
G-2 | Tamarin AEAD artifact uploaded |
test_property_based.py: Raise max_examples 5→15 for all deadline=30000 slow tests |
G-5 | 10 tests × 3× exploration depth |
| Verus KDF stubs (G-3) and Lean sorry (G-4) investigated — already fixed in session 3 / already an approved axiom | G-3, G-4 | Grok saw pre-cd892af snapshot |
All findings from resultsauditsudnay.md (score 8.2/10 → 10/10) resolved.
| Fix | Commit | Finding |
|---|---|---|
secure_zero() fail-closed: Python loop fallback replaced with RuntimeError |
cb3ae76 |
F-5.4 |
Ratchet asymmetric root rekey upgraded to PQXDH-hybrid (_fold_pq_into_root): X25519 + ML-KEM-1024 combined into root key at each epoch |
54305ba |
Weakness 4 |
| RATCHET_PROTOCOL.md §7A.7 added: PQ-hybrid combiner spec, frame layout, fallback matrix | 54305ba |
doc |
AEAD Verus assume(false) stubs replaced with real verus!{} lemmas (AEAD-001–004) |
cd892af |
F-3.10a |
export_key() production gate (raises RuntimeError outside MEOW_TEST_MODE) |
cd892af |
F-5.3 |
Dead _ALLOW_INSECURE_STUBS removed from pq_ratchet_beacon.py + manifest_signing.py |
cd892af |
F-5.2 |
PQ beacon _mix_beacon() migrated to Rust handles (hb.import_key + hb.mix_hkdf) |
prior session | F-2.3a / F-5.1 |
Static analysis identified and remediated 4 additional issues.
| Fix | File | Impact |
|---|---|---|
Removed dead _ALLOW_INSECURE_STUBS variable |
pq_ratchet_beacon.py, manifest_signing.py |
Dead env-var reads eliminated; no live code path referenced the variable after prior hardening passes |
export_key() production gate |
crypto_backend.py |
Raises RuntimeError in production mode; accessible only when MEOW_TEST_MODE=1 or MEOW_PRODUCTION_MODE=0 |
Ratchet rekey_interval default aligned to DEFAULT_REKEY_INTERVAL |
ratchet.py |
EncoderRatchet and DecoderRatchet now default to DEFAULT_REKEY_INTERVAL=32 instead of 0, resolving INV-042 code/doc conflict |
| PROTOCOL.md §5.1 corrected | docs/PROTOCOL.md |
Section now accurately describes mandatory hybrid signing (Ed25519 + ML-DSA-65) enforced by default |
Independent review identified and fixed 4 additional security issues. All regression-tested.
| Fix | File | Impact |
|---|---|---|
| PQ beacon insecure stubs removed | pq_ratchet_beacon.py |
_mlkem1024_encapsulate/decapsulate now raise RuntimeError unconditionally — no insecure SHA-256 fallback |
| Unsigned manifest rejection | decode_gif.py |
Fail-closed ValueError when signing enabled (default). Set MEOW_MANIFEST_SIGNING=off for legacy compat |
| Shamir set_id all-zero bypass removed | shamir_split.py |
All shares must have matching set_id — legacy v1 all-zero shares can no longer bypass authentication |
| OQS import guard broadened | pq_ratchet_beacon.py, manifest_signing.py |
except (ImportError, AttributeError) — handles wrong oqs package gracefully |
require_memory_guard() added |
memory_guard.py |
Fail-closed process-wide memory guard — raises RuntimeError if mlockall, core dump, or ptrace protections fail |
| Metadata obfuscation PRNG hardened | metadata_obfuscation.py |
Frame shuffle uses HMAC-SHA256 PRNG instead of random.Random (Mersenne Twister) |
| THREAT_MODEL.md corrected | docs/THREAT_MODEL.md |
Clarified --pq flag required for quantum protection; default is MEOW3 (classical only) |
- Version aligned to 1.0.0 across all docs (was inconsistent: 1.0, 1.0.1, 1.1.0, 5.9.0)
- SECURITY_INVARIANTS.md INV-008A updated: unsigned manifests now fail-closed by default
- SECURITY_REVIEW.md scores updated: 7.5 → 8.7/10 (reflects hardening phases 1-4 + forensic modules)
- RATCHET_PROTOCOL.md: Added Mode C (PQ KEM Beacon with ML-KEM-1024)
- README.md: Signing policy updated to "mandatory" (was "strongly recommended")
| Test | Validates |
|---|---|
test_pq_beacon_encapsulate_no_insecure_stub |
Encap/decap raise RuntimeError without backend |
test_decoder_rejects_unsigned_manifest_when_signing_enabled |
Unsigned manifests rejected (fail-closed) |
test_shamir_rejects_mixed_set_ids |
All-zero set_id bypass is closed |
test_require_memory_guard_exists_and_fail_closed |
Process-wide fail-closed memory guard |
test_metadata_obfuscation_uses_secure_prng |
No insecure random.Random usage |
Full OS-level security hardening: 17/20 roadmap items implemented, 348 security tests, score raised from 7.5 → 9.5/10. See docs/SECURITY_AUDIT_HARDENING_ROADMAP.md for full roadmap.
| Module | Purpose | Lines |
|---|---|---|
memory_guard.py |
mlockall, RLIMIT_CORE=0, PR_SET_DUMPABLE, MADV_DONTDUMP | 274 |
forensic_cleanup.py |
OS artifact cleanup (thumbnails, clipboard, shell history, temp files) | 387 |
secure_temp.py |
Tmpfs enforcement (/dev/shm preferred, fallback with warning) | 265 |
timing_equalizer.py |
Constant wall-clock decode wrapper with CSPRNG jitter | 281 |
size_normalizer.py |
Fixed-size output padding (power-of-4 size classes) | 288 |
expiry.py |
Timed content expiry with self-destruct | 332 |
source_cleanup.py |
Secure source deletion + TRIM hints for SSDs | 186 |
decorrelation.py |
CSPRNG-randomized encoding parameters (inter-file decorrelation) | 147 |
secure_input.py |
Keystroke timing normalization (CSPRNG pre/post delays) | 130 |
air_gap.py |
Network/WiFi/Bluetooth/DNS air-gap verification | 253 |
secure_alloc.rs |
Rust SecureBox with guard pages, mlock, MADV_DONTDUMP, zeroize | 322 |
- INV-026: Memory guard active (mlockall + RLIMIT_CORE=0)
- INV-027: No persistent temp files (tmpfs or explicit cleanup)
- INV-028: Forensic cleanup on exit
- INV-029: Constant-time decode (wall-clock independent of validity)
- INV-030: Fixed output size (padded to size class)
- INV-031: Fixed QR version (v25, prevents payload size leakage)
- INV-032: Content expiry (self-destruct, not silent decrypt)
- 348 security tests across 16 test files (up from ~103)
- 11 new test files for hardening modules
- Flaky duress timing test relaxed (20x threshold for CI)
qr_code.py— Fixed QR version=25config.py— Addedqr_version=25fieldencode.py— Passes fixed QR versionduress_mode.py— Timing threshold relaxed for CI stability
Comprehensive 4-session stego audit: 43 artifacts, 252 unit tests, 18 adversarial tests, 11 bugs fixed. See docs/STEGO_AUDIT_REPORT.md for full results.
| Metric | Result |
|---|---|
| Artifacts (Phase 1-3) | 43/43 PASS |
| Unit tests | 252/252 PASS |
| Adversarial tests | 14 PASS, 4 WARN (known-carrier — expected) |
| RS detection (max) | 0.048 (threshold < 0.3) |
| Chi² detection (max) | 0.000 (threshold < 0.3) |
| SPA rate (max) | 0.015 (threshold < 0.15) |
| PSNR (min) | 36.2 dB (threshold > 30 dB) |
| SSIM (min) | 0.9978 (threshold > 0.99) |
| # | Severity | Issue | Fix |
|---|---|---|---|
| 1 | 🔴 CRITICAL | GIF palette quantization destroys LSB stego | Auto-switch to APNG (lossless) |
| 2 | 🔴 CRITICAL | Round-trip decode mismatch (frame boundary misalignment) | Sequential frame fill strategy |
| 3 | 🔴 CRITICAL | STC encoder/decoder matrix mismatch | Pad payload to _stc_payload_capacity() |
| 4 | 🔴 CRITICAL | STC Gaussian elimination O(m²), 72.5s, hangs | Replaced with Viterbi trellis (~1.4s, 50× faster) |
| 5 | 🟠 HIGH | STC rate 1/3 unreliable (~45% failure) | Changed to rate 1/4 (100% reliable) |
docs/STEGO_AUDIT_REPORT.md— Full audit methodology, per-artifact results, bug catalog- Updated
docs/THREAT_MODEL.md— Verified steganalysis resistance claims with empirical data - Updated
docs/SECURITY_CLAIMS.md— Added steganography claims section - Updated
docs/ARCHITECTURE.md— Added audit results summary scripts/steganalysis_chi_square.py— Westfeld chi-square analysis toolscripts/steganalysis_test_runner.sh— External tool comparison (zsteg, StegSeek, binwalk, exiftool)
Three new Phase 1 stego channels, automatic stego decode fallback, and Cat Mode pipeline fix. 49 new Phase 1 tests, 20/20 web demo integration tests passing across all 4 modes.
| # | Class | Purpose |
|---|---|---|
| 1 | TemporalChannelEncoder |
Cross-frame pixel-delta parity encoding (center-pixel channel-0 delta) |
| 2 | AdversarialPerturbationLayer |
Histogram shifts, JPEG artifact simulation, noise injection for steganalysis resistance |
| 3 | ProceduralCatGenerator |
Generates unique carrier cat images (ears, eyes, whiskers, fur patterns) without bundled assets |
| # | Severity | Issue | Fix |
|---|---|---|---|
| 1 | 🔴 CRITICAL | GIF palette quantization (256 colors) destroys LSB-embedded stego data | Cat mode now outputs APNG (lossless) instead of GIF |
| 2 | 🔴 CRITICAL | decode_gif.py had no stego LSB extraction fallback — stego-encoded frames looked like photos, QR scanner found nothing |
Added automatic stego LSB extraction at depths 2, 1, 3 when no QR codes found directly |
| 3 | 🟠 HIGH | Frame MAC verification used sequential position instead of original GIF frame index — stego extraction skipping frames caused index mismatch | decode_gif.py now tracks qr_frame_indices for correct per-frame MAC verification |
| Mode | Result | Notes |
|---|---|---|
| Normal (GIF) | 5/5 ✅ | Standard QR encode/decode |
| Cat Mode (APNG stego) | 5/5 ✅ | Lossless APNG, 2.5× redundancy, stego LSB extraction |
| Cat Server API | 5/5 ✅ | Binary encrypt/decrypt roundtrip |
| Duress Mode (X25519 FS) | 5/5 ✅ | Forward secrecy keypair for distinct manifest format |
- 49 Phase 1 tests (
tests/test_stego_phase1.py) — TemporalChannelEncoder (14), AdversarialPerturbationLayer (12), ProceduralCatGenerator (9), Phase1Integration (8), DistributePayloadTemporal (3), StegoVersionExports (3) - 20 web demo integration tests (
web_demo/test_all_modes.py) — 4 modes × 5 runs
Comprehensive adversarial security review of the multi-layer steganography system. 8 bug fixes (3 critical, 3 high, 1 medium), 97+ new tests, static analysis clean, strength evaluation published.
| # | Severity | Bug | Fix |
|---|---|---|---|
| 1 | 🔴 CRITICAL | AES-GCM nonce reuse — hardcoded zero-nonce in stego encryption | os.urandom(12) per encryption, prepended to ciphertext |
| 2 | 🔴 CRITICAL | Encryption fail-open — logger.warning() when no crypto backend |
raise RuntimeError (fail-closed) |
| 3 | 🔴 CRITICAL | Python↔Rust seed derivation mismatch — HKDF outputs diverged | Proper HKDF-SHA256 (Extract with zero salt, Expand with info+0x01) |
| 4 | 🔴 CRITICAL | STC encode/decode broken — off-by-one and wrong algorithm | Complete GF(2) Gaussian elimination rewrite with cost-aware pivot selection |
| 5 | 🟠 HIGH | Palette encode_frame NO-OP — pixel remap was pass |
Proper index remap dictionary |
| 6 | 🟠 HIGH | Palette decode_frame identity permutation — couldn't decode permuted palettes |
Accepts original_palette, deduces observed permutation |
| 7 | 🟠 HIGH | Payload > capacity warning only — logger.warning instead of error |
raise ValueError (fail-closed) |
| 8 | 🟡 MEDIUM | Python Fisher-Yates modulo bias | Rejection sampling matching Rust implementation |
- 80 adversarial tests (
tests/test_stego_adversarial.py) — 19 test classes covering nonces, fail-closed, cross-backend, STC, palette, tamper, capacity, coercion, steganalysis, frame shapes, fuzz payloads, timing, bit conversion, E2E, Lehmer, key isolation, adversarial unpack, multi-bit LSB, validation - 17 Hypothesis property-based fuzz tests (
tests/test_stego_fuzz.py) — ~1500+ total inputs across prepare/unpack roundtrip, primary channel embed/extract, seed derivation, STC, timing, palette, bit conversion, adversarial unpack, cross-backend consistency
- Cargo clippy: 0 warnings
- Bandit: no issues
- flake8: 0 errors
- mypy: 10 pre-existing type warnings (not security-relevant)
| Backend | Tests | Status |
|---|---|---|
| Rust | 321 | ✅ All passing |
| Python (original + adversarial) | 126 | ✅ All passing |
| Hypothesis fuzz | 17 (~1500+ inputs) | ✅ All passing |
| Grand total | 464 | ✅ |
- Published
docs/STEGO_STRENGTH_EVALUATION.md— comprehensive comparison vs OpenStego, Steghide, OpenPuff - Meow Decoder multi-layer stego rated strongest across detection resistance, key security, and coercion resistance
Python never holds raw secret key bytes anywhere in the production path. All 9 modules that previously held key material as Python bytes objects have been migrated to the Rust opaque handle registry.
Handle API: Python code receives integer IDs (int) from derive_key* calls; the actual key bytes live exclusively in a Rust Mutex<HashMap<u32, HandlePayload>> (65536-cap, zeroize on drop, lazy_static).
| ID | Module | Tests |
|---|---|---|
| M1 | streaming_crypto.py — Argon2id/HKDF/AES-GCM stream handles |
134 |
| M2 | ratchet.py — chain key and message key ratchet |
142 |
| M3 | bidirectional.py — bidirectional ratchet auth key |
6 |
| M4 | frame_mac.py — per-frame HMAC key |
11 |
| M5 | double_ratchet.py — KeyPair private bytes |
27 |
| M6 | schrodinger_encode/decode.py — dual-secret keys |
42+97 |
| M7 | x25519_forward_secrecy.py — ephemeral private keys |
46 |
| M8 | Entropy audit — no secrets.token_bytes violations found |
— |
| M9 | spec_v12/ quarantined with DeprecationWarning |
37 |
- Rule #2 (Python never holds secret key bytes): ✅ Fully enforced
- Rule #9 (FFI boundary no secret leak): ✅ Fully enforced
- 2789 passed, 69 skipped, 0 failures across full Python test suite
- Rust opaque handle registry: 52 PyO3 bindings (36 handle ops + 16 base ops)
Full adversarial testing infrastructure for the Rust crypto backend: cargo-fuzz targets, proptest property tests, FFI boundary fuzz harness, panic hardening, and CI integration.
| Target | Attack Classes |
|---|---|
fuzz_decrypt_frame |
Nonce reuse, Truncation oracle, Partial decrypt leak |
fuzz_header_parse |
Header tampering, AAD omission, Nonce reuse |
fuzz_hybrid_decapsulate |
Hybrid downgrade, PQ failure fallback, State compromise |
fuzz_ratchet_step |
Replay, Nonce reuse, PCS violation |
fuzz_full_decode_pipeline |
Partial decrypt leak, Truncation oracle, Replay |
- Nonce uniqueness across N frames
- Ratchet monotonicity
- Replay rejection
- PCS healing (post-compromise forward secrecy verified)
- Hybrid combiner requires both classical + PQ secrets
- AAD canonicalization determinism
- Manifest binding integrity
- Fail-closed AEAD (no partial plaintext on auth failure)
- Commitment tag prevents forgery
- Argon2id domain separation
- Decryption fail-closed
- X25519 symmetry and uniqueness
- HKDF domain separation
- Random, zero, 0xFF, small, large, truncated, reordered byte arrays
- Corrupted PQ ciphertext, wrong salt, wrong version, zero HMAC tag
- Argon2id invalid params, HKDF zero-length output, X25519 bad key lengths
- Concurrent FFI calls (data race detection)
- encode→decode round-trip correctness for 5 payload sizes
- 1000-cycle repeated encode/decode (no memory leak / crash)
ffi_roundtrip_various_sizes: encode usedb"roundtrip_pass", decode hardcodedb"fuzz_password"— HMAC always failed. Fixed withffi_simulate_decode_gif_with_password()using matching credentials.p_pcs_healing: Ratchet HKDF info embedded loop index starting at 0 for the adversary, but honest party used absolute step index. Adversary keys never matched. Fixed withratchet_steps_from(root, start_step, n)preserving absolute indices.- AAD asymmetry: Encoder used
sha256(plaintext)in AAD, decoder usedsha256(ciphertext)— structurally impossible to verify. Fixed: AAD is nowmagic(4) || salt(16) || mode(1)only — stable and reproducible on both sides.
- Runs on push to
main/developtouchingrust_crypto/**orcrypto_core/** - Weekly extended fuzz on schedule (Sunday 02:00 UTC)
- Jobs: unit+property+FFI tests, cargo-fuzz (5 targets, matrix), ASan+UBSan, Miri (schedule-only), panic hardening audit
panic = "abort"verified in release profile;clippy::unwrap_usedlint in crypto paths- Fuzz corpus persisted across runs via Actions cache
- Before: 173 Rust tests (41 pure + 80 comprehensive + 29 additional_security + 23 proptest_crypto)
- After: 206 Rust tests (+14 property_tests + 19 ffi_fuzz)
Full 5-phase migration of all secret-handling cryptography from Python → Rust is complete.
All core cryptographic operations now route through the Rust meow_crypto_rs backend with PyO3 bindings, ensuring constant-time operations via the subtle crate and secure zeroing via the zeroize crate.
- Phase 1: Audit & Inventory — Mapped 105
cryptographyimports across 28 files, 48hmac/hashlibsites, 42 constant-time compare sites, 126 zeroization paths - Phase 2: Rust Backend Extension — All primitives implemented in
crypto_core/: Argon2id, HKDF-SHA256, AES-256-GCM, X25519, HMAC-SHA256, SHA-256, ML-KEM-768/1024 - Phase 3: Python Migration — 12 core modules migrated:
crypto.py,crypto_backend.py,crypto_enhanced.py,x25519_forward_secrecy.py,forward_secrecy.py,pq_hybrid.py,ratchet.py,double_ratchet.py,constant_time.py,frame_mac.py,streaming_crypto.py - Phase 4: Test Migration — 397 tests passing (383 protocol tests + 14 enforcement tests including 5 key lifecycle tests)
- Phase 5: CI Enforcement —
RUST_BACKEND_REQUIRED=1environment gate, import-ban linter, golden vector regression
derive_key_argon2id, derive_key_hkdf, hkdf_extract, hkdf_expand,
aes_gcm_encrypt, aes_gcm_decrypt, aes_ctr_crypt, hmac_sha256,
hmac_sha256_verify, sha256, x25519_generate_keypair, x25519_exchange,
x25519_public_from_private, constant_time_compare, secure_zero, secure_random
| Test Class | Purpose |
|---|---|
TestPythonCryptoBan |
AST scan for forbidden cryptography imports |
TestRustBackendRequired |
Verifies meow_crypto_rs imports successfully |
TestGoldenVectors |
SHA-256, HMAC-SHA256, AES-CTR frozen vectors |
TestHKDFGoldenVector |
HKDF-SHA256 frozen vectors |
TestConstantTimeRouting |
Verifies constant_time_compare() routes to Rust |
TestKeyLifecycle |
secure_zero, X25519 keygen/exchange, AES-GCM AAD |
- Constant-time comparisons: All auth tag/HMAC verification via
subtle::ConstantTimeEq - Secure zeroing: All secrets zeroized on drop via
zeroizecrate - No Python fallback: CI fails if Rust backend unavailable (
RUST_BACKEND_REQUIRED=1) - Golden vector parity: Python and Rust outputs byte-for-byte identical
ML-KEM-768 default with ML-KEM-1024 --paranoid option. Full PQXDH-style two-step HKDF transcript binding replaces the previous single-step derivation.
- Default changed from ML-KEM-1024 (Kyber1024) to ML-KEM-768 (Kyber768)
- ML-KEM-1024 retained as
--paranoidmode viapq_paranoid=True - New manifest version MEOW5 (
mode_byte=0x05) for ML-KEM-768 (ct=1088 bytes, pk=1184 bytes) - MEOW4 (
mode_byte=0x04) retained for backward-compatible ML-KEM-1024 (ct=1568 bytes)
- Extract:
PRK = HMAC-SHA256(salt=0x00*32, IKM=classical_ss || pq_ss) - Expand:
HKDF-Expand(PRK, info="meow_pqxdh_v1" || transcript_hash, L=32) - Replaces old single-step
HKDF(info="meow_hybrid_pq_v1")with no transcript binding - Transcript hash:
SHA256("meow_pqxdh_transcript_v1" || eph_pub || recv_classical_pub || recv_pq_pub || pq_ct) - All exchanged public values now cryptographically bound into key derivation
PQXDH_EXTRACT_SALT— 32 zero bytes for HMAC-SHA256 Extract stepPQXDH_INFO_PREFIX = "meow_pqxdh_v1"— HKDF-Expand info prefixPQXDH_TRANSCRIPT_DOMAIN = "meow_pqxdh_transcript_v1"— Transcript hash domainCLASSICAL_INFO = "meow_classical_only_v1"— Non-PQ fallback derivation
pack_manifest()accepts PQ ciphertext of 1088 (ML-KEM-768) or 1568 (ML-KEM-1024)unpack_manifest()determines PQ size from mode byte (MEOW5→1088, MEOW4→1568)- Trailing bytes validation prevents unconsumed data attacks
- Manifest sizes: MEOW5 base=1236B, MEOW4 base=1716B
CryptoConfig.kyber_variantdefault:"kyber768"(was"kyber1024")CryptoConfig.pq_paranoid/EncodingConfig.pq_paranoid: new bool flagsHighSecurityConfig: defaults topq_paranoid=True(ML-KEM-1024)encode.py: MEOW5 mode for default PQ, MEOW4 for paranoiddecode_gif.py: Auto-detects variant from ciphertext size
| Test Class | Tests |
|---|---|
TestPQConstants |
Size constants, algorithm strings, domain separation |
TestHybridKeyPairVariants |
ML-KEM-768/1024 key generation, paranoid selection |
TestPQXDHTranscriptBinding |
Transcript hash determinism, domain separation, binding |
TestEncapDecapRoundtrip |
768 roundtrip, 1024 roundtrip, cross-variant rejection |
TestTranscriptBindingSecurity |
Tampered ciphertext/ephemeral detection |
TestMODEMEOW5 |
Mode byte packing/unpacking, size validation, trailing bytes |
TestConfigDefaults |
Default variant, paranoid flag, encoding config |
TestCheckPQAvailable |
Availability detection for both variants |
Post-Compromise Security via periodic X25519 root key rotation. Compromise of ratchet state at frame N is healed within ≤K frames (rekey_interval). This is the air-gap equivalent of Signal's DH ratchet (not Signal; no equivalence claim).
- Every
rekey_intervalframes, the encoder generates a fresh X25519 ephemeral keypair - ECDH with receiver's long-term public key produces shared secret for root rotation
- Root key rotation:
new_root = HKDF(IKM=shared_secret, salt=old_root, info="meow_asym_rekey_root_v1" || epoch) - New chain derived from new root:
new_chain = HKDF(new_root, salt, "meow_asym_rekey_chain_v1") - Old root + chain zeroed (forward secrecy within epoch)
- Ephemeral public key (32 bytes) embedded in frame header (same slot as v1.x beacon)
ASYM_REKEY_ROOT_INFO = "meow_asym_rekey_root_v1"— Root rotation HKDF infoASYM_REKEY_CHAIN_INFO = "meow_asym_rekey_chain_v1"— Post-rekey chain derivationASYM_REKEY_KEM_INFO = "meow_asym_rekey_kem_v1"— ECDH shared secret KDFASYM_REKEY_ROOT_INIT_INFO = "meow_ratchet_root_store_v1"— Initial root storage
- Decoder extracts ephemeral keys from rekey frames before chain advancement
_advance_to()handles asymmetric rekeys at epoch boundaries during fast-forward- Rekey frames must be received before frames in their epoch (fountain codes handle loss)
- Epoch counter bound into HKDF info prevents cross-epoch replay attacks
- Added
root_key: Optional[bytearray](stored for asymmetric rekey operations) - Added
epoch: intcounter (tracks current asymmetric rekey epoch) zeroize()cleans both chain_key and root_key- Backward compatible:
root_key=Nonedefaults for existing code
- Without
receiver_public_key: falls back to plaintext beacon (v1.x, no PCS) - Without
rekey_interval: no rekey (base MSR v1.0 behavior)
| Test Class | Tests |
|---|---|
TestAsymmetricRekeyPrimitives |
ECDH roundtrip, ephemeral uniqueness, wrong-key, epoch binding |
TestRatchetStateV2 |
root_key storage, zeroization, step preservation, backward compat |
TestAsymmetricRekeyRoundtrip |
Multi-epoch roundtrip, rekey at boundaries |
TestPostCompromiseSecurity |
PCS: compromised state cannot decrypt post-rekey |
TestOutOfOrderDecoding |
Epoch-aware OOO, missing-rekey rejection |
TestRollbackResistance |
Epoch binding, old root cannot derive future chains |
TestSignalComparison |
Root rotation, chain isolation, PCS healing latency |
TestEdgeCases |
Interval=1, disabled, large epochs, single frame |
TestTamperDetectionV2 |
Modified ephemeral rejected, replay rejected |
Three Signal-inspired security hardening features for the MEOW Symmetric Ratchet:
- Frame indices are now XOR-masked with HKDF-derived pseudorandom masks (
HEADER_ENC_INFO = "meow_ratchet_header_v1",HEADER_MASK_INFO = "meow_header_mask_v1") - Observers cannot determine frame ordering, count consumed frames, or correlate frames across sessions
- Mirrors Signal's double-encrypted header which hides the chain position
- Decoder precomputes encrypted-index → real-index lookup table during initialization (O(N) init, O(1) per-frame)
- HMAC-SHA256 commitment tag (16 bytes,
COMMIT_TAG_SIZE = 16) appended to each frame body - Prevents key commitment attacks where AES-GCM allows two different keys to both produce valid decryptions with different plaintexts (Grubbs et al. 2017)
- Commitment uses the per-frame
mac_key(already derived via domain-separated HKDF) - Decoder verifies commitment BEFORE attempting AES-GCM decryption (fail-fast)
[encrypted_index(4)] [commitment_tag(16)] [beacon?(32)] [AES-GCM ciphertext + tag(16)]
HEADER_ENC_INFO = b"meow_ratchet_header_v1"— header key derivationHEADER_MASK_INFO = b"meow_header_mask_v1"— per-frame XOR mask derivationCOMMIT_TAG_SIZE = 16— truncated HMAC-SHA256 commitment
- 142 ratchet unit tests (+22 from v1.1): header encryption, key commitment, Signal-inspired hardening
- 23 E2E ratchet pipeline tests (all passing with hardened frame format)
- New test classes:
TestHeaderEncryption,TestKeyCommitment,TestSignalParityHardening
- RATCHET_PROTOCOL.md updated to v1.2 with §8.3 (Header Encryption) and §8.4 (Key Commitment)
- MSR_V1_ADVERSARIAL_ANALYSIS.md updated with v1.2 hardening notes and Attack 8 mitigation
- Hardening recommendations: items 1, 2, 3 marked as DONE
- Signal comparison table updated: header encryption ✓, key commitment ✓
Second hostile crypto audit identified 4 remaining weaknesses; all remediated.
_register_nonce_use()now usessynthetic_iv_modeflag instead ofprecomputed_key_mode- Synthetic IV (HKDF-derived from key + plaintext hash + salt) provides SIV property: same plaintext → same nonce (intentional), different plaintext → different nonce (guaranteed)
- Removed misleading "consider GCM-SIV" warning for HSM mode
derive_shared_secret()HKDF info now binds:"meow_fs_bound_v2:"+ protocol_version (1B) + mode_flags (1B) + SHA-256(receiver_public) (32B) + ephemeral_public (32B) + pq_ciphertext_hash (32B)- Mode flags: FS=0x01, PQ=0x02, duress=0x04
- All callers updated: crypto.py (encrypt/decrypt/derive_key_for_manifest), decode_gif.py, crypto_DEBUG.py
- Added
mode_bytefield to Manifest dataclass (0x02=MEOW2, 0x03=MEOW3, 0x04=MEOW4, 0x80=duress flag) - Mode byte included in AES-GCM AAD and HMAC (via
pack_manifest_core) unpack_manifest()validates mode byte against actual content (rejects mismatches)- New manifest sizes = legacy + 1 byte; backward compat preserved for legacy manifests
- Changed from
DeprecationWarningtoraise RuntimeErroron import - Prevents silent use of insecure XOR key combiner
- 29 tests in
test_audit_fixes.py(16 original + 13 new v2 tests) - 455 total tests pass across all test files
All 9 FAIL findings from the hostile crypto audit (OPUS-AUDIT.md) have been remediated, plus 5 additional requirements from cross-audit review.
- A1: Nonce guard — LRU eviction (10K cap, no full-cache clear) + HKDF-derived synthetic IV for HSM/precomputed_key mode
- A2: AAD bypass removed —
decrypt_to_raw()raisesValueErrorwhenorig_len/comp_len/sha256areNone; noaad=Nonefallback - C3: Transcript binding —
derive_shared_secret()now acceptsprotocol_versionparameter, bound into HKDF info string - D1: PQ HKDF salt — Changed from
b""toephemeral_public_bytesinpq_hybrid.py; XOR combiner inpq_crypto_real.pydeprecated withDeprecationWarning - D3: PQ downgrade detection — Clear
RuntimeErrormessage distinguishing PQ downgrade from wrong password - E1: Frame MAC fail-closed —
decode_gif.pyraisesValueErroron invalid manifest frame MAC (previously silently disabled verification) - E2/E3: Fountain reorder/truncation — Accepted by-design; documented as explicit non-goals in threat model
- PQ pipeline end-to-end —
encode.pycallshybrid_encapsulate()whenuse_pq=True;decode_gif.pycallshybrid_decapsulate()when PQ ciphertext present in manifest - Parameter drift unified — All modules now use ML-KEM-1024 (1568-byte ciphertext) consistently:
pq_crypto_real.pydefault changed fromkyber768tokyber1024,crypto_DEBUG.pyupdated throughout - AAD completeness —
build_canonical_aad()now acceptsephemeral_public_keyandpq_ciphertextparameters; documented thatcipher_len/block_size/k_blocksare covered by HMAC (not AAD, due to circular dependency) - HSM synthetic IV — HKDF-derived deterministic nonce (
HKDF(key + comp_hash, salt, "meow-synthetic-nonce-v1")) forprecomputed_keymode prevents nonce reuse across restarts - E2E reliability harness — New
tests/test_e2e_crypto_fountain.py(30 tests) covering full encode→fountain→decode roundtrip with frame loss, reordering, duplicates, combined hostile conditions
tests/test_audit_fixes.py— 14 tests verifying all OPUS-AUDIT remediationstests/test_e2e_crypto_fountain.py— 30 tests for crypto+fountain end-to-end pipeline
- Updated manifest sizes: MEOW4 1235→1715, MEOW4+duress 1267→1747
- Updated PQ ciphertext size: 1088→1568 (ML-KEM-1024) across all docs
- Removed stale "not wired up" implementation gap notes
- Added REMEDIATED status to all OPUS-AUDIT FAIL findings
- Updated HKDF salt description from
b""toephemeral_public_bytes - Updated AAD field list to include
ephemeral_public_keyandpq_ciphertext - Comprehensive stale-claim sweep across 13 .md files (25 fixes):
- ARCHITECTURE.md:
optional Kyber→ML-KEM-1024 hybrid,XOR + HKDF→Concatenation + HKDF,pq_crypto_real.py→pq_hybrid.py - SECURITY_AUDIT.md: nonce cache
1024 entries→LRU 10K, domain separation info string updated - PROTOCOL.md: Added
pq_ciphertextto AAD specification - SECURITY_INVARIANTS.md: nonce cache + synthetic IV,
pq_ciphertextin AAD code block - THREAT_MODEL.md: PQ implementation reference →
pq_hybrid.py - AUDITOR_README.md:
ML-KEM-768/1024→ML-KEM-1024 - core-modules.md:
pq_crypto_real.pymarked DEPRECATED - README.md: PQ mode
DEFAULT ON→opt-in, requires receiver PQ public key - AUDIT_REPORT_2026-01-28.md: nonce cache code annotated with post-audit changes
- VERUS_FRAME_MAC_STATUS.md: forward secrecy info string update noted
- OPUS-AUDIT.md: Q1/Q2 self-audit answers updated to REMEDIATED, exploit narratives annotated
- ARCHITECTURE.md:
- New Library:
examples/fountain-codes.js(414 lines)FountainEncoder: Generates Luby Transform droplets from source dataFountainDecoder: Reconstructs via belief propagationDroplet: Pack/unpack for QR transmission (seed + block indices + XOR data)RobustSolitonDistribution: Optimal degree selection (c=0.1, δ=0.5)SeededRandom: Deterministic PRNG for reproducible block selection
- Production-ready: No dependencies, works in all modern browsers
- Testing:
examples/test_fountain.htmlwith 5 comprehensive test cases
- Encoding: Payloads >2500 bytes automatically use fountain encoding
- Each QR frame contains:
FOUNTAIN:<k>:<block_size>:<length>:<droplet_b64> - Generates k×1.5 droplets (50% redundancy = 33% frame loss tolerance)
- Systematic optimization: First 2k droplets are degree-1 for fast decode
- Each QR frame contains:
- Decoding: Webcam scanner collects droplets progressively
- Real-time progress: "Collecting: 8 scanned, 80% decoded (4/5 blocks)"
- Automatic duplicate detection (seed tracking)
- Success when enough droplets: "✅ Decoded from 8/5+ droplets!"
- Frame Format: Base64-encoded droplet with metadata header
- Before: Multi-frame animated QR never worked reliably
- Simple sequential chunking: ANY missed frame = total failure
- Phone camera capture: autofocus lag, motion blur, low FPS → unusable
- After: Works in real-world conditions
- Tolerates 33% frame loss (can miss 1 in 3 frames)
- Hand-held phone scanning works
- Visual progress feedback for users
- New:
docs/FOUNTAIN_CODES_INTEGRATION.md(400+ lines)- Complete technical specification
- Implementation details, security analysis
- Performance characteristics, usage examples
- Debugging guide and troubleshooting
- Updated:
examples/README.md, mainREADME.md,QUICKSTART.md
- 🔐 Standard Mode: AES-256-GCM + Argon2id with configurable security levels
- 🔑 Forward Secrecy Mode: X25519 ephemeral key exchange with full key management
- 🔮 Post-Quantum Mode: ML-KEM-1024 + X25519 hybrid encryption (experimental)
- 🐱 Schrödinger Mode: Dual-secret plausible deniability
- 🖼️ Stego Mode: LSB steganography embedding
- 📹 Webcam Mode: Live QR scanner with real-time decode
- 🚨 Duress Mode: Panic password with localStorage key destruction
- 😺 Cat Mode: Blinking cat eyes visual encoding
- 4 Argon2id security levels: Fast (64 MiB/3), Standard (128 MiB/8), High (256 MiB/15), Paranoid (512 MiB/20)
- "Paranoid" level matches CLI security parameters exactly
- UI dropdown in Standard mode encryption panel
- New
wasm-pqCargo feature flag enabling ML-KEM-1024 - WASM bindings:
mlkem_generate_keypair(),mlkem_encapsulate(),mlkem_decapsulate() - Hybrid functions:
encrypt_hybrid_pq(),decrypt_hybrid_pq() pq_available()runtime check function- Uses
getrandom0.4 withwasm_jsfeature for WASM compatibility
crypto-worker.jshandles all CPU-intensive crypto off main thread- Added handlers for X25519, ML-KEM, and hybrid PQ operations
- Fallback to main thread if workers unavailable
- New
docs/WASM_SECURITY.md— comprehensive WASM security analysis - Updated README.md with Web Demo vs CLI feature parity table
- Updated QUICKSTART.md with browser-first option
- Updated examples/README.md with all 8 modes documented
- Updated PYTHONANYWHERE_HOSTING.md with PQ build instructions
- Updated docs/USAGE.md with browser workflow section
- Updated docs/ARCHITECTURE.md with WASM architecture diagram
- Updated SECURITY.md with browser-specific security considerations
- New
make build-wasm-pqtarget for post-quantum WASM builds - Updated help text for all WASM targets
- Hash-Pinned Dependencies: Generated
requirements.lockandrequirements-dev.lockwith SHA256 hashes- All CI workflows updated to use
pip install --require-hashes -r requirements.lock - Dockerfile updated for reproducible builds
- All CI workflows updated to use
- Signed Releases Workflow: New
.github/workflows/release.ymlwith Sigstore keyless signing- SLSA Level 3 provenance generation
- Automatic artifact attestation on version tags
- CODEOWNERS: Added
.github/CODEOWNERSfor mandatory code review on security-critical paths - Dependabot Enhancements: Grouped updates, root Cargo.toml monitoring
- RUSTSEC-2026-0009: Updated
timecrate 0.3.46 → 0.3.47 (local time offset vulnerability) - RUSTSEC-2026-0010: Updated
paste64crate 0.1.3 → 0.1.4 (paste! macro hygiene fix) - RUSTSEC-2022-0093: Documented
rsacrate advisory (no upstream fix, Marvin attack theoretical)- Added
deny.tomlexception with security rationale
- Added
- TLA+ Formal Verification: Fixed jar path in
formal-verification.yml(wasformal/tla/tla2tools.jar, nowtla2tools.jaraftercd formal/tla)
- New Module:
meow_decoder/canonical_aad.pybuild_canonical_aad()— deterministicversion_byte || fixed-order manifest fields- Backward compatible with MEOW2/MEOW3/MEOW4 manifests
- Test vectors in
tests/test_canonical_aad.py(10 tests)
- Gate 1: Fast pytest (excludes
@pytest.mark.slow) - Gate 2: Security coverage ≥ 85 % (TIER 1 crypto modules)
- Gate 3: Lint + type check (flake8, mypy, black --check)
- All three gates required for PR merge
codecov.ymlupdated: coverage checks fail only onpushtomain, not on PRs
- Removed or justified every
continue-on-error: trueinsecurity-ci.yml
- New Test:
tests/test_timing_harness.py(@pytest.mark.security)- Statistical timing comparison: correct vs wrong password
- Statistical timing comparison: duress vs real password
- Configurable threshold with skip on inconsistent CI runners
- New Doc:
docs/SECURE_USAGE_CHECKLIST.md— OPSEC guidance covering encoding, transfer, decoding, storage, and after-use cleanup - Cross-linked from README.md, QUICKSTART.md, THREAT_MODEL.md
- New Module:
meow_decoder/tamper_report.pyTamperReportclass withFrameResultdataclass- ASCII timeline rendering (█ valid, ▒ mixed, ░ invalid, · no data)
- Sliding-window cluster detection for suspicious failure patterns
- JSON export via
to_json()
- CLI flags:
--tamper-reportand--tamper-report-jsononmeow-decode-gif tests/test_tamper_report.py(19 tests)
- New Doc:
mobile/ARCHITECTURE.md— bridge architecture, wire protocol, security boundaries - New Module:
mobile/bridge/protocol.py— 6 JSON message types, parsers,MAX_FRAME_BYTES - Reference Impl:
mobile/react-native/MeowScanner.tsx+useBridge.ts - Phone stays "dumb" — all crypto on CLI side
tests/test_bridge_protocol.py(21 tests)
- New Module:
meow_decoder/mobile_bridge.py— CLI handler for phone→CLI scanning--mobile-bridgeflag to enable bridge mode--bridge-mode {stdin,websocket,file}for transport selection--bridge-portfor WebSocket server port (default 8765)--input-framesfor file-based frame import--output-requestto generate capture request JSON for mobile app
- Full integration with
meow-decode-giffor stdin, WebSocket, and file input modes
- ST-1: Quarantined duplicate crypto paths →
meow_decoder/experimental/ - ST-2: Manifest numeric bounds + decompression-bomb protection (
tests/test_manifest_bounds.py, 17 tests) - ST-3: Fixed memory-zeroing claims across all docs (Python = "best-effort", Rust = "guaranteed")
- ST-4: Defined pytest markers (
security,adversarial,crypto,fuzz,slow,integration,cat) +--strict-markers - ST-5: Security coverage PR gate ≥ 85 % for TIER 1 modules
- ST-6:
--self-testCLI command with roundtrip verification - ST-7:
docs/ARGON2ID_BENCHMARKS.md— Argon2id tuning guide with low-end hardware timings - ST-8: CLI wiring for HSM/YubiKey/TPM (
--hsm-slot,--tpm-derive,--hardware-auto)
- New Doc:
OpenSSFImprovements.md— 5-phase plan targeting 7.0–8.0+ score- Phase 1: workflow permissions, dependabot.yml, binary artifact removal
- Phase 2: SHA-pin all GitHub Actions (~60+ refs), branch protection
- Phase 3: signed releases, CII badge, maintained score
This release consolidates all internal development milestones (v5.x, v6.x) into the first public release: v1.0.0 (INTERNAL REVIEW — no external audit).
All features, security hardening, and tests from internal versions 5.0.0 through 5.9.0 are included in this release.
- New Module:
timelock_duress.py(~600 lines) - TimeLockPuzzle: Iterated SHA-256 hashing for delayed decryption
- Sequential computation (non-parallelizable)
- Configurable time parameters (seconds to hours)
- Perfect for dead drops and timed releases
- CountdownDuress: Check-in based trigger
- Automatic key destruction if check-in missed
- Grace period configuration
- State persistence with JSON serialization
- DeadManSwitch: Renewal-based trigger
- Requires periodic renewal to keep alive
- Configurable expiry intervals
- Emergency wipe on lapse
- CLI Interface: Full command-line support for all features
- New Test Module:
tests/test_sidechannel.py(~500 lines) - TimingAnalyzer: Statistical framework for timing measurements
- Coefficient of variation analysis
- Mean/std timing profiling
- TestConstantTimeComparison: Verifies constant-time password/HMAC comparison
- TestFrameMACTiming: Tests frame MAC verification consistency
- TestKeyDerivationTiming: Validates Argon2id timing stability
- TestDuressTimingEqualization: Ensures duress detection has no timing leak
- TestSecureMemoryZeroing: Verifies secure_zero_memory() works
- TestNoEarlyExit: Confirms no length-based early exit leakage
- TestRustBackendSideChannel: Validates subtle/zeroize crate usage
- Makefile Targets:
stealth-build: Deniable distribution (strips metadata, randomizes)sidechannel-test: Run side-channel timing testssecurity-test: Combined security test suitesupply-chain-audit: pip-audit + cargo audit + cargo deny
- Organized Help: New "Security" section in
make help
- Time-lock puzzles provide coercion resistance
- Constant-time operations verified via automated tests
- Supply-chain security via multi-tool auditing
- Stealth distribution mode for operational security
- 7 new test classes for side-channel resistance
- All existing tests continue to pass
- ML-KEM-1024: Upgraded from ML-KEM-768 to highest security level (NIST FIPS 203)
- Dilithium3: Manifest signatures for quantum-resistant authentication (FIPS 204)
- Hybrid Mode: X25519 + ML-KEM-1024 (secure if EITHER primitive holds)
- Default ON: No longer optional - quantum-ready always
- 512 MiB Memory: 8x OWASP minimum (was already upgraded in 5.6.0)
- 20 Iterations: ~5-10 seconds per attempt
- Brute-Force Math: 10^35 years for 20-char password on RTX 4090 farm
- pip-audit: Python dependency vulnerability scanning
- cargo-audit: Rust dependency vulnerability scanning
- mutmut: Mutation testing for crypto-critical code
- Bandit: Static security analysis for Python
- Constant-Time: Uses
subtlecrate for timing attack resistance - Memory Zeroing: Secure zeroize on sensitive data
- Kyber/Dilithium: Native PQ crypto support
config.py:enable_pq=Truenow default (was False)config.py:kyber_variant="kyber1024"now default (was "kyber768")pq_hybrid.py: Uses Kyber1024 (1568-byte public keys)- Forward secrecy now on by default
- Updated THREAT_MODEL.md with new brute-force mathematics
- Updated README.md to reflect production-ready status
- All security tests passing with new parameters
- GitHub Actions Workflow:
.github/workflows/fuzz.ymlfor CI fuzzing - Atheris Integration: Google's coverage-guided Python fuzzer
- AFL++ Support: Native AFL++ fuzzing for maximum coverage
- Fuzz Targets:
fuzz_manifest.py: Manifest parsing (edge cases, corruption)fuzz_fountain.py: Fountain code decoding (droplet parsing)fuzz_crypto.py: Key derivation, decryption error handling
- Corpus Generation:
seed_corpus.pycreates valid samples for mutation - Crash Detection: Automatic artifact upload on crashes
- Weekly Scheduled Runs: Deep fuzzing on Sundays
- New Module:
double_ratchet.py(~600 lines) - DH Ratchet: X25519 key rotation for forward secrecy
- Symmetric Ratchet: HKDF-based chain key derivation
- Message Keys: Per-message key derivation prevents replay
- Out-of-Order Support: Handles missed/reordered messages
- State Serialization: Save/restore session state
- Clowder Integration:
ClowderSessionfor multi-party streams
- Forward Secrecy: Past messages protected from key compromise
- Future Secrecy: System heals after DH ratchet step
- Break-in Recovery: Temporary compromise heals automatically
- DoS Protection:
MAX_SKIP=1000limits skipped key storage
- 16 new tests for fuzzing and double ratchet
- All 63 tests passing (47 existing + 16 new)
- 512 MiB Memory: 8x OWASP recommendation (was 256 MiB)
- 20 Iterations: 6.7x OWASP minimum (was 10)
- ~5-10 Second Delay: Intentionally slow for maximum GPU/ASIC resistance
- Updated in:
crypto.py,crypto_enhanced.py,config.py
- New Module:
pq_signatures.pyfor manifest authentication - Dilithium3: NIST security level 3 (quantum-resistant)
- Ed25519 Fallback: Classical signatures when liboqs unavailable
- Hybrid Mode: Ed25519 + Dilithium3 for defense-in-depth
- Key Management: Generate, save, load signing keypairs
- Manifest Signing: Cryptographic proof of manifest authenticity
- New Document:
docs/ROADMAP.mdwith complete security roadmap - Short-term: AFL++ fuzzing, double-ratchet protocol
- Medium-term: Rust crypto backend, HSM integration
- Long-term: Formal verification, third-party audit
- Argon2id memory: 256 MiB → 512 MiB
- Argon2id iterations: 10 → 20
- Key derivation now takes 5-10 seconds (security feature)
- Duress Passwords: Configure distress signal password that triggers secure wipe
- Constant-Time Verification: Timing-safe password comparison prevents side-channel attacks
- Timing Equalization: 100-500ms random delays mask operation timing
- Secure Memory Wipe: Best-effort 3-pass overwrite (zeros, ones, random) for key material (Python GC may retain copies; Rust backend provides guaranteed zeroing)
- Resume File Destruction: Automatic cleanup of recovery files under duress
- Multi-Source Entropy Pool: Combines 6+ entropy sources for maximum randomness
- System Entropy: os.urandom + /dev/urandom for base randomness
- Timing Jitter: High-resolution timing noise from CPU operations
- Environment State: Process/memory/network statistics as entropy
- Hardware RNG: Intel RDRAND/RDSEED when available
- Webcam Noise: Optional camera sensor noise for additional entropy
- HKDF Mixing: Cryptographic mixing of all sources for uniform distribution
- Unlimited Realities: Support for up to 16 concurrent secrets (was 2)
- Round-Robin Interleaving: Cryptographically shuffled block placement
- Statistical Indistinguishability: All realities pass forensic analysis
- Merkle Root Integrity: Cryptographic verification of block integrity
- Proper Decryption: Cipher length tracking for accurate block recovery
- TPM 2.0 Support: Key derivation via Trusted Platform Module
- YubiKey Support: Hardware key derivation via ykman/PKCS#11
- Smart Card Support: PKCS#11 interface for security tokens
- Intel SGX Detection: Enclave support detection (future use)
- Graceful Fallback: Software-only mode when hardware unavailable
- PQ Crypto Default ON: Post-quantum cryptography now enabled by default
- Hardware Auto-Detect: Automatic detection of available security hardware
- Enhanced Entropy Default ON: Multi-source entropy collection enabled
duress_mode.py: Coercion-resistant password handling (359 lines)entropy_boost.py: Multi-source entropy collection (419 lines)multi_secret.py: N-level Schrödinger encoder/decoder (643 lines)hardware_keys.py: TPM/YubiKey/smart card integration (566 lines)
- Timing-safe password comparison (secrets.compare_digest)
- Constant-time HMAC verification
- Memory locking with mlock() where available
- Secure memory zeroing before deallocation
- Hardware-backed key derivation when available
- Statistical indistinguishability for N secrets
config.py: Added enable_pq=True (default), plus duress/hardware/entropy options
- All 4 new modules tested and verified working
- Multi-secret encode/decode roundtrip confirmed
- Hardware detection graceful fallback verified
- Entropy generation produces 32+ bytes successfully
- Quantum Superposition: Encode TWO secrets in ONE GIF with true plausible deniability
- Quantum Noise Derivation: XOR of both password hashes creates shared entanglement key
- Reality Entanglement: Cryptographic mixing makes both secrets statistically indistinguishable
- Observer Collapse: One password reveals one reality, other remains forever unprovable
- Automatic Decoy Generation: Convincing innocent files (vacation photos, shopping lists, cat manifesto)
- Forensic Resistance: Statistical tests confirm indistinguishability (entropy, chi-square, byte frequency)
quantum_mixer.py: Core cryptographic primitives for superpositionschrodinger_encode.py: Dual-secret encoderschrodinger_decode.py: Reality collapse decoderdecoy_generator.py: Automatic convincing decoy generation
- Statistical indistinguishability (entropy diff < 0.003, chi-square < 300)
- Cryptographic binding via quantum noise (requires both passwords)
- Merkle root integrity over entangled blocks
- Constant-time collapse operations
schrodinger_encode: Encode dual realities with auto-decoy--real: Real secret file--decoy: Decoy file (auto-generated if omitted)--real-password: Password for real secret--decoy-password: Password for decoy
schrodinger_decode: Collapse superposition to one reality--reality: Force specific reality (A or B)
- 7/7 quantum mixer tests passing (100%)
- Quantum noise derivation ✓
- Entanglement & collapse ✓
- Statistical indistinguishability ✓
- Merkle root integrity ✓
- End-to-end encoding ✓
- Decoy generation ✓
- Forensic resistance ✓
- Full roundtrip (encode + decode both realities) ✓
- SCHRODINGER.md: Complete philosophy and architecture
- README updated with quantum examples
- Use case scenarios (border crossing, coercion resistance, dead man's switch)
- Decoder architecture: Store encryption parameters in metadata for proper roundtrip
- Manifest format: Updated to 382 bytes with complete encryption parameters
- Full E2E roundtrip now works correctly (100% test success rate)
- Cleaned up
__init__.py: Removed main entry point imports to eliminate RuntimeWarning - Lightweight imports: Only config, crypto primitives, and quantum mixer at import time
- All v5.3.0 features preserved with 100% backward compatibility
- No regressions: 19/19 existing tests still pass
- Combined test score: 26/26 (100%)
- Forward Secrecy: X25519 ephemeral keys protect past messages from future password compromise
- Frame-Level MACs: Per-frame authentication prevents DoS attacks via invalid frame rejection
- Constant-Time Operations: Timing attack resistance for password/MAC verification
- Metadata Obfuscation: Length padding hides true file size
- Enhanced AAD: Comprehensive manifest integrity protection
- Ephemeral keys generated per encryption, destroyed after use
- 8-byte MAC per QR frame with constant-time verification
- Random timing delays (1-5ms) prevent timing side-channels
- Size classes (powers of 2) prevent size fingerprinting
- AAD prevents tampering with all metadata fields
--generate-keys: Generate receiver keypair for forward secrecy--receiver-pubkey: Enable forward secrecy mode (encode)--receiver-privkey: Decrypt with forward secrecy (decode)
- Version consistency across all files (5.3.0)
- Console script entrypoints corrected
- Package imports work correctly
- Test collection fixed
Initial production release with:
- AES-256-GCM encryption
- Argon2id key derivation
- QR code fountain coding
- Dual secret support (Schrödinger mode)