release: restore ZUPT and harden source-only 5.2.2

This commit is contained in:
Cristian Cezar Moisés 2026-08-31 14:14:36 -03:00
commit ff99770bd0
205 changed files with 19627 additions and 13215 deletions

View file

@ -1,328 +1,298 @@
# VaptVupt threat model
Plain-English description of what VaptVupt protects against, what it
doesn't, and what assumptions you're making when you use it.
This document is for users and downstream packagers. Read it before
trusting VaptVupt with anything you can't afford to lose.
---
## TL;DR
VaptVupt is designed for at-rest backup encryption by someone who
controls the machine doing the encryption and the machine doing the
extraction. It is not a network protocol, a multi-party scheme, or
a substitute for full-disk encryption.
| Use case | VaptVupt is appropriate? |
|---|---|
| Backing up files to an untrusted cloud (S3, Backblaze, Google Drive) | Yes |
| Backing up a disk image to external media you might lose | Yes |
| Long-term archival of personal/business data | Yes |
| Sharing an encrypted archive with someone you trust to handle the key | Yes, with care (see "Key distribution" below) |
| Real-time encrypted communication | No (use Signal, age, or TLS) |
| Multi-party access (n-of-m) | No (no threshold scheme) |
| Hiding the existence of an archive (steganography) | No (archive header has fixed magic bytes) |
| Protecting against a hostile machine you're encrypting on | No (a compromised host can read plaintext before encryption) |
---
## Modes referenced in this document
- `-p` / password mode: symmetric encryption with a key derived from a
password. The default build derives the key with PBKDF2-SHA256
(600k iterations). Argon2id is available only in an upstream
`make WITH_SDK=1` build against the separately distributed
libraries.
- `--pq`: native post-quantum **hybrid** mode (ML-KEM-768 + X25519), the
recommended PQ mode in the default build. The ML-KEM-768 implementation
is in-tree.
- `--pq-only`: native **full/pure** post-quantum mode (ML-KEM-768 only, no
X25519), also in the default build. For compliance postures that mandate a
single NIST-standardised PQ primitive with no classical KEM in the envelope.
Its threat profile differs from `--pq` in exactly one axis: it has no
classical fallback, so a break of ML-KEM-768 alone breaks the archive
(see §5 and "Cryptographic assumptions").
- `--pq-sdk` / `--pq-box`: optional post-quantum modes backed by the
separately distributed `libzuptsdk` / `libpqvaptvupt` libraries.
Available only in a `make WITH_SDK=1` build. Key files for these
modes are produced by `vaptvupt keygen --sdk`, also SDK-only.
---
## What VaptVupt protects against
### 1. Confidentiality of archive contents (encrypted mode)
An attacker with read access to the archive bytes cannot recover
plaintext file contents, file names, file sizes, file modes, or
embedded comments without the key/password, assuming:
- The chosen mode is one of the encrypted modes (`-p`, `--pq`, or the
optional `--pq-sdk` / `--pq-box`)
- The password is strong enough to resist offline brute-force
(see "Password strength" below)
- The key file (for `--pq-sdk` / `--pq-box`) was not compromised at
generation time
### 2. Integrity of every byte of an encrypted archive
If any single bit of the on-disk archive bytes is flipped, the
extraction fails with an authentication error. Coverage layers:
- Per-block HMAC-SHA256 with frame-preface AAD (F-09): every data
block carries an HMAC over its ciphertext and over the canonical
29-byte preface (block_type, codec_id, block_flags, sizes,
plaintext-XXH64)
- Archive Integrity Trailer (F-08): HMAC-SHA256 over the 64-byte
header and 24 bytes of footer, appended after the footer
- Strict structural validation of the encryption-header block (F-09):
codec must be `STORE`, flags must be 0, csz must equal usz, the
plaintext XXH64 must match
### 3. Tamper detection on plaintext archives (best-effort)
Plaintext archives (no `-p`, no `--pq*`) are protected by XXH64
plaintext checksums per block plus structural validation. This is
not cryptographic integrity — a determined attacker with write access
can produce a tampered plaintext archive that passes the checksum
(XXH64 is not collision-resistant). It does catch accidental
corruption and naive tampering.
Use an encrypted mode if you need cryptographic integrity.
### 4. Authentication failure indistinguishability (F-11)
The default error message for "wrong password", "wrong PQ key",
and "actual header tamper" is the same single line:
> `Error: Authentication failed (wrong key, wrong password, or tampered archive).`
This prevents an attacker who can issue extraction attempts from
learning which check failed first via the stderr output. Timing is
also constant (HMAC is always run, branchless return).
The detailed cause is available via `--verbose` for debugging on
machines under the user's own control.
### 5. Post-quantum forward secrecy (`--pq`, `--pq-only`, and optional `--pq-sdk`)
The native `--pq` mode uses ML-KEM-768 (FIPS 203 — validated byte-for-byte
against OpenSSL 3.5's ML-KEM-768; see AUDIT.md) hybridized with X25519 via an
HKDF combiner. Archives encrypted today cannot be decrypted by a future quantum
adversary holding only the ciphertext, assuming:
- ML-KEM-768 retains its claimed security level (NIST Category 3,
192-bit classical / 96-bit quantum strength)
- X25519 hybridization protects against an unforeseen ML-KEM break
- The recipient's private key is not later compromised
The native `--pq-only` mode (envelope type `0x06`) provides the same
harvest-now-decrypt-later protection using ML-KEM-768 as the *sole* key
mechanism. It exists for compliance postures that mandate a single
NIST-standardised PQ primitive with no classical KEM in the envelope
(CNSA 2.0-style "PQ-only"). **The trade-off is a loss of the second
assumption above:** there is no X25519 hybridization, so an unforeseen
break of ML-KEM-768 alone is sufficient to recover the archive key. For
that reason `--pq` (hybrid) is the recommended default, and `--pq-only`
should be used only when a policy forbids the classical component.
The optional `--pq-sdk` mode provides the same hybrid guarantee as
`--pq` via the separately distributed SDK libraries.
### 6. Side-channel resistance for cryptographic primitives
The hot crypto paths (AES-256-CTR, HMAC-SHA256 comparison, X25519
field operations, ML-KEM polynomial arithmetic) are implemented in
Jasmin and proved constant-time at the assembly level on x86_64.
Non-Jasmin platforms (aarch64, fallback x86_64) use C implementations
that avoid secret-dependent branches and memory accesses where
feasible — but without formal proof.
---
## What VaptVupt does NOT protect against
### 1. Compromised endpoints
VaptVupt cannot protect against:
- Malware on the machine doing the encryption (it sees plaintext
before any crypto is applied)
- Malware on the machine doing the extraction (it sees plaintext
after decryption)
- A hardware keylogger capturing the password
- A compromised user account that can read your files or
`~/.zupt-key` directly
- Cold-boot attacks on running machines
If you don't trust the machine, VaptVupt cannot help.
### 2. Key compromise
If the password or `~/.zupt-key` is leaked:
- All archives encrypted with that key are decryptable
- VaptVupt has no forward secrecy across archives — each archive
is encrypted under a single static key derived from the password
or stored in the key file
- There is no key-rotation feature; rotate by re-encrypting
archives under a new password/key and securely deleting the old
password/key
For high-value, long-term archives, treat the key file as you
would a master password: store it offline, encrypt it under
another layer (e.g. on an encrypted USB), and rotate periodically.
### 3. Password strength
Password mode derives the key with PBKDF2-SHA256 (600k iterations)
in the default build, or Argon2id in a `make WITH_SDK=1` build. A
key derivation function slows offline guessing but does not make a
short, common password safe: a determined attacker with GPU clusters
or cloud compute can still exhaust a weak password.
Use a long, high-entropy password — a multi-word diceware passphrase
or a random 16+ character string with a full alphabet. For critical
data, use a key-file mode (native `--pq`, or the optional `--pq-sdk`
with a random key file from `vaptvupt keygen --sdk`) so the key is
CSPRNG output, not derived from human-typed text.
### 4. Metadata leakage from archive structure
Even with encryption, an attacker who can see the archive bytes
can infer:
- Approximate file count (from `total_blocks` in the footer)
- Total archive size (file size on disk)
- Whether the archive is encrypted at all (`ZUPT_FLAG_ENCRYPTED`
in the global flags is visible)
- Whether the archive is solid or per-file mode (visible flag)
- Whether post-quantum mode is in use (visible flag)
- Approximate file size distribution (block sizes are visible
even when block payloads are encrypted)
- Archive creation time (a 64-bit timestamp in the header)
- A random 16-byte UUID per archive (no information leak, but
globally identifies the archive across copies)
If metadata privacy matters, layer VaptVupt under another tool that
hides bulk metadata (e.g., put the `.zupt` file inside a fixed-size
encrypted container).
### 5. Network attacks
VaptVupt is not a network protocol. There is no:
- Forward-secure session establishment (use TLS or Noise)
- Mutual authentication of remote parties (use signed messages or
TLS client certs)
- Replay protection across sessions (archives can be replayed by
an attacker who can write to the destination)
- Network-layer encryption (use TLS to transport `.zupt` files)
### 6. Multi-party schemes
There is no threshold cryptography, no n-of-m sharing, no
multi-party computation, no proxy re-encryption. Each archive
has exactly one decryption credential (one password OR one
recipient key). To give two people access to the same archive,
they must share the password or the key file.
### 7. Plausible deniability / hidden volumes
VaptVupt archives have a fixed 6-byte magic `\x90\x5a\x55\x50\x54\x01`
at offset 0. Anyone scanning the bytes can see it's a VaptVupt
archive. VaptVupt has no hidden-volume or duress-password feature.
### 8. Side channels we don't claim to address
- Power analysis (relevant for embedded targets, not commodity desktops)
- Electromagnetic emanation
- Acoustic side channels
- Network timing of upload patterns
- Filesystem-level metadata (mtime/atime of the `.zupt` file)
### 9. Trusted setup of post-quantum primitives
The in-tree ML-KEM-768 implementation was not independently audited
at the time of writing. We use NIST KAT vectors for correctness
verification but have not formally proven constant-time properties
for every PQ code path.
For maximum assurance, treat the post-quantum layer as a hedge — it
does not replace the X25519 layer; both must be broken for an
attacker to recover plaintext.
### 10. Format extension attacks
The format is versioned (v1.6). Older readers may accept newer
archives in unexpected ways. We try to maintain forward
compatibility, but a careful attacker who can produce
malformed-but-just-valid archives may find parser-state issues that
don't rise to the level of a CVE. The fuzzing harness
(`make fuzz-format`) is the primary mitigation; report bugs.
### 11. Compression-side-channel attacks (CRIME / BREACH style)
VaptVupt compresses before encryption. If an attacker can:
- Influence part of the plaintext (e.g. inject a known prefix)
- Observe the resulting archive size precisely
then they can use the compression ratio to learn information about
the rest of the plaintext — this is the classic CRIME/BREACH attack
against TLS compression.
VaptVupt is designed for offline backup, where attacker-controlled
plaintext injection is rare. If your threat model includes
attacker-chosen plaintext mixed with secret plaintext in the same
archive, use `--no-compress` (codec 0 = STORE) to disable the
LZ codec and eliminate this side channel.
---
## Cryptographic assumptions
VaptVupt's security rests on the following standard assumptions:
| Assumption | What breaks if it fails |
|---|---|
| AES-256-CTR is a secure stream cipher | All encrypted archives become readable |
| HMAC-SHA256 is a secure PRF / MAC | Tamper detection fails; integrity can be forged |
| PBKDF2-SHA256 (or Argon2id, WITH_SDK) is a secure password KDF | Password-mode archives become brute-forceable faster |
| ML-KEM-768 retains NIST Category 3 security | `--pq` / `--pq-sdk` reduce to the X25519 layer; **`--pq-only` has no fallback and is broken** |
| X25519 retains 128-bit security (no quantum) | Hybrid PQ modes reduce to the ML-KEM layer; `--pq-only` and classical password mode unaffected |
| HKDF-SHA256 is a secure key-derivation construction | Combined PQ + classical keys may be predictable |
| SHA3 / SHAKE retain pre-image and collision resistance | Auxiliary protocol bindings may be forged |
If you don't trust one of these primitives, VaptVupt cannot protect
you. We rely on the same primitives the broader cryptographic
community has standardized.
---
# ZUPT 5.2.2 threat model
This document defines the security boundary of the ZUPT archive tool. It is
not a certification, a guarantee against every hostile input, or a substitute
for reviewing the exact source and binary used for important data.
## Intended use
ZUPT is intended for at-rest backup archives created and restored on
machines controlled by the user. It can be used when the storage provider or
physical medium is not trusted, provided encryption is enabled and credentials
remain secret.
It is not a network protocol, a full-disk encryption system, a multi-party or
threshold scheme, a password manager, or a way to make an archive's existence
plausibly deniable.
## Baseline considered here
The upstream baseline is built from the 5.2.2 source with:
```sh
make WITH_SDK=0 WITH_PQBOX=0
```
It contains the native password, ML-KEM-768 + X25519 hybrid `--pq`, and
ML-KEM-768-only `--pq-only` modes. It does not load a precompiled library from
the repository and does not download a dependency while building.
`WITH_SDK=1` and `WITH_PQBOX=1` add separately installed system libraries and
change the assessed code boundary. The SDK and PQBOX integrations must be
reviewed with their exact packaged source and version; success of the baseline
tests is not evidence for them.
Textual assembly under `jasmin/` is a separate `WITH_JASMIN=1` option for
supported x86_64 compiler targets. The directory contains both generated and
separately identified hand-written assembly. Portable C is the default.
Architecture portability is a source property, not evidence that an unexecuted
architecture passed.
## Assets
The assets ZUPT tries to protect are:
- archived file contents and encrypted index data;
- the integrity and ordering of encrypted archive blocks and current global
metadata covered by the archive integrity trailer;
- private keys, passwords, and derived encryption/MAC keys while held by the
trusted caller;
- safe placement of extracted entries within the requested destination.
The archive's existence, total byte length, magic, encryption/framing flags, and
some size/structure information are observable. Plain archives provide
corruption detection, not cryptographic protection against an active attacker.
## Adversaries considered
The design considers an adversary who can read, copy, truncate, reorder, or
modify stored archive bytes but cannot read the encryption endpoint's memory or
credentials. It also considers accidental corruption and malicious archive
entry paths during extraction.
The following adversaries are outside the protection boundary:
- malware, a keylogger, or an administrator on the source or restore endpoint;
- an attacker who obtains the password or matching private key;
- a malicious compiler, kernel, CPU, firmware, or random-number generator;
- an attacker with unrestricted side-channel observation of a shared machine;
- an attacker allowed unbounded CPU, memory, or storage denial of service.
## Security properties
### Encrypted archive confidentiality
Password and native PQ modes encrypt blocks with AES-256-CTR and authenticate
them with HMAC-SHA256. Confidentiality depends on unique nonces, correct
implementations, OS randomness, and credential secrecy. In password mode it
also depends on password entropy; PBKDF2-SHA256 slows but cannot prevent offline
guessing of a weak password.
Prefer `--password-prompt`, `--pass-file`, or `--pass-fd`. A password supplied
through `-p/--password` can be visible through process inspection or shell
history. A password file is protected only by the caller's filesystem choices;
ZUPT does not validate its ownership or permission bits. A descriptor is
trusted input inherited from the caller. Both non-interactive forms read one
line and reject empty, NUL-containing, or overlong values. The descriptor form
duplicates but shares the underlying stream/offset and may buffer beyond the
line, so callers should provide a descriptor dedicated to that password read.
On POSIX, handled prompt interruptions restore the saved terminal state before
termination; an exact-candidate PTY regression is required before release.
Native private-key generation uses no-replace creation with POSIX mode `0600`
or a Windows current-user-only DACL. A failed write, flush/fsync, or close leaves
the incomplete or durability-uncertain exclusive file for manual review and
removal instead of risking an unlink-after-close race against a replacement
pathname. ZKEY and ZPQK inputs
are accepted only after checksum, version, flags, reserved bytes, exact size,
and public/private role validation. This prevents role confusion and
partial/trailing-key acceptance; it does not protect a key after endpoint or
account compromise.
### Encrypted archive integrity
Current encrypted archives authenticate ciphertext, canonical block metadata,
and each frame's logical position. DATA and DEDUP_REF frames both receive this
positional AAD. A reference is authenticated at its own position and carries
the authenticated source position needed to verify the referenced DATA frame,
so exchanging otherwise equivalent frames is not accepted.
Current archives carry an archive-integrity trailer for global metadata. The
`extract`, `list`, `test`, and `disk restore` paths refuse any no-AIT layout by
default without relying on an unauthenticated header flag.
`--allow-legacy-no-ait` is a narrowly scoped, warning-producing recovery option
for those commands when the caller already trusts a pre-AIT archive. Selecting
it for attacker-controlled storage removes the header/footer authentication
assumption and is outside this threat model. `info` is an unauthenticated
framing inspection that reports apparent AIT presence but validates neither the
trailer nor archive contents. These checks do not prevent deletion of the
entire archive, rollback to an older valid archive, or storage-layer replay.
Archive comments remain untrusted presentation data even when they are
authenticated. Display paths render control bytes without emitting raw terminal
control sequences, limiting terminal-output injection while leaving the stored
and authenticated comment bytes unchanged.
New 5.2.2 encrypted+dedup archives authenticate each reference offset. New
encrypted disk archives also authenticate an index that binds image size,
block count, and a chained XXH64 hash of the complete restored stream. The
writer's additional SHA-256/128 comparison is only an in-memory collision guard
before deduplication; it is not an on-disk cryptographic hash. XXH64 is not
cryptographic, so a writer who controls a plain archive can recompute it.
Plain archives use non-cryptographic checksums. A writer who controls a plain
archive can recompute them.
### Native hybrid post-quantum mode
The `--pq` mode combines an ML-KEM-768 shared secret and an X25519 shared secret
as implemented in 5.2.2:
```text
hybrid_ikm = ml_ss XOR x25519_ss
archive_key = SHA3-512(hybrid_ikm || ml_ct || ephemeral_pk ||
"ZUPT-HYBRID-v1")
```
Its goal is harvest-now/decrypt-later resistance if ML-KEM-768 remains secure,
with X25519 as a classical hedge under the combiner assumptions. This is not
session forward secrecy: compromise of the recipient's long-term private key
can compromise previously captured archives encrypted to it.
The native `--pq-only` mode removes X25519 and derives a key from ML-KEM-768
alone. Use it only when a policy specifically excludes the classical component;
it loses the hybrid hedge.
The in-tree ML-KEM code has project tests, including known-answer vectors and a
conditional OpenSSL 3.5 interoperability test. It has not been independently
audited or formally verified as a whole implementation.
### Extraction containment
The reader rejects absolute paths, traversal components, control characters,
ambiguous trailing dot/space components, NTFS alternate-stream syntax, and
reserved Windows device names. POSIX extraction resolves every parent below a
pinned destination descriptor with no-follow operations after canonicalizing
the user-selected root once. Windows extraction
uses handle-relative traversal, rejects reparse-point parents, and publishes the
final name by handle without replacing an existing leaf. A checked path is not
re-resolved through a mutable parent.
Decoded bytes are first written to a private, exclusively created temporary
file. The final name is published only after the expected decoded size and
chained checksum match and the stream closes successfully; failures remove the
temporary through its descriptor or handle. These controls reduce traversal,
link, race, and partial-output risks, but do not establish that no parser or
filesystem bug can exist.
The Windows handle-relative boundary in 5.2.2 covers normal local Win32 paths.
Win32 extended-length and device-namespace paths, raw UNC output roots, and
mapped/network-drive output are not supported. Cross-build and Wine results are
not a substitute for the required native `windows-latest` Unicode package
gate. Restore locally before moving verified output to network storage.
Disk restore copies the measured compacted archive into one exclusively
created, auto-deleted scratch file before it opens a destructive destination.
Preflight and restoration consume that same open snapshot. An explicit
`ZUPT_TMPDIR` selects an existing scratch directory; failure there does not
fall back to consuming the mutable source pathname. On supported Linux, macOS,
and FreeBSD interfaces, a raw block-device target is rejected before writing if
its capacity is unknown or smaller than the image. These controls reduce source
exchange and immediate overrun risk but do not protect against a compromised
kernel/device, a wrongly selected sufficiently large device, power loss, or
hardware failure.
For an untrusted archive:
1. use a new empty destination outside sensitive trees;
2. run as a dedicated unprivileged user, never root;
3. apply a container, sandbox, resource limits, and a storage quota when
available;
4. inspect extracted paths, types, permissions, and content before moving them;
5. never restore a disk image to a device without independently confirming both
source and destination.
## Non-goals and residual risks
ZUPT does not claim to provide:
- resistance to cache, power, EM, acoustic, speculative-execution, or all
compiler-introduced timing side channels;
- bounded resource consumption for every malformed archive;
- confidentiality of archive size or complete framing metadata;
- protection against compression-length oracles when secret and
attacker-controlled data are compressed together;
- rollback detection across multiple valid versions of a backup;
- forward-secure sessions, remote authentication, replay protection, or secure
transport;
- automatic key rotation, recovery, escrow, threshold access, or secure
deletion;
- preservation of every operating-system ACL, ownership attribute, extended
attribute, or special-file semantic;
- safe operation on a compromised host.
## Credential handling
- Generate PQ keys on a trusted system using the OS CSPRNG.
- Keep private keys separate from the archive and from release/package inputs.
- Store an offline recovery copy and test recovery before relying on a backup.
- Use a distinct high-entropy credential where compromise isolation matters.
- Re-encrypt under a new credential after suspected disclosure; there is no
in-place key rotation.
- Never include credentials or sensitive archives in bug reports or CI logs.
## Supply-chain boundary
Git and upstream source archives are source-only. They must pass
`scripts/check-source-only.sh` and must not contain executable code artifacts,
objects, shared/static libraries, distribution packages, unsafe symlinks, or Git
LFS pointers.
Nested inspection is itself an untrusted-input boundary. The release scanner
must cap recursion depth, archive members, per-entry expansion, and total
expanded bytes and fail closed when a cap is reached. Scanner bomb regressions
and all other late self-audit fixes are pending until rerun on the exact
candidate.
DEB, binary RPM, SRPM, notice-bearing Linux tar.xz, source-only portable GUI
ZIP, Windows ZIP, and macOS DMG files can be published separately from the
tagged source. Each artifact extends the trust boundary to its builder,
toolchain, runner image, and packaging scripts. Treat it as validated only when
the exact target has a recorded build, content/package inspection, extracted or
installed smoke test, and applicable archive round trip. An AppImage is not
promoted for 5.2.2; bare Linux and Windows executables are also excluded.
For 5.2.2, that gated artifact scope covers the CLI files plus the exact GUI
DEB, noarch/source RPM, and source-only portable ZIP named in the README. The
portable ZIP contains no compiled runtime and crosses the release boundary only
after source scans and an exact safe-member check. AppDir and Flatpak bundles
and GUI platform installers remain excluded; Windows ZIP and macOS DMG outputs
remain CLI-only.
## Historical compatibility notes
These are historical facts about earlier releases, retained to support recovery:
- Releases through 4.1.0 could reuse an AES-CTR nonce in encrypted `--dedup`
archives. Release 4.2.0 changed to fresh random per-block nonces. Re-encrypt
affected older archives.
- Releases through 4.2.1 used round-3 CRYSTALS-Kyber semantics in the native PQ
path. Release 5.0.0 corrected the implementation to FIPS 203 ML-KEM-768,
changing native PQ key/archive compatibility. See `CHANGELOG.md` before
planning cross-version restoration.
- Pre-AIT archive layouts now fail closed by default. The explicit
`--allow-legacy-no-ait` read option is only for recovery from a known, trusted
historical archive and leaves its header/footer metadata outside the current
authenticated boundary.
- The 5.2.2 reader retains compatibility parsers for the fixed-width disk index
and encrypted-dedup linear AAD sequence published through 5.2.1. An actual
v5.2.1 password-encrypted DATA/DATA/REF/DATA disk fixture is stored as hexadecimal
text with source and hash provenance. The candidate lists, tests, extracts, and restores
it byte-exact, with a warning that the legacy index has no whole-image hash;
the exact final candidate must repeat that gate. Older readers are not
claimed to accept new flag-gated 5.2.2 records, and untested historical mode
combinations remain unclaimed.
Historical test counts in the changelog describe those releases. They do not
automatically become 5.2.2 results; current outcomes belong in the release
validation record, with unavailable environments marked `SKIP`. In particular,
runs made before the final positional-AAD and mandatory-AIT changes are not
final release gates for the resulting candidate.
## Reporting security issues
Email `sac@securityops.co` with the subject `VaptVupt security report`.
PGP key available on request.
Email **zupt@riseup.net** with `[security]` in the subject. Include the version,
platform, impact, and a minimal non-sensitive reproducer. Do not disclose the
issue publicly until a coordinated timeline has been agreed.
We will:
- Acknowledge receipt within 7 days
- Investigate and publish a CVE / advisory if warranted
- Credit you in the CHANGELOG if you wish
Please don't open public issues for security reports until we've
coordinated disclosure. For non-security bugs (parser edge cases,
documentation typos, performance issues), open a public issue
normally.
---
## Document version
This threat model covers archive format v1.6 as shipped in VaptVupt
5.0.0. It is part of the source tree (`THREAT_MODEL.md`) and
versioned with the project; this section will be updated as the
format evolves.
Document version: 5.2.2, 2026-08-31.