feat: bring a picture in from a web address, and the Files pages (#1181) #1213

Merged
claude-bot merged 17 commits from feat/1181-pictures-and-files into main 2026-10-17 12:55:20 +00:00
Collaborator

Fixes #1181: bring a picture in from a web address, saved as an upload, and the Files pages. The owner's decisions are on #1181, including Q181 (a) and support access (17 Oct).

Server

  • Migration a1181f11e5a1 (now revising a1190e7e0a01): where a stored picture came from (source_url, fetched_at, plus who added a page picture and when), and campaigns.media_add_mode (editors or gm_only). The mode is enforced on every upload route.
  • Bring it in (POST …/lore/{eid}/attachments/from-url), for anyone who can edit the page. Suggesters can't.
  • Wiki health: "Pictures still on other sites", with a GM-only bring-in that rewrites the text as an undoable edit.
  • Campaign Files (GM only):
    • list, filters, previews and thumbnails;
    • removal shows the text that would break;
    • orphans are listed separately.
  • Admin → Files shows the list only. Opening or previewing another campaign's file is a POST that writes files.opened_by_admin to the audit log and returns a 120 s token bound to that admin.

The fetcher (shared with the page-picture "from a web address")

  • The existing page-picture fetch followed redirects without checking them. It's fixed here: every hop is re-checked, connections are pinned to the checked address, https only, and the size cap is enforced while streaming.
  • An independent security review found that a gzip bomb could inflate about 190 MiB before the cap fired. Now Accept-Encoding: identity is sent, any Content-Encoding is refused, and the cap counts wire bytes.
  • Also from the review:
    • NAT64, 6to4, Teredo, fec0::/10, ::/96 and multicast are blocked, and embedded IPv4 addresses are checked;
    • thumbnails are pixel-bounded;
    • a test proves the pool uses the guard;
    • trust_env=False.
  • Rate limits: 30 per person and 60 per campaign per 10 minutes.

Screens

  • Settings → Files: grid or table, a viewer with zoom, removal, orphans.
  • Settings → Wiki: "Who can add pictures and files".
  • The editor's "Bring it in", and the Wiki health row with Undo.
  • Admin → Files with audited Preview and Open.
  • One line on the Privacy page.

Tests

  • Backend: tests/wiki and friends, 1768 passed; then the security fixes, 132 passed.
  • Frontend: targeted vitest, with load timeouts rerun.
  • Journeys on docker-test: a11y admin and campaign pages, J8 pictures and Link it, 15 passed on desktop, phone and keyboard.
  • The 4.6.0 integration rehearsal (integ/4.6.0): 10,333 backend tests and 187 journeys passed.

CATALOGUE ids renumbered for 4.6.0: CAMP-22 and IS-515 to IS-521 (the line of the night's row is now IS-515).

🤖 Generated with Claude Code

Fixes #1181: bring a picture in from a web address, saved as an upload, and the Files pages. The owner's decisions are on #1181, including Q181 (a) and support access (17 Oct). **Server** - **Migration `a1181f11e5a1`** (now revising `a1190e7e0a01`): where a stored picture came from (`source_url`, `fetched_at`, plus who added a page picture and when), and `campaigns.media_add_mode` (`editors` or `gm_only`). The mode is enforced on every upload route. - **Bring it in** (`POST …/lore/{eid}/attachments/from-url`), for anyone who can edit the page. Suggesters can't. - **Wiki health:** "Pictures still on other sites", with a GM-only bring-in that rewrites the text as an undoable edit. - **Campaign Files** (GM only): - list, filters, previews and thumbnails; - removal shows the text that would break; - orphans are listed separately. - **Admin → Files** shows the list only. Opening or previewing another campaign's file is a POST that writes `files.opened_by_admin` to the audit log and returns a 120 s token bound to that admin. **The fetcher (shared with the page-picture "from a web address")** - The existing page-picture fetch followed redirects without checking them. It's fixed here: every hop is re-checked, connections are pinned to the checked address, https only, and the size cap is enforced while streaming. - An independent security review found that a gzip bomb could inflate about 190 MiB before the cap fired. Now `Accept-Encoding: identity` is sent, any `Content-Encoding` is refused, and the cap counts wire bytes. - Also from the review: - NAT64, 6to4, Teredo, `fec0::/10`, `::/96` and multicast are blocked, and embedded IPv4 addresses are checked; - thumbnails are pixel-bounded; - a test proves the pool uses the guard; - `trust_env=False`. - Rate limits: 30 per person and 60 per campaign per 10 minutes. **Screens** - Settings → Files: grid or table, a viewer with zoom, removal, orphans. - Settings → Wiki: "Who can add pictures and files". - The editor's "Bring it in", and the Wiki health row with Undo. - Admin → Files with audited Preview and Open. - One line on the Privacy page. **Tests** - Backend: `tests/wiki` and friends, 1768 passed; then the security fixes, 132 passed. - Frontend: targeted vitest, with load timeouts rerun. - Journeys on docker-test: a11y admin and campaign pages, J8 pictures and Link it, 15 passed on desktop, phone and keyboard. - The 4.6.0 integration rehearsal (`integ/4.6.0`): 10,333 backend tests and 187 journeys passed. CATALOGUE ids renumbered for 4.6.0: CAMP-22 and IS-515 to IS-521 (the line of the night's row is now IS-515). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Merge main into feat/1181-pictures-and-files; its migration follows a1190e7e0a01; catalogue ids renumbered (CAMP-22, IS-515 to IS-521)
All checks were successful
CI / Bot/backend version sync (pull_request) Successful in 27s
CI / Backend lint (ruff) (pull_request) Successful in 34s
CI / Does this change touch the recording path? (pull_request) Successful in 11s
CI / Which journeys does this change need? (pull_request) Successful in 10s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Feedback site tests (pull_request) Successful in 48s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m1s
CI / Bot tests and audit (pull_request) Successful in 2m3s
CI / Docker image build (pull_request) Successful in 2m22s
CI / End-to-end journeys (phone) (pull_request) Successful in 10m34s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 11m21s
CI / End-to-end journeys (desktop) (pull_request) Successful in 11m45s
CI / Frontend (shard 2/2) (pull_request) Successful in 15m33s
CI / Frontend (shard 1/2) (pull_request) Successful in 17m35s
CI / Backend migration, tests, and audit (pull_request) Successful in 49m11s
9e0e13f1f3
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-17 12:06:06 +00:00
Sign in to join this conversation.
No description provided.