Encryption-at-rest fix (#20) merged but never deployed — running docker-backup-app still stores backups unencrypted #47

Open
opened 2026-08-12 20:58:20 +00:00 by Mark · 1 comment
Owner

Finding (post-deploy probe)

The encryption-at-rest fix for #20 was committed to main and both #20 and follow-up #44 are closed, but the fix was never deployed. The running docker-backup-app service does not contain the encryption code, so backups made today are still stored in plaintext.

Kanban source card: t_22c55f28 (qa board, post-deploy probe after fixit card t_16dac594).

Evidence

  • Repo fix: main at a5b47ce23cabddb5c8a2c3e45c5ab8bf10ab4a65 "feat: add encryption at rest for backup archives (#20)".
  • Running container docker-backup-app (on zsus via docker-backup-zsus tailscale) uses image built 2026-04-15 (sha256:6f755a7e1de4...), Up 6 weeks, restarts=0. It is the only docker_backup-app image on the host.
  • grep for DOCKER_BACKUP_ENC / maybeEncryptArchive inside the running container's /app/backend -> no matches; the deployed binary does not contain the encryption code.
  • Compose build context on zsus (/home/mark/docker_backup) is at commit 5a67863, which predates the fix (a5b47ce).

Reproduction steps

  1. SSH to zsus (via tailscale docker-backup-zsus) and inspect the running container:
    podman/docker ps --filter name=docker-backup-app -> note the image creation date (2026-04-15).
  2. Enter the container and grep the backend for encryption symbols:
    podman exec docker-backup-app grep -r maybeEncryptArchive /app/backend -> no matches.
  3. Run a backup from the UI (or API) and inspect the produced archive file: it has no .enc suffix and is stored unencrypted.

Actual behavior

Service is healthy (/health and /api/health return 200) but encryption-at-rest is not live: any backup made today is stored unencrypted, exactly the state #20 was opened to fix.

Expected behavior

After the #20 fix, the running docker-backup-app should:

  • Be rebuilt from main (a5b47ce or later) and redeployed on zsus (docker compose -f docker-compose.zsus.yml build app && up -d).
  • Show encryption settings in the UI; a test backup should upload with an .enc suffix; restore of an encrypted archive should work.

Remediation (operator action)

Rebuild + redeploy docker-backup-app from origin/main on zsus, then verify encryption settings appear in the UI, a test backup uploads with .enc suffix, and restore of the encrypted archive works.

  • routeJobToAgent transmits the passphrase in plaintext over the agent HTTP API (internal, secret-authenticated — acceptable but worth noting).
  • maybeEncryptArchive deletes plaintext with .catch(() => undefined) — if unlink fails, plaintext silently persists on disk. Low risk.
## Finding (post-deploy probe) The encryption-at-rest fix for #20 was committed to `main` and both #20 and follow-up #44 are closed, but the fix was **never deployed**. The running `docker-backup-app` service does not contain the encryption code, so backups made today are still stored in plaintext. Kanban source card: `t_22c55f28` (qa board, post-deploy probe after fixit card t_16dac594). ## Evidence - Repo fix: `main` at `a5b47ce23cabddb5c8a2c3e45c5ab8bf10ab4a65` "feat: add encryption at rest for backup archives (#20)". - Running container `docker-backup-app` (on zsus via `docker-backup-zsus` tailscale) uses image **built 2026-04-15** (`sha256:6f755a7e1de4...`), Up 6 weeks, restarts=0. It is the only docker_backup-app image on the host. - `grep` for `DOCKER_BACKUP_ENC` / `maybeEncryptArchive` inside the running container's `/app/backend` -> **no matches**; the deployed binary does not contain the encryption code. - Compose build context on zsus (`/home/mark/docker_backup`) is at commit `5a67863`, which predates the fix (`a5b47ce`). ## Reproduction steps 1. SSH to zsus (via tailscale `docker-backup-zsus`) and inspect the running container: `podman/docker ps --filter name=docker-backup-app` -> note the image creation date (2026-04-15). 2. Enter the container and grep the backend for encryption symbols: `podman exec docker-backup-app grep -r maybeEncryptArchive /app/backend` -> no matches. 3. Run a backup from the UI (or API) and inspect the produced archive file: it has no `.enc` suffix and is stored unencrypted. ## Actual behavior Service is healthy (`/health` and `/api/health` return 200) but encryption-at-rest is **not live**: any backup made today is stored unencrypted, exactly the state #20 was opened to fix. ## Expected behavior After the #20 fix, the running `docker-backup-app` should: - Be rebuilt from `main` (`a5b47ce` or later) and redeployed on zsus (`docker compose -f docker-compose.zsus.yml build app && up -d`). - Show encryption settings in the UI; a test backup should upload with an `.enc` suffix; restore of an encrypted archive should work. ## Remediation (operator action) Rebuild + redeploy `docker-backup-app` from origin/main on zsus, then verify encryption settings appear in the UI, a test backup uploads with `.enc` suffix, and restore of the encrypted archive works. ## Related minor observations (non-blocking, from the same probe) - `routeJobToAgent` transmits the passphrase in plaintext over the agent HTTP API (internal, secret-authenticated — acceptable but worth noting). - `maybeEncryptArchive` deletes plaintext with `.catch(() => undefined)` — if unlink fails, plaintext silently persists on disk. Low risk.
Author
Owner

Verification (2026-09-18) — fix landed on remote

The verification card t_8cf4a80c checked that this issue's claim (encryption code not deployed) is no longer true.

Status of the original claim: FULLY RESOLVED.

Code on remote (origin/main)

  • a5b47ce "feat: add encryption at rest for backup archives (#20)" — present, ancestor of HEAD.
  • 7dd94b8 "fix #48: surface warning when plaintext archive unlink fails after encryption" — also present (the related hardening from the auto-decompose of this card's parent).

Live deployment on zsus

  • docker-backup-app container was rebuilt and redeployed 2026-08-16 (image sha256:0ae728d4, head 7df55cc8 at the time of redeploy).
  • Running container's /app/src/core/backup/encryption.ts contains DOCKER_BACKUP_ENC magic + maybeEncryptArchive + .enc extension wiring (delta.ts, executor.ts).
  • /health and /api/health return 200.

End-to-end verification (real artifacts, 2026-08-19)

  • Real ciphertext archive /backups/full/cmswbp5f20006aq7x0k2kqhxv.tar.enc (5183 B) in the live docker_backup_backups volume. file reports data (not gzip/tar/zip); hexdump confirms DOCKER_BACKUP_ENC magic + version 1 + algo 1 (AES-256-GCM).
  • Tooling-side decrypt with PBKDF2-SHA256 100k + AES-256-GCM yields a valid POSIX tar with the expected hello.txt / sub/world.txt / sub/data.json.
  • Restore round-trip via POST /api/volumes/{id}/restore (202, contents match source after tampering).
  • UI exposes encryption-at-rest settings (toggle + passphrase input bound to backup.encryptionEnabled / backup.encryptionKey in the deployed bundle).
  • Live Setting table: encryptionEnabled=true, encryptionKey="\"probe-passphrase-2026-08-16\"" (left enabled by the verifier for the operator to rotate).

Why #47 stays open

This issue was filed by the post-deploy probe as a finding ("merged but never deployed"). Its Remediation requires operator confirmation of deployment + working restore, which is now demonstrated live. Closing the issue is the operator's call — it is no longer a code-fix request; the fix has landed and is live. Operator action remaining: rotate the probe passphrase to a real secret via Settings → Backup (note: there is a separate passphrase double-encoding defect tracked in #48 follow-up — the effective key currently includes literal quotes; rotation under a fresh passphrase that goes through the same writer will continue to double-encode until #48's follow-up lands).

Verification card: kanban t_8cf4a80c complete.

## Verification (2026-09-18) — fix landed on remote The verification card `t_8cf4a80c` checked that this issue's claim (encryption code not deployed) is no longer true. **Status of the original claim: FULLY RESOLVED.** ### Code on remote (origin/main) - `a5b47ce` "feat: add encryption at rest for backup archives (#20)" — present, ancestor of HEAD. - `7dd94b8` "fix #48: surface warning when plaintext archive unlink fails after encryption" — also present (the related hardening from the auto-decompose of this card's parent). ### Live deployment on zsus - `docker-backup-app` container was rebuilt and redeployed 2026-08-16 (image `sha256:0ae728d4`, head `7df55cc8` at the time of redeploy). - Running container's `/app/src/core/backup/encryption.ts` contains `DOCKER_BACKUP_ENC` magic + `maybeEncryptArchive` + `.enc` extension wiring (delta.ts, executor.ts). - `/health` and `/api/health` return 200. ### End-to-end verification (real artifacts, 2026-08-19) - Real ciphertext archive `/backups/full/cmswbp5f20006aq7x0k2kqhxv.tar.enc` (5183 B) in the live `docker_backup_backups` volume. `file` reports `data` (not gzip/tar/zip); hexdump confirms `DOCKER_BACKUP_ENC` magic + version 1 + algo 1 (AES-256-GCM). - Tooling-side decrypt with PBKDF2-SHA256 100k + AES-256-GCM yields a valid POSIX tar with the expected hello.txt / sub/world.txt / sub/data.json. - Restore round-trip via POST /api/volumes/{id}/restore (202, contents match source after tampering). - UI exposes encryption-at-rest settings (toggle + passphrase input bound to `backup.encryptionEnabled` / `backup.encryptionKey` in the deployed bundle). - Live Setting table: `encryptionEnabled=true`, `encryptionKey="\"probe-passphrase-2026-08-16\""` (left enabled by the verifier for the operator to rotate). ### Why #47 stays open This issue was filed by the post-deploy probe as a finding ("merged but never deployed"). Its Remediation requires operator confirmation of deployment + working restore, which is now demonstrated live. Closing the issue is the operator's call — it is **no longer a code-fix request**; the fix has landed and is live. Operator action remaining: rotate the probe passphrase to a real secret via Settings → Backup (note: there is a separate passphrase double-encoding defect tracked in #48 follow-up — the effective key currently includes literal quotes; rotation under a fresh passphrase that goes through the same writer will continue to double-encode until #48's follow-up lands). Verification card: kanban `t_8cf4a80c` complete.
Sign in to join this conversation.
No description provided.