summaryrefslogtreecommitdiffstats
path: root/piolium/attack-surface/manual-attack-surface-inventory.md
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-07-29 02:24:41 +0200
committersalmonik <salmonik@tuta.io>2026-07-29 02:24:41 +0200
commitc654a6d17eb58adbb15b9c47ab32bcc3a1666307 (patch)
tree545544bf7d805254415da787fad1cdc711767833 /piolium/attack-surface/manual-attack-surface-inventory.md
parentAdd per-instance mod management with client-side detection (diff)
downloadminecraft-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 'piolium/attack-surface/manual-attack-surface-inventory.md')
-rw-r--r--piolium/attack-surface/manual-attack-surface-inventory.md311
1 files changed, 311 insertions, 0 deletions
diff --git a/piolium/attack-surface/manual-attack-surface-inventory.md b/piolium/attack-surface/manual-attack-surface-inventory.md
new file mode 100644
index 0000000..c1793e4
--- /dev/null
+++ b/piolium/attack-surface/manual-attack-surface-inventory.md
@@ -0,0 +1,311 @@
1# Manual Attack Surface Inventory — Phase 8 Deep Probe
2
3**Target:** salmon/minecraft-ui
4**Generated:** 2025-07-29
5**Phase:** P8 — Manual Attack Surface Probe
6**Scope:** All Rust source files (16 files), templates, config
7
8---
9
10## 1. Public Routes / URLs (No Auth Required)
11
12| Method | Path | Handler | Source File:Line | Input Sources |
13|--------|------|---------|-----------------|---------------|
14| GET | `/login` | `login_page` | src/routes/mod.rs:53 | None (static template) |
15| POST | `/login` | `authenticate` | src/auth.rs:34 | `username`, `password` (Form) |
16| POST | `/logout` | `logout` | src/auth.rs:130 | Cookie header (Cookie: mcui_session=...) |
17| GET | `/static/*` | `ServeDir::new("static")` | src/routes/mod.rs:28 | File path after `/static/` |
18
19## 2. Authenticated Routes (All Behind `require_auth`)
20
21| Method | Path | Handler | File:Line | Key Inputs | Sink |
22|--------|------|---------|-----------|------------|------|
23| GET | `/` | `dashboard` | src/routes/dashboard.rs:19 | CurrentUser extension | SQL SELECT |
24| GET | `/instances` | `list_instances` | src/routes/instances.rs:44 | None | SQL SELECT |
25| GET | `/instances/new` | `create_form` | src/routes/instances.rs:70 | None | Outbound HTTP (Mojang API) |
26| POST | `/instances/new` | `create_instance` | src/routes/instances.rs:87 | `name`, `version`, `seed`, `motd`, etc. | SQL INSERT, `create_dir_all`, RCON password gen |
27| GET | `/instances/{id}/edit` | `edit_form` | src/routes/instances.rs:129 | Path `id` (i64) | SQL SELECT, `read_instance_seed`, RCON `seed` cmd |
28| POST | `/instances/{id}/edit` | `edit_instance` | src/routes/instances.rs:176 | Path `id`, Form: difficulty, gamemode, motd | SQL UPDATE, `stop_container` |
29| POST | `/instances/{id}/delete` | `delete_instance` | src/routes/instances.rs:198 | Path `id` | SQL UPDATE/DELETE, `remove_dir_all` |
30| POST | `/instances/{id}/activate` | `activate_instance` | src/routes/instances.rs:217 | Path `id` | Docker `start_container`, RCON polling |
31| POST | `/instances/deactivate` | `deactivate_instance` | src/routes/instances.rs:278 | None | Docker `stop_container` |
32| GET | `/instances/{id}/world/export` | `export_world` | src/routes/instances.rs:288 | Path `id` | `read_dir`, `ZipWriter`, file read |
33| POST | `/instances/{id}/world/import` | `import_world` | src/routes/instances.rs:328 | Path `id`, Multipart ZIP | `ZipArchive`, `File::create`, `remove_dir_all` |
34| POST | `/instances/{id}/world/regenerate` | `regenerate_world` | src/routes/instances.rs:360 | Path `id`, Form: `seed` | `read_to_string`, `write`, `remove_dir_all` |
35| GET | `/players` | `manage_players` | src/routes/players.rs:28 | None | `read_to_string` (whitelist/ops/bans JSON) |
36| POST | `/players/whitelist/add` | `add_whitelist` | src/routes/players.rs:89 | `username` (Form) | RCON `send_command_async` |
37| POST | `/players/whitelist/remove` | `remove_whitelist` | src/routes/players.rs:89 | `username` | RCON `send_command_async` |
38| POST | `/players/op/add` | `add_op` | src/routes/players.rs:89 | `username` | RCON `send_command_async` |
39| POST | `/players/op/remove` | `remove_op` | src/routes/players.rs:89 | `username` | RCON `send_command_async` |
40| POST | `/players/ban/add` | `add_ban` | src/routes/players.rs:89 | `username` | RCON `send_command_async` |
41| POST | `/players/ban/remove` | `remove_ban` | src/routes/players.rs:89 | `username` | RCON `send_command_async` |
42| GET | `/instances/{id}/mods` | `list_mods` | src/routes/mods.rs:42 | Path `id` | SQL SELECT |
43| POST | `/instances/{id}/mods` | `upload_mod` | src/routes/mods.rs:86 | Path `id`, Multipart .jar/.zip | `std::fs::write`, JAR parsing, SQL INSERT |
44| POST | `/instances/{id}/mods/{mod_id}/delete` | `delete_mod` | src/routes/mods.rs:188 | Path `instance_id`, `mod_id` | `remove_file`, SQL DELETE |
45| POST | `/instances/{id}/mods/rescan` | `rescan_mods` | src/routes/mods.rs:163 | Path `id` | `read_dir`, `std::fs::read`, JAR parsing |
46| GET | `/instances/{id}/backups` | `list_backups` | src/routes/backups.rs:30 | Path `id` | SQL SELECT |
47| POST | `/instances/{id}/backups/create` | `create_backup` | src/routes/backups.rs:54 | Path `id` | `File::create`, `ZipWriter`, SQL INSERT |
48| POST | `/backups/{id}/restore` | `restore_backup` | src/routes/backups.rs:88 | Path `id` | SQL SELECT (STUB — no-op) |
49| GET | `/backups/{id}/download` | `download_backup` | src/routes/backups.rs:93 | Path `id` | SQL SELECT, `std::fs::read` |
50| GET | `/accounts` | `manage_accounts` | src/routes/accounts.rs:60 | CurrentUser (must be superuser) | SQL SELECT |
51| POST | `/accounts/create` | `create_account` | src/routes/accounts.rs:95 | `username`, `password`, `is_superuser` | SQL INSERT |
52| POST | `/accounts/delete` | `delete_account` | src/routes/accounts.rs:111 | `form.id` | SQL DELETE |
53| POST | `/accounts/set-password` | `set_password` | src/routes/accounts.rs:142 | `form.id`, `new_password` | SQL UPDATE, session DELETE |
54| GET | `/change-password` | `change_password_page` | src/routes/mod.rs:58 | CurrentUser | None |
55| POST | `/change-password` | `change_password` | src/routes/accounts.rs:163 | `current_password`, `new_password` | SQL UPDATE, session DELETE |
56
57---
58
59## 3. Attacker-Controlled Input Sources (By Category)
60
61### 3a. Form Fields (application/x-www-form-urlencoded)
62| Field | Handler(s) | Max Size | Validation | Risk |
63|-------|-----------|----------|------------|------|
64| `username` | authenticate, player actions, create_account, set_password | ~64KB | None (SQL param prevents injection) | LOW (SQL safe), **HIGH** (RCON injection in player actions) |
65| `password` | authenticate, change_password, create_account | ~64KB | Argon2id hashed | LOW |
66| `name` | create_instance | ~64KB | **NONE** | **CRITICAL** — used in fs::create_dir_all, stored in DB, used in remove_dir_all |
67| `seed` | create_instance, regenerate_world | ~64KB | **NONE** — written to server.properties | **MEDIUM** — properties injection |
68| `motd` | create_instance, edit_instance | ~64KB | **NONE** — passed to Docker env | **HIGH** — Docker env injection |
69| `version` | create_instance | ~64KB | **NONE** — passed to Docker env | MEDIUM |
70| `difficulty` / `gamemode` / `max_players` | create_instance, edit_instance | i32 | Range limited | MEDIUM |
71| `memory_mb` | create_instance, edit_instance | i32 | 512..max_memory_mb | LOW |
72| `server_type` | create_instance | ~64KB | CHECK constraint in SQL | LOW (DB enforced) |
73| `level_type` | create_instance | ~64KB | Default "DEFAULT" | MEDIUM — Docker env |
74| `pvp` | create_instance, edit_instance | ~8 chars | Checkbox (Some("on") | LOW |
75| `world_source` | create_instance | ~64KB | CHECK constraint in SQL | LOW |
76
77### 3b. Multipart File Uploads
78| Field | Handler | Max Size | Validation | Risk |
79|-------|---------|----------|------------|------|
80| File bytes (.jar) | upload_mod | 500MB per field | JAR metadata validation + class scan | **HIGH** — file write |
81| File bytes (.zip) | upload_mod | 500MB per field | ZIP → extract .jar → JAR validation | **HIGH** — file write, nested ZIP |
82| File bytes (.zip) | import_world | Multipart stream | `enclosed_name()` check | **HIGH** — world file write, zip slip edge cases |
83| Filename (from Content-Disposition) | upload_mod | ~255 chars | `file_name()` strips dir components | **MEDIUM** — special chars preserved |
84| Content-Type / boundary | upload_mod | ~255 chars | **NONE** — directly parsed for multer | **LOW** — multer handles internally |
85
86### 3c. Path Parameters
87| Parameter | Handler(s) | Validation | Risk |
88|-----------|-----------|------------|------|
89| `id` (i64) | All /instances/{id}/*, /backups/{id}/* | Axum Path<i64> (integer only) | LOW — SQL uses params |
90| `instance_id` (i64) | delete_mod | Axum Path<(i64, i64)> | LOW |
91| `mod_id` (i64) | delete_mod | Axum Path<(i64, i64)> | LOW |
92
93### 3d. Cookie / Header Sources
94| Source | Handler | What It Provides | Risk |
95|--------|---------|-----------------|------|
96| `Cookie: mcui_session` | require_auth (all protected routes) | Session ID → Account | **MEDIUM** — no server-side TTL |
97| `Cookie: mcui_session` | logout | Session ID for deletion | **LOW** — missing auth guard |
98| `Content-Type: boundary=` | upload_mod | Multipart parsing boundary | **LOW** |
99
100---
101
102## 4. Sinks (Dangerous Operations)
103
104### 4a. Filesystem Write Sinks
105| Sink | File:Line | Data Source | Traversal Protection |
106|------|-----------|-------------|---------------------|
107| `std::fs::create_dir_all(&instance_dir)` | src/routes/instances.rs:205 | `form.name` (user input) | **NONE** — `Path::join` traverses on `..` |
108| `std::fs::write(&dest, bytes)` | src/routes/mods.rs:98 | `safe_name` (filename from upload) | Partial — `file_name()` strips dir |
109| `std::fs::File::create(&out_path)` | src/routes/instances.rs:210 | `file.enclosed_name()` (ZIP entry path) | Partial — `enclosed_name()` checks |
110| `std::fs::File::create(&backup_path)` | src/routes/backups.rs:72 | `filename` (timestamp-based) | LOW — controlled by app |
111| `std::fs::write(&props_path, ...)` | src/routes/instances.rs:396 | `form.seed` (user input) | **NONE** — content injection |
112| `std::fs::remove_dir_all(&instance_dir)` | src/routes/instances.rs:392-393 | `name` from DB (originally user input) | **NONE** |
113| `std::fs::remove_dir_all(&world_dir)` | src/routes/instances.rs:421 | Instance name from DB | Partial |
114| `std::fs::remove_file(&mod_path)` | src/routes/mods.rs:226 | `filename` from DB | Partial |
115
116### 4b. Filesystem Read Sinks
117| Sink | File:Line | Data Source | Risk |
118|------|-----------|-------------|------|
119| `std::fs::read(&backup_path)` | src/routes/backups.rs:106 | `filename` from DB | **MEDIUM** — filename from DB could be traversed |
120| `std::fs::read_dir(&data_dir)` | src/routes/backups.rs:68 | Instance name from DB | LOW |
121| `std::fs::read_to_string(&props_path)` | src/routes/instances.rs:386 | Instance name from DB | LOW |
122| `std::fs::read_dir(&mods_dir)` | src/routes/mods.rs:175 | Instance name from DB | LOW |
123| `std::fs::read(&path)` | src/routes/mods.rs:183 | File path from read_dir | LOW |
124
125### 4c. RCON Command Sinks
126| Sink | File:Line | Data Source | Command Template | Risk |
127|------|-----------|-------------|-----------------|------|
128| `client.send_command_async(cmd)` | src/routes/players.rs:97 | `form.username` | `format!("whitelist add {}", form.username)` | **HIGH** — command injection |
129| `client.send_command_async(cmd)` | src/routes/players.rs:97 | `form.username` | `format!("op {}", form.username)` | **HIGH** |
130| `client.send_command_async(cmd)` | src/routes/players.rs:97 | `form.username` | `format!("ban {}", form.username)` | **HIGH** |
131| `client.send_command_async("seed".into())` | src/routes/instances.rs:153 | Hardcoded "seed" | `"seed"` | LOW |
132| `client.send_command_async("list".into())` | src/routes/instances.rs:251 | Hardcoded "list" | `"list"` | LOW |
133
134### 4d. Docker Control Sinks
135| Sink | File:Line | Data Source | Risk |
136|------|-----------|-------------|------|
137| `docker.start_container(&instance)` | src/routes/instances.rs:235 | Full Instance struct | **HIGH** — all settings go to Docker env |
138| `docker.stop_container()` | src/routes/instances.rs:195 | None | LOW |
139| `recreate_container()` | src/docker.rs:62 | `instance.name`, `instance.motd`, all settings | **HIGH** — env injection |
140| `env_strings` construction | src/docker.rs:87-113 | `instance.version`, `instance.motd`, `instance.rcon_password`, etc. | **HIGH** — no escaping |
141
142### 4e. SQL Query Sinks
143All SQL queries use `rusqlite::params![]` macro which provides parameterized queries. SQL injection is not feasible.
144
145### 4f. Template Rendering Sinks
146| Sink | File:Line | Data | Risk |
147|------|-----------|------|------|
148| `template.render().unwrap_or_default()` | Various | User-controlled strings (name, motd, etc.) | **LOW** — Askama is compile-time template engine, no XSS injection possible |
149
150---
151
152## 5. Hidden Control Channels
153
154| Channel | File:Line | Processing | What It Controls | Security Concern |
155|---------|-----------|-----------|-----------------|-----------------|
156| `Cookie: mcui_session` value | src/auth.rs:107 | Raw `split(';')` then `split_once('=')` | Identity (session ID) | Naive cookie parsing, no OWS stripping per RFC 6265. Cookie value with `=` or `;` could confuse parsing |
157| `Content-Type: boundary=` | src/routes/mods.rs:141-148 | Custom parsing: `split(';')` → `strip_prefix("boundary=")` → `trim_matches('"')` | Multer multipart boundary | No validation on boundary value; multer validates internally but custom extraction may produce empty boundary |
158| Instance properties → Docker env | src/docker.rs:87-113 | Direct `format!()` calls | Docker container environment | **No escaping** — newlines in MOTD or seed can inject arbitrary env vars |
159| Instance name → filesystem path | src/routes/instances.rs:205 | `server_directory.join("instances").join(&form.name)` | Filesystem paths | **No sanitization** — `..` in name causes directory traversal |
160| RCON password from DB → rendered HTML | src/routes/instances.rs:313 | `row.get(15)` (rcon_password) | Exposed to all authenticated users | Any authenticated user can see the RCON password for any instance |
161| Seed → server.properties | src/routes/instances.rs:386-397 | Direct `format!("level-seed={seed}")` | server.properties content | **No escaping** — can inject arbitrary properties lines |
162| Username → RCON command | src/routes/players.rs:93-97 | `format!("whitelist add {}", form.username)` | Minecraft server state | **No sanitization** — can inject arbitrary RCON commands |
163
164---
165
166## 6. Exploit-Relevant Paths (Verified with Code Evidence)
167
168### X1: Instance Name Path Traversal → Arbitrary Directory Write
169```
170POST /instances/new → CreateInstanceForm.name = "../../../tmp/evil"
171→ src/routes/instances.rs:205: std::fs::create_dir_all(server_dir/instances/../../../tmp/evil/data)
172→ SQL INSERT at line 119 with name stored as-is
173→ Later: GET /instances/{id}/edit → edit_form reads name from DB
174→ Later: POST /instances/{id}/delete → remove_dir_all(server_dir/instances/../../../tmp/evil)
175```
176**Evidence:** `Path::join` on user-controlled `form.name` at line 173. No sanitization on name before `create_dir_all` at line 205.
177
178### X2: RCON Command Injection via Player Username
179```
180POST /players/op/add → PlayerAction.username = "attacker; op injected_player"
181→ src/routes/players.rs:93: format!("op {}", form.username)
182→ src/routes/players.rs:97: client.send_command_async("op attacker; op injected_player")
183```
184**Evidence:** `player_action!` macro at line 89 generates direct format string. No sanitization, no validation of username characters.
185
186### X3: MOTD Docker Environment Injection
187```
188POST /instances/{id}/edit → EditInstanceForm.motd = "hello\nEULA=FALSE\nONLINE_MODE=true"
189→ SQL UPDATE at line 186 (stored in DB)
190→ POST /instances/{id}/activate → reads Instance struct
191→ src/docker.rs:103: format!("MOTD={}", instance.motd) → env_strings.push("MOTD=hello\nEULA=FALSE\nONLINE_MODE=true")
192→ Docker container env includes injected vars
193```
194**Evidence:** `format!("MOTD={}", instance.motd)` at src/docker.rs:103. No escaping of newlines or special characters.
195
196### X4: Level-Seed Injection into server.properties
197```
198POST /instances/{id}/world/regenerate → RegenerateWorldForm.seed = "123\nlevel-type=FLAT\ngamerule=doDaylightCycle=false\n"
199→ src/routes/instances.rs:397: format!("level-seed={seed}")
200→ Written to server.properties via line replacement (line 390-393)
201→ Injected lines become new properties
202```
203**Evidence:** Line 397: `new_lines.push(format!("level-seed={seed}"));` No escaping. Java `.properties` format treats newline-separated lines as separate entries.
204
205### X5: Mod Filename Path Traversal (Partially Mitigated)
206```
207POST /instances/{id}/mods → multipart file with filename "../../../tmp/evil.jar"
208→ src/routes/mods.rs:98: process_jar_upload → Path::new(filename).file_name()
209→ "evil.jar" preserved, "../.." stripped by file_name()
210→ Mitigation: streps directory components BUT unsafe write to mods_dir/evil.jar
211```
212**Evidence:** `safe_name` derived from `file_name()` at line 96, then `mods_dir.join(&safe_name)` at line 98.
213
214### X6: No Server-Side Session TTL
215```
216POST /login → sets cookie Max-Age=86400 (24h)
217src/auth.rs:108-115: require_auth middleware executes:
218 "SELECT ... FROM sessions s JOIN accounts a ON s.account_id = a.id
219 WHERE s.id = ?1"
220 No "AND s.created_at > datetime('now', '-1 day')" filter
221→ Sessions persist indefinitely in DB
222```
223**Evidence:** Session query at src/auth.rs:113 has no time-based filter. Session ID from leaked cookie works forever.
224
225### X7: No CSRF Protection on Any POST Endpoint
226```
227All POST handlers: No token validation, no origin/referer check, no SameSite=Strict
228→ CSRF on: /logout, /instances/new, /instances/{id}/delete, /players/op/add, etc.
229```
230**Evidence:** No CSRF middleware in src/routes/mod.rs. Cookie has SameSite=Lax (not Strict). All POST operations lack CSRF tokens.
231
232### X8: POST /logout Missing Auth Guard
233```
234POST /logout registered on outer Router NOT behind require_auth
235→ src/auth.rs:130-152: Reads Cookie header, extracts session ID, DELETEs from DB
236→ No authentication required for the request
237```
238**Evidence:** src/routes/mod.rs:20 registers logout on outer Router, before `.merge(protected)` at line 33.
239
240### X9: RCON Password Exposed to All Authenticated Users
241```
242GET /instances/{id}/edit → edit_form handler
243→ src/routes/instances.rs:313: rcon_password: row.get(15)?
244→ Rendered in HTML template (EditInstanceTemplate)
245→ Any authenticated user can see RCON password for any instance
246```
247**Evidence:** SELECT query at line 301 includes `rcon_password`. No superuser or ownership check before rendering.
248
249### X10: Weak RCON Password Generation
250```
251src/routes/instances.rs:523-529: generate_rcon_password()
252→ rand::thread_rng() (not OsRng/crypto RNG)
253→ 36^16 space (lowercase alphanumeric)
254```
255**Evidence:** `rand::thread_rng()` at line 524 is not cryptographically secure. 62^16 would be stronger.
256
257### X11: Docker Env Variables Set Without Sanitization (Multiple)
258ALL instance fields injected into Docker container env without sanitization:
259- `VERSION={version}` (src/docker.rs:93)
260- `MAX_PLAYERS={max_players}` (src/docker.rs:95)
261- `DIFFICULTY={difficulty}` (src/docker.rs:96)
262- `RCON_PASSWORD={rcon_password}` (src/docker.rs:109)
263- `MOTD={motd}` (src/docker.rs:103)
264- `LEVEL_TYPE={level_type}` (src/docker.rs:111)
265- `LEVEL_SEED={seed}` (src/docker.rs:116)
266- `GAMEMODE={gamemode}` (src/docker.rs:112)
267
268**Evidence:** src/docker.rs:87-116 — All `format!()` calls with direct `instance.*` values. No escaping.
269
270### X12: Backup Restore is No-Op (Data Loss Risk)
271```
272POST /backups/{id}/restore → restore_backup handler
273→ src/routes/backups.rs:88-96: Looks up backup in DB → redirects
274→ NO actual file restoration performed
275```
276**Evidence:** Handler at line 88 does SELECT then returns Redirect. No file copy/restore operations.
277
278### X13: Mod Validation DoS via Crafted JAR
279```
280POST /instances/{id}/mods → upload crafted JAR with malicious constant pool
281→ src/mods.rs:120: ZipArchive::new(data)
282→ src/mods.rs:185: scan_single_class(data) — walks constant pool
283→ No timeout, no recursion depth limit, no size limits on class files
284→ Crafted constant pool (e.g., giant CONSTANT_Utf8 entries) could cause OOM
285```
286**Evidence:** Loop at line 173 (`while i < cp_count`) has no iteration limit beyond cp_count. No bounds on CONSTANT_Utf8 length.
287
288### X14: Recursive ZIP Extraction Without Depth Limit
289```
290POST /instances/{id}/mods → upload ZIP containing JAR (which is also a ZIP)
291→ src/routes/mods.rs:131: process_zip_upload → extracts JAR bytes
292→ src/routes/mods.rs:149: process_jar_upload → validate_mod
293→ src/mods.rs:120: ZipArchive::new(data) — re-opens JAR as ZIP
294→ No depth counter; possible infinite recursion
295```
296**Evidence:** process_zip_upload calls process_jar_upload which calls validate_mod which opens the JAR as ZIP. No depth tracking.
297
298---
299
300## 7. Trust Boundary Crossings
301
302| Boundary | Crossing Point | Direction | Data | Protection |
303|----------|---------------|-----------|------|------------|
304| HTTP → Auth | require_auth middleware (src/auth.rs:55) | Inbound | Session cookie → Account | Session lookup in DB |
305| Auth → Instance Data | All protected handlers | Inbound | Authenticated request → CRUD | None (flat auth model) |
306| WebUI → Filesystem | create_dir_all, fs::write, File::create | Outbound | User-controlled name/file content | Partial (mod filename) |
307| WebUI → Docker | docker::start_container | Outbound | Instance settings | **NONE** |
308| WebUI → RCON | RconClient::send_command_async | Outbound | Player username, commands | **NONE** |
309| WebUI → Database | rusqlite execute/query | Outbound | Form data | Parameterized queries |
310| WebUI → HTTP (Mojang) | reqwest::Client::get | Outbound | None (hardcoded URL) | Trusted URL |
311| Upload → Validation | validate_mod (src/mods.rs:99) | Internal | Raw JAR bytes | Metadata + class scan |