feat(webapp): backup credentials write-only, redaction by value, the audio archive's path, and the missing audit events (#795, lane 9 C) #848

Merged
claude-bot merged 1 commit from build/lane9-c-backups-audit into main 2026-09-26 17:15:13 +00:00
Collaborator

Closes #795. Part of lane 9 (#735), server slice C; nothing in it waits on A1–A11.

#795 (1): off-site credentials are write-only

  • GET /api/admin/backup/config returns remote through redact_remote(), plus credentials_set. That flag is true when an inline parameter block was redacted.
  • PUT:
    • an omitted remote keeps the stored one;
    • the redacted form sent back unchanged also keeps it, so a save that doesn't touch the field can't overwrite the credentials with the placeholder;
    • anything else, including "" to clear it, is validated and stored.
  • The config's audit event no longer carries the remote at all, only remote_changed/remote_cleared.
  • The legacy Admin form gains one hint line; its round-trip logic is unchanged. Lane 9 page 4b redraws it.

#795 (2): redaction by known values

_redact_text still matches the remote's shape, and now also scrubs every parameter value longer than 4 characters wherever it appears. That covers a secret quoted on its own, re-ordered or re-quoted parameters, and repeats. Values of 4 characters or fewer (s3, true) are left readable.

#795 (3): the audio archive's path

  • Migration a2c4e6f8b0d1: backup_logs.audio_archive_path (nullable), added on top of e5f7a9b1c3d4.
  • Backups record the path when they write the archive.
  • Verify reads it back: from the recorded path, or for older rows a path derived from the dump's name. It checks the path stays inside the backup root, as it does for the dump.
  • Verify stays silent when no archive was ever made (include_audio is off by default). It reports only a recorded archive that's missing or a file that won't open.
  • The one-head pin moves to the new migration.
  • Uploaded images (IS-396, media_archive_path) wait for the owner's A6.

The missing audit events (lane 9 headline 10)

Eight admin writes were silent and now log one event each, with the actor and no secrets:

  • both retention PUTs;
  • audio restore and retry;
  • the wiki pipeline and summary modes;
  • Back up now;
  • the test email.

Tests

  • Targeted backend: 347 passed.
    • Covers test_admin, test_backup_offsite, test_audit_log, the new migration module, test_drop_scene_text_migration, test_notifications, test_setup and test_retention_cap.
    • 35 of these tests are new.
  • Migration up, down and up on Postgres 16, with one head.
  • Frontend: 2,471 passed; eslint has 0 errors.
  • ruff is clean.

The docs changes:

  • docs/API.md gains a Database Backup section; these endpoints weren't documented before.
  • docs/OPERATIONS.md's backup notes are updated.

🤖 Generated with Claude Code

Closes #795. Part of lane 9 (#735), server slice C; nothing in it waits on A1–A11. ## #795 (1): off-site credentials are write-only - `GET /api/admin/backup/config` returns `remote` through `redact_remote()`, plus `credentials_set`. That flag is true when an inline parameter block was redacted. - `PUT`: - an omitted remote keeps the stored one; - the redacted form sent back unchanged also keeps it, so a save that doesn't touch the field can't overwrite the credentials with the placeholder; - anything else, including `""` to clear it, is validated and stored. - The config's audit event no longer carries the remote at all, only `remote_changed`/`remote_cleared`. - The legacy Admin form gains one hint line; its round-trip logic is unchanged. Lane 9 page 4b redraws it. ## #795 (2): redaction by known values `_redact_text` still matches the remote's shape, and now also scrubs every parameter value longer than 4 characters wherever it appears. That covers a secret quoted on its own, re-ordered or re-quoted parameters, and repeats. Values of 4 characters or fewer (`s3`, `true`) are left readable. ## #795 (3): the audio archive's path - Migration `a2c4e6f8b0d1`: `backup_logs.audio_archive_path` (nullable), added on top of `e5f7a9b1c3d4`. - Backups record the path when they write the archive. - Verify reads it back: from the recorded path, or for older rows a path derived from the dump's name. It checks the path stays inside the backup root, as it does for the dump. - Verify stays silent when no archive was ever made (`include_audio` is off by default). It reports only a recorded archive that's missing or a file that won't open. - The one-head pin moves to the new migration. - Uploaded images (IS-396, `media_archive_path`) wait for the owner's A6. ## The missing audit events (lane 9 headline 10) Eight admin writes were silent and now log one event each, with the actor and no secrets: - both retention PUTs; - audio restore and retry; - the wiki pipeline and summary modes; - Back up now; - the test email. ## Tests - Targeted backend: 347 passed. - Covers `test_admin`, `test_backup_offsite`, `test_audit_log`, the new migration module, `test_drop_scene_text_migration`, `test_notifications`, `test_setup` and `test_retention_cap`. - 35 of these tests are new. - Migration up, down and up on Postgres 16, with one head. - Frontend: 2,471 passed; eslint has 0 errors. - ruff is clean. The docs changes: - `docs/API.md` gains a Database Backup section; these endpoints weren't documented before. - `docs/OPERATIONS.md`'s backup notes are updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(webapp): backups audit — write-only credentials, value redaction, archive path (#795, lane 9 PR C)
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 57s
CI / Backend lint (ruff) (pull_request) Successful in 27s
CI / Feedback site tests (pull_request) Successful in 43s
CI / Bot tests and audit (pull_request) Successful in 2m6s
CI / Does this change touch the recording path? (pull_request) Successful in 19s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Bot/backend version sync (pull_request) Successful in 1m3s
CI / Docker image build (pull_request) Successful in 53s
CI / Frontend tests, audit, and build (pull_request) Successful in 19m27s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 18m37s
CI / Backend migration, tests, and audit (pull_request) Successful in 37m34s
b336b40ade
Server PR C from the lane 9 survey: #795's three backup fixes plus the
missing admin audit events from headline 10.

- GET/PUT /api/admin/backup/config make the off-site remote's credentials
  write-only: the redacted form round-trips, an unchanged save keeps the
  stored value, and the audit event records only whether the remote
  changed or was cleared, never its value.
- rclone's stdout/stderr are scrubbed by each configured parameter's value
  (>4 chars) as well as by the remote's shape, catching a credential quoted
  on its own or with parameters re-ordered/re-quoted.
- backup_logs.audio_archive_path (migration a2c4e6f8b0d1) records where the
  audio snapshot went; POST /admin/backup/verify reads it back the way it
  reads the dump, deriving the old name for a row from before the column.
- log_event added to both retention PUTs, audio restore/retry, the wiki
  pipeline and summarisation mode PUTs, backup/run, and the test email —
  the exceptions to the audit log's "recorded here and nowhere else".

Tests: 227 targeted backend tests (test_admin, test_backup_offsite,
test_audit_log, the new migration module, the two migration-chain test
files) plus alembic upgrade/downgrade/upgrade against real postgres, all
green. Full frontend suite (2471 tests) and eslint (0 errors) green after
the minimal Admin.jsx credentials-set hint. ruff check + format clean.

docs/API.md gains the backup endpoints (previously undocumented);
docs/OPERATIONS.md's backup section covers both fixes. CHANGELOG under
Unreleased: Security (credentials, redaction) and Changed (archive path,
audit events).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign in to join this conversation.
No description provided.