diff options
| author | salmonik <salmonik@tuta.io> | 2026-07-29 02:24:41 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-07-29 02:24:41 +0200 |
| commit | c654a6d17eb58adbb15b9c47ab32bcc3a1666307 (patch) | |
| tree | 545544bf7d805254415da787fad1cdc711767833 /piolium/attack-surface/architecture-entrypoints.md | |
| parent | Add per-instance mod management with client-side detection (diff) | |
| download | minecraft-ui-c654a6d17eb58adbb15b9c47ab32bcc3a1666307.tar.gz minecraft-ui-c654a6d17eb58adbb15b9c47ab32bcc3a1666307.zip | |
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.
Diffstat (limited to '')
| -rw-r--r-- | piolium/attack-surface/architecture-entrypoints.md | 137 |
1 files changed, 137 insertions, 0 deletions
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 @@ | |||
| 1 | # Architecture Entrypoints Inventory | ||
| 2 | |||
| 3 | **Target:** salmon/minecraft-ui | ||
| 4 | **Generated:** 2025-07-29 | ||
| 5 | **Phase:** P3 | ||
| 6 | |||
| 7 | --- | ||
| 8 | |||
| 9 | ## Public Routes (No Auth Required) | ||
| 10 | |||
| 11 | | # | Method | Path | Handler | Source file | Parameters | Attacker-controlled sources | | ||
| 12 | |---|--------|------|---------|-------------|------------|---------------------------| | ||
| 13 | | 1 | GET | `/login` | `login_page` | src/routes/mod.rs:43 | None | None | | ||
| 14 | | 2 | POST | `/login` | `authenticate` | src/auth.rs:34 | `username`, `password` (Form) | `username`, `password` strings | | ||
| 15 | | 3 | POST | `/logout` | `logout` | src/auth.rs:130 | Cookie header (optional) | Session ID from cookie | | ||
| 16 | | 4 | GET | `/static/*` | `ServeDir` | src/routes/mod.rs:28 | File path | Static file path (ServeDir) | | ||
| 17 | |||
| 18 | ## Protected Routes (Auth Required via `require_auth` middleware) | ||
| 19 | |||
| 20 | ### Dashboard | ||
| 21 | |||
| 22 | | # | Method | Path | Handler | Source file | Key inputs | | ||
| 23 | |---|--------|------|---------|-------------|------------| | ||
| 24 | | 5 | GET | `/` | `dashboard` | src/routes/dashboard.rs:19 | Session cookie → CurrentUser | | ||
| 25 | |||
| 26 | ### Instance Management | ||
| 27 | |||
| 28 | | # | Method | Path | Handler | Source file | Key inputs | | ||
| 29 | |---|--------|------|---------|-------------|------------| | ||
| 30 | | 6 | GET | `/instances` | `list_instances` | src/routes/instances.rs:44 | None | | ||
| 31 | | 7 | GET | `/instances/new` | `create_form` | src/routes/instances.rs:70 | None | | ||
| 32 | | 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` | | ||
| 33 | | 9 | GET | `/instances/{id}/edit` | `edit_form` | src/routes/instances.rs:129 | Path: `id` (i64) | | ||
| 34 | | 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` | | ||
| 35 | | 11 | POST | `/instances/{id}/delete` | `delete_instance` | src/routes/instances.rs:198 | Path: `id` | | ||
| 36 | | 12 | POST | `/instances/{id}/activate` | `activate_instance` | src/routes/instances.rs:217 | Path: `id` | | ||
| 37 | | 13 | POST | `/instances/deactivate` | `deactivate_instance` | src/routes/instances.rs:278 | None | | ||
| 38 | |||
| 39 | ### World Management | ||
| 40 | |||
| 41 | | # | Method | Path | Handler | Source file | Key inputs | | ||
| 42 | |---|--------|------|---------|-------------|------------| | ||
| 43 | | 14 | GET | `/instances/{id}/world/export` | `export_world` | src/routes/instances.rs:288 | Path: `id` | | ||
| 44 | | 15 | POST | `/instances/{id}/world/import` | `import_world` | src/routes/instances.rs:328 | Path: `id`; Multipart: ZIP file | | ||
| 45 | | 16 | POST | `/instances/{id}/world/regenerate` | `regenerate_world` | src/routes/instances.rs:360 | Path: `id`; Form: `seed` | | ||
| 46 | |||
| 47 | ### Player Management | ||
| 48 | |||
| 49 | | # | Method | Path | Handler | Source file | Key inputs | | ||
| 50 | |---|--------|------|---------|-------------|------------| | ||
| 51 | | 17 | GET | `/players` | `manage_players` | src/routes/players.rs:28 | None | | ||
| 52 | | 18 | POST | `/players/whitelist/add` | `add_whitelist` | src/routes/players.rs:89 | `username` | | ||
| 53 | | 19 | POST | `/players/whitelist/remove` | `remove_whitelist` | src/routes/players.rs:89 | `username` | | ||
| 54 | | 20 | POST | `/players/op/add` | `add_op` | src/routes/players.rs:89 | `username` | | ||
| 55 | | 21 | POST | `/players/op/remove` | `remove_op` | src/routes/players.rs:89 | `username` | | ||
| 56 | | 22 | POST | `/players/ban/add` | `add_ban` | src/routes/players.rs:89 | `username` | | ||
| 57 | | 23 | POST | `/players/ban/remove` | `remove_ban` | src/routes/players.rs:89 | `username` | | ||
| 58 | |||
| 59 | ### Backup Management | ||
| 60 | |||
| 61 | | # | Method | Path | Handler | Source file | Key inputs | | ||
| 62 | |---|--------|------|---------|-------------|------------| | ||
| 63 | | 24 | GET | `/instances/{id}/backups` | `list_backups` | src/routes/backups.rs:30 | Path: `id` | | ||
| 64 | | 25 | POST | `/instances/{id}/backups/create` | `create_backup` | src/routes/backups.rs:54 | Path: `id` | | ||
| 65 | | 26 | POST | `/backups/{id}/restore` | `restore_backup` | src/routes/backups.rs:88 | Path: `id` (STUB — no-op) | | ||
| 66 | | 27 | GET | `/backups/{id}/download` | `download_backup` | src/routes/backups.rs:93 | Path: `id` | | ||
| 67 | |||
| 68 | ### Mod Management | ||
| 69 | |||
| 70 | | # | Method | Path | Handler | Source file | Key inputs | | ||
| 71 | |---|--------|------|---------|-------------|------------| | ||
| 72 | | 28 | GET | `/instances/{id}/mods` | `list_mods` | src/routes/mods.rs:42 | Path: `id` | | ||
| 73 | | 29 | POST | `/instances/{id}/mods` | `upload_mod` | src/routes/mods.rs:86 | Path: `id`; Multipart: `.jar`/`.zip` file + filename | | ||
| 74 | | 30 | POST | `/instances/{id}/mods/{mod_id}/delete` | `delete_mod` | src/routes/mods.rs:188 | Path: `instance_id`, `mod_id` | | ||
| 75 | | 31 | POST | `/instances/{id}/mods/rescan` | `rescan_mods` | src/routes/mods.rs:163 | Path: `id` | | ||
| 76 | |||
| 77 | ### Account Management (Superuser only — guarded in handler) | ||
| 78 | |||
| 79 | | # | Method | Path | Handler | Source file | Key inputs | | ||
| 80 | |---|--------|------|---------|-------------|------------| | ||
| 81 | | 32 | GET | `/accounts` | `manage_accounts` | src/routes/accounts.rs:60 | None | | ||
| 82 | | 33 | POST | `/accounts/create` | `create_account` | src/routes/accounts.rs:95 | `username`, `password`, `is_superuser` | | ||
| 83 | | 34 | POST | `/accounts/delete` | `delete_account` | src/routes/accounts.rs:111 | `id` | | ||
| 84 | | 35 | POST | `/accounts/set-password` | `set_password` | src/routes/accounts.rs:142 | `id`, `new_password` | | ||
| 85 | | 36 | POST | `/accounts/change-own-password` | `change_own_password_from_accounts` | src/routes/accounts.rs:163 | `current_password`, `new_password` | | ||
| 86 | |||
| 87 | ### Password Change (accessible before password_change_required satisfied) | ||
| 88 | |||
| 89 | | # | Method | Path | Handler | Source file | Key inputs | | ||
| 90 | |---|--------|------|---------|-------------|------------| | ||
| 91 | | 37 | GET | `/change-password` | `change_password_page` | src/routes/mod.rs:58 | Session cookie → CurrentUser | | ||
| 92 | | 38 | POST | `/change-password` | `accounts::change_password` | src/routes/mod.rs:65 | Session cookie; Form: `current_password`, `new_password` | | ||
| 93 | |||
| 94 | --- | ||
| 95 | |||
| 96 | ## Attacker-Controlled Sources Summary | ||
| 97 | |||
| 98 | | Source category | Examples | Highest risk sink | | ||
| 99 | |-----------------|----------|-------------------| | ||
| 100 | | Form fields (strings) | `username`, `name`, `seed`, `motd` | SQLite INSERT/UPDATE (parametrized), RCON command (unsanitized) | | ||
| 101 | | Multipart file bytes | `.jar`/`.zip` uploads | Filesystem write via `std::fs::write` | | ||
| 102 | | Multipart filename | `field.file_name()` | Filesystem path construction | | ||
| 103 | | Path parameters | `id`, `instance_id`, `mod_id` | SQL queries (parametrized), filesystem paths (backup download) | | ||
| 104 | | Cookie header | `mcui_session` | Session lookup (parametrized) | | ||
| 105 | |||
| 106 | --- | ||
| 107 | |||
| 108 | ## High-Value Sinks | ||
| 109 | |||
| 110 | | Sink category | Locations | Risk | | ||
| 111 | |--------------|-----------|------| | ||
| 112 | | **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 | | ||
| 113 | | **Filesystem read** | `src/routes/backups.rs:106` (`std::fs::read`); `src/routes/instances.rs:293` (`std::fs::read`) | MEDIUM — arbitrary file read | | ||
| 114 | | **Docker control** | `src/docker.rs:62` (`start_container`); `src/docker.rs:106` (`stop_container`) | **HIGH** — container lifecycle | | ||
| 115 | | **RCON command** | `src/routes/players.rs:89` (`client.send_command_async(cmd)`); `src/routes/instances.rs:153` (`send_command_async`) | **HIGH** — Minecraft server control | | ||
| 116 | | **SQL execution** | All `execute()` and `query_row()` calls (parametrized) | LOW — parametrized | | ||
| 117 | | **TLS outbound** | `src/routes/instances.rs:280` (`reqwest::Client` → Mojang API) | LOW — trusted URL | | ||
| 118 | |||
| 119 | --- | ||
| 120 | |||
| 121 | ## Key Source Files | ||
| 122 | |||
| 123 | | File | Role | Security relevance | | ||
| 124 | |------|------|-------------------| | ||
| 125 | | `src/auth.rs` | Auth middleware, login/logout, session management | **CRITICAL** — auth boundary | | ||
| 126 | | `src/routes/mod.rs` | Router builder, route composition | **CRITICAL** — which routes are protected | | ||
| 127 | | `src/routes/mods.rs` | Mod upload, ZIP extraction, validation | **HIGH** — file write, parsing | | ||
| 128 | | `src/routes/instances.rs` | Instance CRUD, world import/export, container | **HIGH** — ZIP extraction, file ops | | ||
| 129 | | `src/routes/backups.rs` | Backup create/download/restore | **MEDIUM** — file read/write | | ||
| 130 | | `src/routes/players.rs` | RCON whitelist/op/ban commands | **MEDIUM** — RCON injection | | ||
| 131 | | `src/routes/accounts.rs` | Account CRUD, password change | **HIGH** — privilege management | | ||
| 132 | | `src/mods.rs` | JAR metadata + class-file validation | **HIGH** — parsing attacker-supplied JARs | | ||
| 133 | | `src/rcon.rs` | RCON protocol client | **MEDIUM** — Minecraft server control | | ||
| 134 | | `src/docker.rs` | Docker container lifecycle | **HIGH** — host-level operations | | ||
| 135 | | `src/db.rs` | Database schema + connection | **MEDIUM** — schema design | | ||
| 136 | | `src/config.rs` | Configuration loading | **MEDIUM** — initial creds | | ||
| 137 | | `src/properties.rs` | .properties file reader | **LOW** | | ||
