feat: optional password encryption for backup and domain export (code-review.md § Phase 1.5)
Both secret-bearing downloads can now be sealed with a password. Unticked, the forms produce exactly the files they did before. - internal/secretfile: envelope format — magic/type/scrypt params/salt/nonce prefix header, then 64 KiB AES-256-GCM chunks each authenticated with the header, its counter and an end-of-stream flag, so truncation, reordering and tampering fail to open instead of restoring a plausible prefix. Streams both ways, so a full backup never sits in memory. - Panel: "Encrypt with a password" checkbox on the full-backup and domain-export forms (shared partial, toggled from panel.js — no inline script); domain import detects an encrypted export by magic bytes, not by extension, and asks for the password. - selfpost-backup: writes .spbk when given a password and converts one back with -decrypt, which a restore needs. The password comes from SELFPOST_BACKUP_PASSWORD or -password-file, never argv. - Docs: README, security.md (+ accepted risk: encryption stays opt-in), architecture.md, progress.md, CHANGELOG. Verified locally: panel-encrypted archive decrypts through the CLI and unpacks; wrong password and password mismatch are refused; UI checked in a browser. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -296,7 +296,43 @@ Two related but distinct operations — spec 7.5:
|
||||
Both files are **secrets** — they contain the admin password hash (full
|
||||
backup) or working application credentials (domain export) in the clear or in
|
||||
directly reversible form. Treat them like any other credential material:
|
||||
encrypt at rest, restrict who can read them, don't email them around.
|
||||
restrict who can read them, don't email them around — and encrypt them, which
|
||||
SelfPost can do for you.
|
||||
|
||||
### Encrypting a backup or export
|
||||
|
||||
Both download forms carry an **Encrypt with a password** checkbox. Ticked, the
|
||||
file that comes down is an encrypted envelope instead of the plain archive:
|
||||
|
||||
| Artefact | Plain | Encrypted |
|
||||
|----------|-------|-----------|
|
||||
| Full backup | `.tar.gz` | `.spbk` |
|
||||
| Domain export | `.json` | `.spde` |
|
||||
|
||||
The key is derived from the password with scrypt and the contents are sealed
|
||||
with AES-256-GCM, in chunks, so a truncated or altered file fails to open rather
|
||||
than restoring quietly. **SelfPost does not store the password** — lose it and
|
||||
the file is unrecoverable, which is the entire point.
|
||||
|
||||
*Import a domain* takes an encrypted export directly: tick **The file is
|
||||
encrypted** and give the password.
|
||||
|
||||
A full backup has to be turned back into a plain archive before it can be
|
||||
unpacked into `/data`, which the CLI does with the same password:
|
||||
|
||||
```sh
|
||||
docker exec -i <container> selfpost-backup -decrypt < backup.spbk > backup.tar.gz
|
||||
```
|
||||
|
||||
The CLI also *writes* encrypted backups for scripted/cron use. The password
|
||||
comes from `SELFPOST_BACKUP_PASSWORD` or `-password-file <path>` (first line),
|
||||
never from a command-line argument, which would be visible in the process list:
|
||||
|
||||
```sh
|
||||
docker exec -e SELFPOST_BACKUP_PASSWORD="$PW" <container> selfpost-backup > backup.spbk
|
||||
```
|
||||
|
||||
With no password set, the CLI keeps writing the plain `.tar.gz` it always has.
|
||||
|
||||
## Published ports
|
||||
|
||||
|
||||
Reference in New Issue
Block a user