fix(webapp): read-only routes no longer write (#1206) #1294

Merged
claude-bot merged 1 commit from fix/1206-read-routes-write into main 2026-10-30 21:50:49 +00:00
Collaborator

Closes #1206.

  • Character creations: list_creations (sheet_creation.waiting) filters creations idle past KEEP_FOR in its query instead of deleting them. A daily Beat task, purge_stale_sheet_creations (purge_all_stale), does the deleting. clear_stale stays for the POST start route.
  • Legacy lore pictures: download_lore_image no longer moves a pre-#894 public picture or rewrites image_url. It serves the private file by X-Accel-Redirect, or a not-yet-swept legacy file in place via FileResponse, after the same audience check. A daily, idempotent sweep (sweep_legacy_lore_images → move_legacy_image) moves the file and rewrites the URL. If a private copy already exists, the public one is removed.
  • A third writer the scan missed: the files route's _content_relative called the same move two helpers down. It's now _content_response, and serves in place too.
  • test_get_routes_do_not_write.py: the allow-list is down to auth.callback, auth.discord_link and the two content-pack exports.

Security, reviewed: the legacy file's path comes only from the route's typed campaign_id / entry_id (_legacy_dir), never from image_url, so a request can't steer it elsewhere. The reader's audience check runs first, unchanged.

Tests:

  • The creations test is "not listed, and the row still exists". The task deletes the stale row and keeps the fresh one, and a second run removes nothing.
  • The legacy-picture test checks:
    • after a GET, nothing moved and the URL is unchanged;
    • after the sweep, the file has moved and the URL is rewritten;
    • a second sweep does nothing;
    • the route then serves it by redirect.

Checked on docker-test:

  • Full backend suite (-m "not synthetic"): 10,391 passed, no failures.
  • ruff format and check, version sync, and the changelog check all pass.

Note: until the first sweep runs after deploy, an unswept legacy picture is served by the backend rather than nginx. The deploy can run the sweep at once: celery -A app.tasks.celery_app call app.tasks.reminder_tasks.sweep_legacy_lore_images.

🤖 Generated with Claude Code

Closes #1206. - **Character creations:** `list_creations` (`sheet_creation.waiting`) filters creations idle past `KEEP_FOR` in its query instead of deleting them. A daily Beat task, `purge_stale_sheet_creations` (`purge_all_stale`), does the deleting. `clear_stale` stays for the POST `start` route. - **Legacy lore pictures:** `download_lore_image` no longer moves a pre-#894 public picture or rewrites `image_url`. It serves the private file by `X-Accel-Redirect`, or a not-yet-swept legacy file in place via `FileResponse`, after the same audience check. A daily, idempotent sweep (`sweep_legacy_lore_images` → `move_legacy_image`) moves the file and rewrites the URL. If a private copy already exists, the public one is removed. - **A third writer the scan missed:** the files route's `_content_relative` called the same move two helpers down. It's now `_content_response`, and serves in place too. - `test_get_routes_do_not_write.py`: the allow-list is down to `auth.callback`, `auth.discord_link` and the two content-pack exports. **Security, reviewed:** the legacy file's path comes only from the route's typed `campaign_id` / `entry_id` (`_legacy_dir`), never from `image_url`, so a request can't steer it elsewhere. The reader's audience check runs first, unchanged. **Tests:** - The creations test is "not listed, and the row still exists". The task deletes the stale row and keeps the fresh one, and a second run removes nothing. - The legacy-picture test checks: - after a GET, nothing moved and the URL is unchanged; - after the sweep, the file has moved and the URL is rewritten; - a second sweep does nothing; - the route then serves it by redirect. **Checked on docker-test:** - Full backend suite (`-m "not synthetic"`): 10,391 passed, no failures. - ruff format and check, version sync, and the changelog check all pass. **Note:** until the first sweep runs after deploy, an unswept legacy picture is served by the backend rather than nginx. The deploy can run the sweep at once: `celery -A app.tasks.celery_app call app.tasks.reminder_tasks.sweep_legacy_lore_images`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(webapp): read-only routes no longer write (#1206)
All checks were successful
CI / Bot/backend version sync (pull_request) Successful in 32s
CI / Feedback site tests (pull_request) Successful in 55s
CI / Backend lint (ruff) (pull_request) Successful in 1m0s
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 / Which journeys does this change need? (pull_request) Successful in 17s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m43s
CI / Bot tests and audit (pull_request) Successful in 3m6s
CI / Docker image build (pull_request) Successful in 5m24s
CI / Frontend (shard 2/2) (pull_request) Successful in 8m59s
CI / Frontend (shard 1/2) (pull_request) Successful in 11m22s
CI / End-to-end journeys (desktop) (pull_request) Successful in 14m5s
CI / Backend migration, tests, and audit (pull_request) Successful in 53m25s
a0268260f4
- list_creations filters creations idle past KEEP_FOR in its query instead
  of deleting them; a daily Beat task (purge_stale_sheet_creations) does
  the deleting.
- download_lore_image serves a legacy public picture where it is (same
  audience check, a backend FileResponse until it is moved) instead of
  moving it and rewriting image_url; a daily idempotent sweep
  (sweep_legacy_lore_images) moves it into the private store. The files
  route's _content_relative had the same move two helpers down, which the
  scan test missed; it now serves in place too.
- test_get_routes_do_not_write's allow-list is down to the two auth
  callbacks and the content-pack exports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-bot scheduled this pull request to auto merge when all checks succeed 2026-10-30 20:57:23 +00:00
claude-bot deleted branch fix/1206-read-routes-write 2026-10-30 21:50:50 +00:00
Sign in to join this conversation.
No description provided.