Read-only routes that still write: the ones the scan found #1178

Closed
opened 2026-10-13 18:03:29 +00:00 by claude-bot · 1 comment
Collaborator

Follow-up to #1163. tests/test_get_routes_do_not_write.py (PR #1177) allow-lists these GET handlers as writers. None is a security hole found in use; each is a GET that a prefetch, a crawler or a link preview can trigger.

Found by the scan, not yet reviewed

  • Planning names, GET /planning/names/{category}: writes a generation_results row on every cached call, and on a cold cache runs the LLM synchronously inside a GET. The one worth changing: make generating a POST and the GET a pure read of what exists.
  • GET /admin/settings/secrets-health: audit_encrypted_settings upgrades plaintext secrets and commits. Admin only. Move the upgrade to startup or an explicit action; the GET should only report.
  • Content pack exports (export_campaign_pack, export_pack_admin): an audit row per download. Probably right as it is (a download is the event); confirm and give the allow-list entry its real reason.
  • Server ownership offer, users.get_server_owner: lazy expiry with a commit, the same shape as the campaign one.

Confirmed fine, but avoidable (each could lose its allow-list entry)

  • Campaign and server ownership offers: compute expiry in memory, expire in the poller.
  • ensure_builtin on the four sheet-definition GETs: run at startup or in a migration.
  • The lore image GET's move of a legacy file: a one-off migration.

Limits of the scan (from its docstring): it follows one level of app.* callees and recognises sessions named db, session, sess or s and Redis clients with "redis" in the name. A write behind a second helper is not seen.

Not in 4.5.1.

Follow-up to #1163. `tests/test_get_routes_do_not_write.py` (PR #1177) allow-lists these GET handlers as writers. None is a security hole found in use; each is a GET that a prefetch, a crawler or a link preview can trigger. **Found by the scan, not yet reviewed** - **Planning names**, `GET /planning/names/{category}`: writes a `generation_results` row on every cached call, and on a cold cache runs the LLM synchronously inside a GET. The one worth changing: make generating a POST and the GET a pure read of what exists. - **`GET /admin/settings/secrets-health`**: `audit_encrypted_settings` upgrades plaintext secrets and commits. Admin only. Move the upgrade to startup or an explicit action; the GET should only report. - **Content pack exports** (`export_campaign_pack`, `export_pack_admin`): an audit row per download. Probably right as it is (a download is the event); confirm and give the allow-list entry its real reason. - **Server ownership offer**, `users.get_server_owner`: lazy expiry with a commit, the same shape as the campaign one. **Confirmed fine, but avoidable** (each could lose its allow-list entry) - Campaign and server ownership offers: compute expiry in memory, expire in the poller. - `ensure_builtin` on the four sheet-definition GETs: run at startup or in a migration. - The lore image GET's move of a legacy file: a one-off migration. **Limits of the scan** (from its docstring): it follows one level of `app.*` callees and recognises sessions named `db`, `session`, `sess` or `s` and Redis clients with "redis" in the name. A write behind a second helper is not seen. Not in 4.5.1.
Author
Collaborator

Done for 4.6.0 (PR #1208, merged 17 Oct). Planning names generate by POST, secrets health only reports, ownership offers expire in a Beat task, and the sheet-definition GETs no longer seed. The allow-list in test_get_routes_do_not_write.py is down to 5 entries, each with its real reason. The two that remain are filed as #1206: deleting stale character creations, and the legacy lore picture move.

Done for 4.6.0 (PR #1208, merged 17 Oct). Planning names generate by POST, secrets health only reports, ownership offers expire in a Beat task, and the sheet-definition GETs no longer seed. The allow-list in test_get_routes_do_not_write.py is down to 5 entries, each with its real reason. The two that remain are filed as #1206: deleting stale character creations, and the legacy lore picture move.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#1178
No description provided.