- C 67.6%
- Shell 15.7%
- Python 7.5%
- HTML 2.3%
- Makefile 2%
- Other 4.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Project renamed following the CLI's v3.0.0 rename (prior INPI Brasil trademark registration on "Zupt"). The .zupt archive extension and container format (v1.6) are unchanged. Bundled CLI: zupt-2.2.3/ → vaptvupt-5.2.1/ (verbatim upstream tree plus prebuilt vendor/vuptsdk/libvuptsdk.so.2.0.0, force-added past the vendored .gitignore). Docker builder now builds WITH_SDK=1 (--pq-sdk + Argon2id password KDF; links -lvuptsdk -lcrypto -largon2, so the builder stage installs libssl-dev + libargon2-dev). Web app: - All env vars renamed VAPTVUPT_* with legacy ZUPT_* fallbacks - New /keygen-pqonly endpoint + --pq-only key fields in compress/extract (full post-quantum mode, ML-KEM-768 only, CLI ≥ 4.2.0) - New /info endpoint + Inspect card on the Verify tab (vaptvupt info: codec, encryption mode, block count — no credential required) - Password hardening: reject whitespace-only passwords (previously produced an UNENCRYPTED archive silently) and leading-dash passwords (CLI parser would misread them and echo the password back) - Uploaded key files moved to a keys/ subdir and sources to in/ so a source file named upload_pub.key can no longer be clobbered - Empty-filename guard added to /test-archive and /info - healthz reports service vaptvupt-web, version 5.2.1 Container: - Binary /usr/local/bin/vaptvupt with legacy zupt symlink (one major version cycle, matching upstream's install rule) - SDK at /usr/lib/vaptvupt/libvuptsdk.so.2.0.0; RUNPATH retargeted - Runtime user renamed zuptweb → vaptvupt (uid 1001), workdir /tmp/vaptvupt-work - gunicorn worker timeout raised to 660s (above the 600s CLI timeout so timeout errors render as pages instead of a SIGKILLed worker) - Image vaptvupt-web:5.2.1; docker-compose service renamed to vaptvupt Docs: README/LICENSE preamble rebranded, links moved to vaptvupt / vaptvupt-codec / libvuptsdk, compatibility note corrected (v5.0.0's FIPS 203 switch broke --pq/--pq-only interop with ≤ 4.2.1), AES-256-CTR citation extended with NIST SP 800-38A, screenshots retaken with the new UI. Verification: upstream make check 16/16 in the builder image; end-to-end smoke (password / --pq / --pq-only / --pq-sdk round-trips byte-exact, info, verify, CSRF, mutual-exclusion, rate limits) all passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
| screenshots | ||
| static | ||
| templates | ||
| vaptvupt-5.2.1 | ||
| .gitignore | ||
| app.py | ||
| build-vaptvupt.sh | ||
| docker-compose.yml | ||
| Dockerfile | ||
| LICENSE | ||
| README.md | ||
| setup.sh | ||
VaptVupt Web — Post-Quantum Backup in Your Browser
Encrypt, compress, and protect files with quantum-resistant cryptography — from any browser.
One-command deploy. No accounts. No cloud. Your keys, your data, your server.
docker compose up -d
# → http://localhost:8181
This release bundles VaptVupt CLI 5.2.1 (with VaptVupt 2.65.3 codec and libvuptsdk 2.0.0). See CHANGELOG of bundled vaptvupt for the full list of changes.
Renamed from "Zupt-Web" together with the CLI's v3.0.0 rename, because of a prior INPI Brasil trademark registration on the name "Zupt" for unrelated software. The
.zuptarchive extension and container format (v1.6) are unchanged. One documented exception to cross-version compatibility: CLI v5.0.0 switched--pq/--pq-onlyto genuine FIPS 203 ML-KEM-768 (a breaking change), so hybrid/full-PQ encrypted archives and keys made by ≤ 4.2.1 — including all Zupt 2.x — cannot be decrypted by this release (and vice versa). Password-mode,--pq-sdk, and unencrypted archives are unaffected. Thezuptcommand remains available inside the container as a symlink tovaptvupt, and the legacyZUPT_*environment variables are still honored.
What's new in 5.2.1
- Bundled CLI upgraded — Zupt 2.2.3 → VaptVupt 5.2.1: genuine FIPS 203 ML-KEM-768 (5.0.0), full post-quantum mode (4.2.0), source-only native build with vendored SDK (4.1.0), codec 2.48.2 → 2.65.3 with large ratio gains (5.1.0: level-9 text 3.77× → 5.98×, JSON 8.25× → 9.38×) and ~1.6–2× faster extreme-mode encode (5.2.0).
- Full post-quantum mode in the UI — new "Full-PQ Keypair" button and
--pq-onlypublic/private key fields in the compress/extract forms. ML-KEM-768 only, no classical X25519 layer. - Archive header inspection — new Inspect card on the Verify tab
(
vaptvupt info): reports codec, encryption mode (password / hybrid PQ / full PQ / SDK v2), and block count without any credential. The same header detection the 5.2.1 GUI uses to auto-pick the right decrypt mode. - Argon2id password KDF — the container builds the CLI
WITH_SDK=1, so password-mode archives use Argon2id (RFC 9106) instead of PBKDF2. - Renamed environment variables —
VAPTVUPT_MAX_UPLOAD_MB,VAPTVUPT_KEY_TTL_SEC,VAPTVUPT_BIN,VAPTVUPT_WORKDIR,VAPTVUPT_SECRET_KEY,VAPTVUPT_COMPRESS_TIMEOUT,VAPTVUPT_EXTRACT_TIMEOUT. The legacyZUPT_*names still work as fallbacks. - Repo renamed — git.securityops.co/cristiancmoises/zupt-web → vaptvupt-web (mirrors on GitHub and Codeberg).
Features
| Feature | Description |
|---|---|
| SDK v2 key generation | HKDF + key commitment + HPKE binding + Argon2id. --pq-sdk workflow |
| Hybrid key generation | ML-KEM-768 + X25519. --pq workflow (FIPS 203 since v5.0.0 — keys/archives from ≤ 4.2.1 are incompatible) |
| Full-PQ key generation | ML-KEM-768 only, no classical layer. --pq-only workflow |
| Compress & encrypt | Upload files → .zupt archive. Password, --pq, --pq-only, or --pq-sdk (mutually exclusive) |
| Extract & decrypt | Upload .zupt + key/password → original files returned |
| Verify integrity | Validate every block's XXH64 + HMAC-SHA256 without extracting |
| Inspect header | Codec, encryption mode, block count — no credential required |
| Codec selection | AUTO (hardware-adaptive) · VaptVupt 2.65.3 (AVX2/NEON) · LZHP (universal) · Store |
| Block dedup | XXH64 fingerprint index, eliminates duplicate blocks before compression |
| Solid mode | Cross-file dictionary sharing for higher ratios on similar files |
| Levels 1–9 | Fastest to maximum compression ratio |
Security posture
- Runs as non-root in the container (
vaptvupt, uid 1001) viaUSER vaptvuptin the Dockerfile. There is noroot-owned process at runtime. no-new-privilegesenabled indocker-compose.yml— no path to capability-based or setuid escalation.- CSRF tokens on every form (double-submit cookie,
hmac.compare_digestconstant-time check). - Rate limiting — 10 keygen/min, 30 compress·extract·test·info/min per IP.
- Tight Content-Security-Policy set by Flask in
@app.after_request:default-src 'none'; script-src 'self' 'unsafe-inline'; …; frame-ancestors 'none'(theunsafe-inlineis required because templates are deliberately single-file with inline<style>and minimal<script>; no separate.js/.cssfiles are served). - Full security header set applied by Flask: CSP, X-Frame-Options DENY, X-Content-Type-Options nosniff, X-XSS-Protection, Referrer-Policy, Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy, Permissions-Policy (camera/mic/geo/payment/USB/interest-cohort revoked).
- No
shell=True— every subprocess call uses explicit argv. - Path traversal protection — filenames sanitised, paths verified with
Path.resolve().relative_to(WORKDIR). - Password scrubbing — passwords are stripped from error output before being rendered to the browser.
- Keys auto-expire from the server after 4 h (
VAPTVUPT_KEY_TTL_SECenv override). - Workdir is a tmpfs (
/tmp/vaptvupt-work, uid-mapped to 1001) — compress jobs never touch the container's writable layer. - Container resource limits —
memory: 4Gindocker-compose.yml(defence against runaway compress jobs). CPU is uncapped by default; uncomment thecpus:line if your host is shared. - HEALTHCHECK uses dedicated
/healthzendpoint — no CLI fork on each probe, no rate-limit consumption, no auth roundtrip. - Audited bundled CLI — VaptVupt 5.2.1 ships with
make check16/16, NIST/RFC KAT 16/16, ML-KEM-768 FIPS 203 conformance 3/3, plus the full security regression matrix (path traversal, block swap, dedup nonce, arg order, decode slack). See AUDIT.md.
Screenshots




Comparison
| VaptVupt Web | age + gzip | GPG + tar | Duplicati | BorgBackup | |
|---|---|---|---|---|---|
| Post-quantum encryption | ML-KEM-768 hybrid and full PQ | — | — | — | — |
| SDK v2 (key commitment + HPKE binding) | ✓ via --pq-sdk |
— | — | — | — |
| Block-level dedup | XXH64 fingerprint index | — | — | — | HMAC |
| Web interface | Self-hosted Docker | CLI | CLI | Web | CLI |
| Zero deps in codec | Pure C11 (vaptvupt + VV codec) | Go | GnuPG | .NET | Python |
| Hardware-adaptive codec | AVX2/NEON auto | — | — | — | — |
| Per-block integrity | XXH64 + HMAC-SHA256 per block | — | Whole-file | — | HMAC |
| Docker one-command | docker compose up -d |
— | — | Yes | — |
Cryptographic stack
| Algorithm | Standard | Purpose |
|---|---|---|
| ML-KEM-768 | FIPS 203 | Post-quantum key encapsulation (--pq hybrid, --pq-only pure) |
| X25519 | RFC 7748 | Elliptic curve Diffie–Hellman (hybrid layer) |
| AES-256-CTR | FIPS 197 / NIST SP 800-38A | Symmetric encryption (--pq, -p) |
| XChaCha20-Poly1305 | RFC 8439 / draft-irtf-cfrg-xchacha | SDK v2 AEAD (--pq-sdk default) |
| AES-256-SIV | RFC 5297 | SDK v2 nonce-misuse-resistant alternative |
| HMAC-SHA256 | RFC 2104 | Per-block authentication |
| HKDF-SHA3-256 | RFC 5869 / FIPS 202 | SDK v2 hybrid combiner |
| Argon2id | RFC 9106 | Password key derivation (WITH_SDK build — this container) |
| PBKDF2-SHA256 | RFC 8018 | Password key derivation (source-only builds, 600 K iter) |
| SHA3 / SHAKE | FIPS 202 | Hybrid key derivation, Keccak |
Deploy
Prerequisites
- Docker and Docker Compose
Quick start
git clone https://git.securityops.co/cristiancmoises/vaptvupt-web && cd vaptvupt-web
docker compose up -d
Open http://localhost:8181.
Or grab the packaged release tarball (vaptvupt-web-5.2.1.tar.gz from the
release page),
verify it against SHA256SUMS, unpack, and run ./setup.sh — it checks
Docker DNS, builds, starts, and verifies the stack in one go.
Tuning
# Cap upload size. Default: 2 GiB.
# Edit app.py — value in MEGABYTES:
app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 50 MB
# Or override at runtime via env (no rebuild needed):
docker run -e VAPTVUPT_MAX_UPLOAD_MB=50 -p 8181:8080 vaptvupt-web:5.2.1
# Tighten/relax key TTL on the server (defaults to 4 hours):
docker run -e VAPTVUPT_KEY_TTL_SEC=900 -p 8181:8080 vaptvupt-web:5.2.1
(The legacy ZUPT_* env names still work.)
Docker DNS fix
If the build fails with Temporary failure resolving 'archive.ubuntu.com':
echo '{"dns":["9.9.9.9","1.1.1.1"]}' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker
Or run ./setup.sh — it detects this case automatically.
Verify
curl localhost:8181/healthz # {"ok": true, "service": "vaptvupt-web", "version": "5.2.1"}
curl localhost:8181/version # {"version": "vaptvupt 5.2.1 ...", "ok": true}
docker exec -t vaptvupt-web vaptvupt version
Architecture
┌────────────────────────────────────────────────────┐
│ Browser ──HTTP──▶ gunicorn (port 8080, 2 wk) ──┐
│ │
│ Flask app
│ (sets all
│ security
│ headers,
│ serves
│ /static/)
│ │
│ ▼
│ /usr/local/bin/vaptvupt
│ │ │
│ ▼ │
│ /usr/lib/vaptvupt/ │
│ libvuptsdk.so.2 │
└────────────────────────────────────────────────────┘
- gunicorn binds 8080 directly as
vaptvupt(uid 1001), 2 workers, 660 s worker timeout (deliberately above the app's 600 s CLI timeout so timeout errors render as pages), recycles workers every 1000 ± 100 requests. PID 1 istinifor proper signal forwarding and zombie reaping. - Flask app (
app.py) serves the UI, accepts uploads, sets all security headers in@app.after_request, serves static files fromstatic/, and calls thevaptvuptCLI via explicit argv (no shell). Streams the result back to the browser. - vaptvupt CLI (built from the bundled
vaptvupt-5.2.1/source tree,WITH_SDK=1) does the actual compress/encrypt/extract work. Links to libvuptsdk viaRUNPATH=/usr/lib/vaptvupt. A legacyzuptsymlink is kept for one major version cycle.
Project family
All maintained by Cristian Cezar Moisés on git.securityops.co:
- vaptvupt — CLI + GUI (this repo bundles a copy of its source)
- vaptvupt-web — this repo
- vaptvupt-codec — standalone LZ + tANS codec, embedded in vaptvupt
- libvuptsdk — C SDK powering
--pq-sdk+ Argon2id password KDF
License
VaptVupt-Web is licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later).
The full license text is in LICENSE, preceded by a formal preamble explaining the rationale.
- You can run VaptVupt-Web freely — for personal use, internal company use, homelab self-hosting, research, or as a tool in your sysadmin workflow. The AGPL imposes essentially no obligations on simple use.
- If you operate a modified VaptVupt-Web as a hosted/SaaS service (a backup-management portal, a cloud archive viewer, a backup-as-a-service frontend, etc.), you MUST make the source code of your modifications available to the users of that service. This is the AGPL's "SaaS clause" and is the entire reason VaptVupt-Web is AGPL rather than GPL or MIT — VaptVupt-Web is by design a network-facing application, the exact deployment shape the AGPL exists to address.
- If you redistribute VaptVupt-Web (modified or not), the AGPL travels with it.
The bundled vaptvupt-5.2.1/ subdirectory contains the VaptVupt CLI source tree, also licensed under AGPL-3.0-or-later. The integrated VaptVupt 2.65.3 compression codec inside the bundled CLI is licensed under GPL-3.0-or-later (kept in sync with git.securityops.co/cristiancmoises/vaptvupt-codec). The vendored libvuptsdk.so.2.0.0 shared object is also AGPL-3.0-or-later. GPL-3.0-or-later is two-way compatible with AGPL-3.0-or-later via section 13 of both licenses.
Commercial licensing
If your intended use is incompatible with the AGPL — for example:
- Operating VaptVupt-Web as a hosted backup-as-a-service product without releasing your modifications
- Embedding VaptVupt-Web into a closed-source commercial portal or appliance
- Redistributing VaptVupt-Web as part of a proprietary product
- Requiring warranty, indemnification, or written terms
A commercial license is available. Contact: sac@securityops.co