fix: the images backup carried the key to the accounts it backs up (4234)
CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 3s
Build images / sign-extension (push) Successful in 4s
Build images / build-agent (push) Successful in 7s
CI / frontend-build (push) Successful in 23s
CI / backend-lint-and-test (push) Successful in 33s
Build images / build-web (push) Successful in 57s
Build images / smoke-web (push) Skipped
Build images / build-ml (push) Successful in 1m49s
Build images / promote (push) Skipped
CI / integration (push) Successful in 2m20s
CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 3s
Build images / sign-extension (push) Successful in 4s
Build images / build-agent (push) Successful in 7s
CI / frontend-build (push) Successful in 23s
CI / backend-lint-and-test (push) Successful in 33s
Build images / build-web (push) Successful in 57s
Build images / smoke-web (push) Skipped
Build images / build-ml (push) Successful in 1m49s
Build images / promote (push) Skipped
CI / integration (push) Successful in 2m20s
Listing a 2026-05 tarball while investigating the 4.3T `_backups` pile showed
its second and third entries:
images/secrets/
images/secrets/credential_key.b64
That is the key that decrypts the stored Patreon/SubscribeStar session
credentials, and `cookies/` sat beside it — both unexcluded, so this was true
of every images backup taken today, not just the old ones. An images tarball
is supposed to be a media archive; one that carries the operator's account
keys is a credential leak wearing a backup's name, in a single file that is
easy to copy to another disk or restore somewhere less protected. Encryption
at rest buys nothing when the key travels in the same archive.
`secrets` and `cookies` join `_backups` and `_quarantine` in one named tuple,
each with its reason recorded — the recursion that produced 4.3T of nested
tarballs is the cautionary tale for why the list is worth explaining rather
than just listing.
A restore no longer re-establishes credentials. You sign in again, which is
the correct outcome for a media backup.
Tests cover both new names and that every exclude stays root-relative — a bare
`secrets` would also match an artist folder of that name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
This commit is contained in:
@@ -134,6 +134,29 @@ def test_backup_images_excludes_backups_and_quarantine(tmp_path, fake_subprocess
|
||||
assert any("_quarantine" in e for e in excludes)
|
||||
|
||||
|
||||
def test_backup_images_excludes_credentials(tmp_path, fake_subprocess):
|
||||
"""#4234: the images tarball carried `secrets/credential_key.b64` — the key
|
||||
that decrypts the stored session cookies — and `cookies/` itself. A media
|
||||
archive must not be a credential leak; encryption at rest is worth nothing
|
||||
if the key travels with the data."""
|
||||
backup_service.backup_images(images_root=tmp_path)
|
||||
excludes = [a for a in fake_subprocess[0] if a.startswith("--exclude=")]
|
||||
assert any(e.endswith("/secrets") for e in excludes)
|
||||
assert any(e.endswith("/cookies") for e in excludes)
|
||||
|
||||
|
||||
def test_backup_images_excludes_are_root_relative(tmp_path, fake_subprocess):
|
||||
"""tar matches --exclude against the archived path, which is prefixed with
|
||||
the root's own directory name (`-C <parent> <name>`). A bare `secrets`
|
||||
would also match an ARTIST folder called secrets; the prefix is what keeps
|
||||
the exclusion to the top level."""
|
||||
backup_service.backup_images(images_root=tmp_path)
|
||||
excludes = [a for a in fake_subprocess[0] if a.startswith("--exclude=")]
|
||||
assert excludes and all(
|
||||
e.startswith(f"--exclude={tmp_path.name}/") for e in excludes
|
||||
)
|
||||
|
||||
|
||||
# --- restore_db ------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user