# State & Concurrency Audit ## State-Holding Entities Catalogued | Entity | Table | State Columns | Allowable Values | Concurrency Risk | |--------|-------|---------------|-----------------|------------------| | Account | `accounts` | `is_superuser` | 0/1 | Low (rarely mutated) | | Account | `accounts` | `password_change_required` | 0/1 | Low | | Session | `sessions` | `created_at` | datetime | **High** — no TTL enforcement | | Instance | `instances` | `is_active` | 0/1 | **CRITICAL** — background task stale writes | | Instance | `instances` | `container_state` | stopped/pulling/booting/running/error | **CRITICAL** — not bound to instance ID in background writes | | Instance | `instances` | `has_modpack` | 0/1 | Medium — RMW across mutex acquisitions | | Instance | `instances` | `name` | TEXT (UNIQUE) | **HIGH** — name-reuse race between delete and create | | Mod | `mods` | `accepted` | 0/1 | Low | | Backup | `backups` | `filename`, `created_at` | TEXT, datetime | Medium — orphan ZIP on concurrent delete | | All | — | — | — | **No idempotency infrastructure** | **Total state-holding entities catalogued:** 10 (across 5 tables) ## Concurrency Primitives Observed | Primitive | Location | Scope | Limitation | |-----------|----------|-------|------------| | `Arc>` | `src/db.rs:7` | SQLite connection | Serialises per-statement, not per-transaction | | `MutexGuard::drop()` implicit | Every `let conn = state.db.conn()` / `drop(conn)` pattern | Statement boundaries | Mutex released between read and write in multiple handlers | | Tokio async tasks (background spawn) | `src/routes/instances.rs:457` | container lifecycle | **No instance ID binding** — uses `WHERE is_active = 1` | | SQLite WAL mode | `src/db.rs:19` (PRAGMA) | Write-ahead logging | Allows concurrent reads during writes but does not prevent any of the races documented | **No distributed locks, no advisory locks, no `SELECT ... FOR UPDATE`, no explicit BEGIN/COMMIT boundaries.** ## Idempotency Infrastructure **Absent.** No `idempotency_key` table, no deduplication tokens, no request ID storage, no unique constraints leveraged for dedup (except `accounts.username` and `instances.name`). Every POST handler is vulnerable to replay/double-submit. ## Drafts Filed | Draft | Severity | Class | Summary | |-------|----------|-------|---------| | p6-001 | CRITICAL | toctou | Background task in `activate_instance` uses `WHERE is_active = 1` (no instance ID bound). After deactivation/reactivation, stale task writes `container_state` on the wrong instance. | | p6-002 | HIGH | toctou | `delete_instance` releases DB mutex before `remove_dir_all`. Concurrent `create_instance` with same name has its data directory wiped. Cross-resource TOCTOU with data loss. | | p6-003 | MEDIUM | state-machine-violation | `edit_instance` stops container on active instances without restarting. `is_active=1` and stale `container_state='running'` persist. Instance appears running but isn't. | | p6-004 | HIGH | idempotency | No idempotency infrastructure on any POST handler. Mod upload, backup create, login, account create all vulnerable to replay/double-submit. | | p6-005 | MEDIUM | rmw-no-txn | `process_jar_upload` writes file to disk before DB INSERT. Crash between file write and DB insert leaves orphaned mod file. Also performs `has_modpack` update across separate mutex acquisition. | | p6-006 | MEDIUM | toctou | `create_backup` reads instance name, releases mutex, creates ZIP file (long I/O window), then re-acquires mutex. Concurrent instance deletion causes FK violation — orphaned ZIP file with no DB record. | **Total draft findings filed: 6**