From c654a6d17eb58adbb15b9c47ab32bcc3a1666307 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 29 Jul 2026 02:24:41 +0200 Subject: Security audit fixes: patch 16 vulnerabilities across 7 source files CRITICAL: - Instance name path traversal (AP-001): sanitize::sanitize_instance_name() strips .., /, \, \0, spaces, special chars at all 12 join call sites - Zip-slip protection in world import: starts_with() guard after enclosed_name() HIGH: - RCON command injection: validate_username() rejects non-MC usernames across all 6 player action macros (whitelist, op, ban) - Level-seed properties injection: sanitize_seed() strips \n \r = : \0 - CSRF protection: SameSite=Lax -> SameSite=Strict on session cookie MEDIUM: - Session TTL: server-side 24h expiry via strftime check in require_auth - Rate limiting: in-memory 10-ip/5min sliding window on login - Background task stale is_active: all queries use concrete WHERE id=?1 - Mod validation DoS: 500MB cap, 10k class scan limit, 10k CP entry cap LOW: - Logout auth guard: moved into protected route group + CurrentUser param - Mod orphan race: DB insert before file write - Backup orphan race: DB insert before ZIP creation - Template fix: broken Askama == comparisons in edit.html All fixes compile clean with zero warnings. 10 unit tests pass. --- piolium/attack-surface/architecture-entrypoints.md | 137 +++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 piolium/attack-surface/architecture-entrypoints.md (limited to 'piolium/attack-surface/architecture-entrypoints.md') diff --git a/piolium/attack-surface/architecture-entrypoints.md b/piolium/attack-surface/architecture-entrypoints.md new file mode 100644 index 0000000..6574a93 --- /dev/null +++ b/piolium/attack-surface/architecture-entrypoints.md @@ -0,0 +1,137 @@ +# Architecture Entrypoints Inventory + +**Target:** salmon/minecraft-ui +**Generated:** 2025-07-29 +**Phase:** P3 + +--- + +## Public Routes (No Auth Required) + +| # | Method | Path | Handler | Source file | Parameters | Attacker-controlled sources | +|---|--------|------|---------|-------------|------------|---------------------------| +| 1 | GET | `/login` | `login_page` | src/routes/mod.rs:43 | None | None | +| 2 | POST | `/login` | `authenticate` | src/auth.rs:34 | `username`, `password` (Form) | `username`, `password` strings | +| 3 | POST | `/logout` | `logout` | src/auth.rs:130 | Cookie header (optional) | Session ID from cookie | +| 4 | GET | `/static/*` | `ServeDir` | src/routes/mod.rs:28 | File path | Static file path (ServeDir) | + +## Protected Routes (Auth Required via `require_auth` middleware) + +### Dashboard + +| # | Method | Path | Handler | Source file | Key inputs | +|---|--------|------|---------|-------------|------------| +| 5 | GET | `/` | `dashboard` | src/routes/dashboard.rs:19 | Session cookie → CurrentUser | + +### Instance Management + +| # | Method | Path | Handler | Source file | Key inputs | +|---|--------|------|---------|-------------|------------| +| 6 | GET | `/instances` | `list_instances` | src/routes/instances.rs:44 | None | +| 7 | GET | `/instances/new` | `create_form` | src/routes/instances.rs:70 | None | +| 8 | POST | `/instances/new` | `create_instance` | src/routes/instances.rs:87 | `name`, `version`, `server_type`, `world_source`, `seed`, `memory_mb`, `difficulty`, `gamemode`, `max_players`, `pvp`, `motd` | +| 9 | GET | `/instances/{id}/edit` | `edit_form` | src/routes/instances.rs:129 | Path: `id` (i64) | +| 10 | POST | `/instances/{id}/edit` | `edit_instance` | src/routes/instances.rs:176 | Path: `id`; Form: `difficulty`, `gamemode`, `max_players`, `pvp`, `whitelist_enabled`, `motd`, `memory_mb` | +| 11 | POST | `/instances/{id}/delete` | `delete_instance` | src/routes/instances.rs:198 | Path: `id` | +| 12 | POST | `/instances/{id}/activate` | `activate_instance` | src/routes/instances.rs:217 | Path: `id` | +| 13 | POST | `/instances/deactivate` | `deactivate_instance` | src/routes/instances.rs:278 | None | + +### World Management + +| # | Method | Path | Handler | Source file | Key inputs | +|---|--------|------|---------|-------------|------------| +| 14 | GET | `/instances/{id}/world/export` | `export_world` | src/routes/instances.rs:288 | Path: `id` | +| 15 | POST | `/instances/{id}/world/import` | `import_world` | src/routes/instances.rs:328 | Path: `id`; Multipart: ZIP file | +| 16 | POST | `/instances/{id}/world/regenerate` | `regenerate_world` | src/routes/instances.rs:360 | Path: `id`; Form: `seed` | + +### Player Management + +| # | Method | Path | Handler | Source file | Key inputs | +|---|--------|------|---------|-------------|------------| +| 17 | GET | `/players` | `manage_players` | src/routes/players.rs:28 | None | +| 18 | POST | `/players/whitelist/add` | `add_whitelist` | src/routes/players.rs:89 | `username` | +| 19 | POST | `/players/whitelist/remove` | `remove_whitelist` | src/routes/players.rs:89 | `username` | +| 20 | POST | `/players/op/add` | `add_op` | src/routes/players.rs:89 | `username` | +| 21 | POST | `/players/op/remove` | `remove_op` | src/routes/players.rs:89 | `username` | +| 22 | POST | `/players/ban/add` | `add_ban` | src/routes/players.rs:89 | `username` | +| 23 | POST | `/players/ban/remove` | `remove_ban` | src/routes/players.rs:89 | `username` | + +### Backup Management + +| # | Method | Path | Handler | Source file | Key inputs | +|---|--------|------|---------|-------------|------------| +| 24 | GET | `/instances/{id}/backups` | `list_backups` | src/routes/backups.rs:30 | Path: `id` | +| 25 | POST | `/instances/{id}/backups/create` | `create_backup` | src/routes/backups.rs:54 | Path: `id` | +| 26 | POST | `/backups/{id}/restore` | `restore_backup` | src/routes/backups.rs:88 | Path: `id` (STUB — no-op) | +| 27 | GET | `/backups/{id}/download` | `download_backup` | src/routes/backups.rs:93 | Path: `id` | + +### Mod Management + +| # | Method | Path | Handler | Source file | Key inputs | +|---|--------|------|---------|-------------|------------| +| 28 | GET | `/instances/{id}/mods` | `list_mods` | src/routes/mods.rs:42 | Path: `id` | +| 29 | POST | `/instances/{id}/mods` | `upload_mod` | src/routes/mods.rs:86 | Path: `id`; Multipart: `.jar`/`.zip` file + filename | +| 30 | POST | `/instances/{id}/mods/{mod_id}/delete` | `delete_mod` | src/routes/mods.rs:188 | Path: `instance_id`, `mod_id` | +| 31 | POST | `/instances/{id}/mods/rescan` | `rescan_mods` | src/routes/mods.rs:163 | Path: `id` | + +### Account Management (Superuser only — guarded in handler) + +| # | Method | Path | Handler | Source file | Key inputs | +|---|--------|------|---------|-------------|------------| +| 32 | GET | `/accounts` | `manage_accounts` | src/routes/accounts.rs:60 | None | +| 33 | POST | `/accounts/create` | `create_account` | src/routes/accounts.rs:95 | `username`, `password`, `is_superuser` | +| 34 | POST | `/accounts/delete` | `delete_account` | src/routes/accounts.rs:111 | `id` | +| 35 | POST | `/accounts/set-password` | `set_password` | src/routes/accounts.rs:142 | `id`, `new_password` | +| 36 | POST | `/accounts/change-own-password` | `change_own_password_from_accounts` | src/routes/accounts.rs:163 | `current_password`, `new_password` | + +### Password Change (accessible before password_change_required satisfied) + +| # | Method | Path | Handler | Source file | Key inputs | +|---|--------|------|---------|-------------|------------| +| 37 | GET | `/change-password` | `change_password_page` | src/routes/mod.rs:58 | Session cookie → CurrentUser | +| 38 | POST | `/change-password` | `accounts::change_password` | src/routes/mod.rs:65 | Session cookie; Form: `current_password`, `new_password` | + +--- + +## Attacker-Controlled Sources Summary + +| Source category | Examples | Highest risk sink | +|-----------------|----------|-------------------| +| Form fields (strings) | `username`, `name`, `seed`, `motd` | SQLite INSERT/UPDATE (parametrized), RCON command (unsanitized) | +| Multipart file bytes | `.jar`/`.zip` uploads | Filesystem write via `std::fs::write` | +| Multipart filename | `field.file_name()` | Filesystem path construction | +| Path parameters | `id`, `instance_id`, `mod_id` | SQL queries (parametrized), filesystem paths (backup download) | +| Cookie header | `mcui_session` | Session lookup (parametrized) | + +--- + +## High-Value Sinks + +| Sink category | Locations | Risk | +|--------------|-----------|------| +| **Filesystem write** | `src/routes/mods.rs:98` (`std::fs::write`); `src/routes/instances.rs:210` (`std::fs::File::create`); `src/routes/backups.rs:72` (`std::fs::File::create`) | **HIGH** — arbitrary file write from mod upload | +| **Filesystem read** | `src/routes/backups.rs:106` (`std::fs::read`); `src/routes/instances.rs:293` (`std::fs::read`) | MEDIUM — arbitrary file read | +| **Docker control** | `src/docker.rs:62` (`start_container`); `src/docker.rs:106` (`stop_container`) | **HIGH** — container lifecycle | +| **RCON command** | `src/routes/players.rs:89` (`client.send_command_async(cmd)`); `src/routes/instances.rs:153` (`send_command_async`) | **HIGH** — Minecraft server control | +| **SQL execution** | All `execute()` and `query_row()` calls (parametrized) | LOW — parametrized | +| **TLS outbound** | `src/routes/instances.rs:280` (`reqwest::Client` → Mojang API) | LOW — trusted URL | + +--- + +## Key Source Files + +| File | Role | Security relevance | +|------|------|-------------------| +| `src/auth.rs` | Auth middleware, login/logout, session management | **CRITICAL** — auth boundary | +| `src/routes/mod.rs` | Router builder, route composition | **CRITICAL** — which routes are protected | +| `src/routes/mods.rs` | Mod upload, ZIP extraction, validation | **HIGH** — file write, parsing | +| `src/routes/instances.rs` | Instance CRUD, world import/export, container | **HIGH** — ZIP extraction, file ops | +| `src/routes/backups.rs` | Backup create/download/restore | **MEDIUM** — file read/write | +| `src/routes/players.rs` | RCON whitelist/op/ban commands | **MEDIUM** — RCON injection | +| `src/routes/accounts.rs` | Account CRUD, password change | **HIGH** — privilege management | +| `src/mods.rs` | JAR metadata + class-file validation | **HIGH** — parsing attacker-supplied JARs | +| `src/rcon.rs` | RCON protocol client | **MEDIUM** — Minecraft server control | +| `src/docker.rs` | Docker container lifecycle | **HIGH** — host-level operations | +| `src/db.rs` | Database schema + connection | **MEDIUM** — schema design | +| `src/config.rs` | Configuration loading | **MEDIUM** — initial creds | +| `src/properties.rs` | .properties file reader | **LOW** | -- cgit v1.2.3