zupt/AUDIT.md
Cristian Cezar Moisés 5050570b23 v5.0.0: version bump, audit fixes, documentation overhaul
Version bumped to 5.0.0 across include/zupt.h, all packaging recipes, man
page, and docs.

Audit fixes (pre-5.0.0 review):
- src/zupt_format.c: overflow-safe bound in the solid-mode `test` path
  (off+sz could wrap and drive an OOB read in zupt_xxh64 on a crafted archive;
  the extract path was already hardened, the test path was not).
- gui: run_async now marshals the completion callback onto the GUI thread with
  QueuedConnection (a bare functor connected DirectConnection and touched
  widgets off the worker thread); Extract auto-detect note survives the log
  clear via a new `info` param.
- .github/workflows/ci.yml: trigger on `master` (was main/develop, so CI never
  ran); `make dist` tarball is vaptvupt-*.tar.gz not zupt-*; the ASAN PQ
  round-trip uses native --pq (was --pq-sdk, which fails on the source-only
  build and blocked the release job).

Documentation:
- New AUDIT.md (methodology, FIPS 203 conformance validation, findings, repro).
- CHANGELOG 5.0.0 entry covers the FIPS 203 conformance fix + BREAKING note and
  the GUI/CLI/security/packaging work.
- README "What's new in 5.0.0", download tables (incl. Windows/macOS/BSD +
  portable GUI), version-history row.
- SECURITY.md + THREAT_MODEL.md: ML-KEM-768 documented as FIPS 203, validated
  byte-for-byte against OpenSSL 3.5.
- Accuracy fixes: man page (--kdf default is PBKDF2 on source-only; codec
  2.60.4), rpm %description, debian control/copyright, homebrew header
  (no vendored library on source-only builds).

make check 16/16 (FIPS 203 conformance 3/3, all distro-safe checks).
2026-07-10 17:22:02 -03:00

6 KiB

VaptVupt — Security Audit

This document records the security review of VaptVupt: what is checked, how, the findings and their resolutions, and how to reproduce the checks. It complements SECURITY.md (policy + primitives) and THREAT_MODEL.md (what is and isn't defended).

Scope: the pure-C11 CLI (src/, include/) and the PySide6/PyQt6 GUI (gui/src/zupt_gui.py). Out of scope: the optional, separately distributed libzuptsdk / libpqvaptvupt binaries (only present in a make WITH_SDK=1 build); the shipped source-only build contains no vendored binaries.

Not independently certified. This is the project's own structured review, not a third-party accredited audit. Treat it as "reviewed, with reproducible evidence" and do your own review for high-assurance use.

Methodology

Technique What it covers Where
Cryptographic conformance vs an independent reference ML-KEM-768 is validated byte-for-byte against OpenSSL 3.5's FIPS 203 ML-KEM-768 — deterministic keygen ek equality plus shared-secret agreement in both cross-decapsulation directions. tests/test_mlkem_fips203.sh, in make check
NIST/RFC known-answer vectors SHA-256 (FIPS 180-4), SHA-3/SHAKE (FIPS 202), AES-256-CTR (SP 800-38A F.5.5/F.5.6), HMAC-SHA256 (RFC 4231), X25519 (RFC 7748), ML-KEM-768, PBKDF2. tests/test_vectors.c
Byte-level tamper sweep Every byte position of a representative archive is flipped and re-opened; zero silent-accepts required (F-09). tests/ byte-sweep
Authenticated-encryption fuzzing HMAC / integrity-trailer fuzz over many trials (F-06, F-08). tests/
Constant-time measurement dudect-style Welch t-test on the MAC-tag compare and the ML-KEM FO implicit-rejection compare (the two decapsulation-oracle-sensitive paths). tests/test_ct_timing.*
Memory-safety sanitizers ASan + UBSan builds; exact-size decode cases; crafted-input decode. make test-asan
Static analysis cppcheck (warning/style/performance) on the first-party sources; strict -Wall -Wextra -Wpedantic -Werror gcc + clang matrix. CI
Formal annotations Frama-C/ACSL contracts on memory-safety-critical functions; 5 Jasmin-verified constant-time assembly routines (x86_64). include/zupt_acsl.h, jasmin/
Adversarial multi-agent review Independent reviewers per dimension (crypto, parser/memory-safety, CLI, GUI↔CLI contract, packaging), each finding then adversarially refuted before it is accepted. manual, per release

Cryptographic conformance

  • ML-KEM-768 — genuine FIPS 203 (v5.0.0). Earlier releases shipped round-3 CRYSTALS-Kyber under a "FIPS 203" label; it was self-consistent and secure as an IND-CCA2 KEM but not interoperable with a compliant ML-KEM. Validating against OpenSSL 3.5 revealed three deviations — a transposed matrix-Â sampling convention (in both K-PKE.KeyGen and K-PKE.Encrypt), the round-3 final KDF, and the implicit-rejection domain. All three were fixed and the result is now byte-for-byte interoperable with OpenSSL in both directions. A permanent conformance test guards against regression. This changed the shared secret, so it is a wire-breaking change for --pq/--pq-only archives (see CHANGELOG 5.0.0 BREAKING).
  • Hybrid is the flagship. --pq combines ML-KEM-768 with X25519 through a SHA3-512 combiner; the archive key is secure if either primitive holds — the strongest real-world posture and the recommended default. --pq-only offers pure ML-KEM-768 for single-primitive compliance mandates.
  • Envelope. AES-256-CTR with a fresh random 128-bit nonce per block (the dedup keystream-reuse bug is fixed and regression-tested), HMAC-SHA256 Encrypt-then-MAC verified before any decryption, and an archive-integrity trailer over the header/footer.

Notable findings and resolutions (recent)

Sev Finding Resolution
High ML-KEM-768 not FIPS 203-conformant / not interoperable Fixed (transpose + KDF); validated vs OpenSSL; permanent conformance test
High compress -p out.zupt f1 f2 overwrote an input file (data loss, exit 0) Refuse to overwrite an existing non-.zupt output without -y/--force; self-overwrite guard
High compress out.zupt dir -p pw wrote an unencrypted archive (exit 0) Error on a misplaced option after the archive (-- escape available)
Critical AES-CTR keystream reuse across --dedup blocks (many-time-pad) Fresh random per-block nonce; regression test
Medium Heap OOB read in the AVX2 decoder fast path on crafted input Bound the 2-/3-byte offset read like the scalar tail path
Medium GUI defaulted to SDK modes absent from the source-only build (unusable) Reworked to native --pq/--pq-only; SDK shown only when supported
Low Hybrid-decrypt did not wipe secret buffers on key-read failure Wipe on the error path (matches the pq-only path)
Low Untruthful banner (Argon2id-default / /zupt URL) on source-only builds Build-aware, accurate version/help output
Critical* Packaging (debian/rules, aur, nix, homebrew, opensuse) would fail a source-only build Removed vendored-lib/AUDIT.md steps, fixed URLs, added completions

* build-time failure, not a runtime security issue.

Known limitations / non-goals

  • No protection against a compromised endpoint, a weak password, or key custody failures (see THREAT_MODEL.md).
  • Metadata (total archive size, block count) is observable.
  • The review is reproducible but not third-party certified.

Reproducing

make check                 # vectors, tamper sweep, FIPS 203 conformance, guards
make test-asan             # ASan + UBSan
bash tests/test_mlkem_fips203.sh   # FIPS 203 interop vs OpenSSL (needs openssl 3.5+)

FIPS 203 conformance needs an ML-KEM-capable OpenSSL (3.5+); the test skips gracefully otherwise (e.g. inside a distro package build).