summaryrefslogtreecommitdiffstats
path: root/piolium/attack-surface/advisory-summary.md
diff options
context:
space:
mode:
Diffstat (limited to 'piolium/attack-surface/advisory-summary.md')
-rw-r--r--piolium/attack-surface/advisory-summary.md395
1 files changed, 395 insertions, 0 deletions
diff --git a/piolium/attack-surface/advisory-summary.md b/piolium/attack-surface/advisory-summary.md
new file mode 100644
index 0000000..eaea57a
--- /dev/null
+++ b/piolium/attack-surface/advisory-summary.md
@@ -0,0 +1,395 @@
1# Advisory Summary: minecraft-ui
2
3**Generated**: 2025-07-29T01:15:00Z
4**Target**: `salmon/minecraft-ui` (self-hosted git at `git.2137697.xyz/salmon/minecraft-ui.git`)
5**Analysis scope**: Stage 01 — Intelligence & Dependency Risk
6
7---
8
9## Repository Identity
10
11| Field | Value |
12|---|---|
13| OWNER | `salmon` |
14| REPO | `minecraft-ui` |
15| OWNER_REPO | `salmon/minecraft-ui` |
16| Resolution source | `git remote` (`https://git.2137697.xyz/salmon/minecraft-ui.git`) |
17| Git history available | Yes (6 commits) |
18| GitHub API available | No — self-hosted git server, not github.com |
19| Primary ecosystem | Rust (Cargo) — edition 2024 |
20| Total lines of code | ~3,213 (Rust source), 6 templates (Askama), 1 CSS, 1 JS (htmx) |
21
22## Coverage Gaps
23
24- **Source 1 (git log CVE search)**: No CVE/GHSA references found in repo (0 results) — new project, no changelog, no SECURITY.md
25- **Source 2 (GitHub Security Advisories)**: **Skipped** — `gh api` requires github.com; repo lives on `git.2137697.xyz` (self-hosted Gitea/Forgejo)
26- **Source 5 (supplementary websearch)**: Not performed — Sources 1, 3, 4 cover all dependency advisories; no project-specific CVEs expected for a new internal tool
27
28---
29
30## Advisory Inventory
31
32### Direct Dependencies — Vulnerability Status
33
34All 22 direct dependencies are at versions past all known advisories. No unfixed public vulnerabilities in the direct dependency tree.
35
36| # | Package | Version | Known Advisories | Affected Range | Current Status |
37|---|---------|---------|-----------------|---------------|----------------|
38| 1 | `axum` | 0.8.9 | 0 | — | ✅ Clean |
39| 2 | `multer` | 3.1.0 | 0 | — | ✅ Clean |
40| 3 | `tokio` | 1.52.3 | 5 (historic) | All < 1.38.2 (oldest fix) | ✅ Fixed well above |
41| 4 | `rusqlite` | 0.40.1 | 15 (historic) | < 0.25.4 (data races), < 0.26.2 (UAF) | ✅ Fixed well above |
42| 5 | `bollard` | 0.17.1 | 2 | Checked — no active vulns | ✅ Clean |
43| 6 | `argon2` | 0.5.3 | 0 | — | ✅ Clean |
44| 7 | `zip` | 8.6.0 | **1 (CVE-2025-29787)** | 1.3.0 ≤ v < 2.3.0 | ✅ Fixed (v8.6.0 >> fix) |
45| 8 | `reqwest` | 0.13.4 | 0 | — | ✅ Clean |
46| 9 | `cookie` | 0.18.1 | 2 (historic) | < 0.7.6 | ✅ Fixed well above |
47| 10 | `serde` | 1.0.228 | 0 | — | ✅ Clean |
48| 11 | `askama` | 0.16.0 | 0 | — | ✅ Clean |
49| 12 | `tower-http` | 0.7.0 (+ 0.6.11 transitive) | 2 (historic dir traversal) | < 0.1.3 / < 0.2.1 | ✅ Fixed well above |
50| 13 | `axum-extra` | 0.12.6 | 0 | — | ✅ Clean |
51| 14 | `tower` | 0.5.3 | 0 | — | ✅ Clean |
52| 15 | `chrono` | 0.4.45 | 0 | — | ✅ Clean |
53| 16 | `uuid` | 1.23.4 | 0 | — | ✅ Clean |
54| 17 | `tempfile` | 3.27.0 | 3 | All historic/pre-2022 | ✅ Clean |
55| 18 | `rand` | 0.8.7 | 1 (RUSTSEC-2026-0097) | 0.7.0 ≤ v < 0.8.6 | ✅ Fixed (0.8.7) |
56| 19 | `tracing` | 0.1.44 | 1 (RUSTSEC-2023-0078 UAF) | 0.1.38 ≤ v < 0.1.40 | ✅ Fixed (0.1.44) |
57| 20 | `serde_json` | 1.0.150 | 0 | — | ✅ Clean |
58| 21 | `toml` | 1.1.2 | 0 | — | ✅ Clean |
59| 22 | `futures-util` | 0.3.32 | 0 | — | ✅ Clean |
60
61### Notable Transitive Dependencies
62
63| Package | Version | Known Advisories | Status |
64|---------|---------|-----------------|--------|
65| `hyper` | 1.10.1 | 0 | ✅ Clean |
66| `h2` | 0.4.15 | 3 (all fixed in 0.4.4+) | ✅ Clean (0.4.15 > 0.4.4) |
67| `mio` | 1.2.1 | 0 | ✅ Clean |
68| `aes-gcm` | 0.10.3 | 0 | ✅ Clean |
69
70### Detailed Advisory Records
71
72#### 1. CVE-2025-29787 — zip crate path traversal (HIGH)
73
74| Field | Value |
75|---|---|
76| **ID** | `GHSA-94vh-gphv-8pm8` / `CVE-2025-29787` |
77| **Published** | 2025-03-17 |
78| **Severity** | HIGH (CVSS:4.0 8.1) |
79| **Summary** | `zip` incorrectly canonicalizes paths during archive extraction, leading to arbitrary file write via path traversal |
80| **CWEs** | CWE-180 (Incorrect Behavior Order), CWE-22 (Path Traversal), CWE-61 (UNIX Symbolic Link) |
81| **Affected** | zip >= 1.3.0, < 2.3.0 |
82| **Fixed in** | zip 2.3.0 |
83| **Current** | 8.6.0 — **Not affected** |
84| **Relevance** | The project uses `zip::ZipArchive` in `mods.rs` (mod extraction from ZIP), `routes/instances.rs` (world import), and `routes/backups.rs` (backup creation). All are using version 8.6.0, past the fix. |
85
86#### 2. RUSTSEC-2025-0023 — tokio broadcast channel soundness
87
88| Field | Value |
89|---|---|
90| **ID** | `GHSA-rr8g-9fpq-6wmg` |
91| **Published** | 2025-04-07 |
92| **Severity** | Informational (unsoundness) |
93| **Summary** | `tokio::sync::broadcast` channel calls `clone` in parallel but does not require `Sync` |
94| **Affected** | tokio >= 0.2.5 (various ranges) |
95| **Fixed in** | 1.38.2 / 1.43.1 / 1.44.2 |
96| **Current** | 1.52.3 — **Not affected** |
97
98#### 3. RUSTSEC-2024-0332 — h2 CONTINUATION flood (HIGH)
99
100| Field | Value |
101|---|---|
102| **ID** | `GHSA-q6cp-qfwq-4gcv` / `RUSTSEC-2024-0332` |
103| **Published** | 2024-04-03 |
104| **Severity** | HIGH |
105| **Summary** | h2 servers vulnerable to degradation of service with CONTINUATION flood frames |
106| **Affected** | h2 < 0.3.26 / < 0.4.4 |
107| **Fixed in** | 0.4.4 |
108| **Current** | 0.4.15 — **Not affected** |
109
110#### 4. RUSTSEC-2023-0078 — tracing use-after-free
111
112| Field | Value |
113|---|---|
114| **ID** | `GHSA-8f24-6m29-wm2r` |
115| **Published** | 2024-01-17 |
116| **Severity** | Informational |
117| **Summary** | Use-after-free in tracing subscriber when `MakeVisitor` is implemented on a type that implements `Visit` without `Clone` |
118| **Affected** | tracing >= 0.1.38, < 0.1.40 |
119| **Fixed in** | 0.1.40 |
120| **Current** | 0.1.44 — **Not affected** |
121
122---
123
124## Vulnerability Pattern Analysis
125
126### 2a. Component Vulnerability Heatmap
127
128The project is a **brand-new codebase** with zero first-party CVEs. The heatmap below is derived from dependency advisory history and architectural analysis:
129
130| Component | Advisory Count (historic) | Risk Factors | Dominant Bug Types |
131|-----------|-------------------------|--------------|--------------------|
132| **`rusqlite`** | 15 (all historic) | Data races (CVE-2020-35866..73), Use-After-Free (CVE-2021-45713..19) | Race conditions, UAF |
133| **`tokio`** | 5 (all historic) | Race conditions, unsoundness | Concurrency bugs |
134| **`h2`** (transitive) | 3 (all fixed by 0.4.4) | CONTINUATION flood, DoS | Resource exhaustion |
135| **`zip`** | 1 (CVE-2025-29787, fixed in 2.3.0) | Path traversal, symlink | Path traversal |
136| **`cookie`** | 2 (both historic, < 0.7.6) | Max-Age DoS, input validation | Input validation |
137| **`tempfile`** | 3 (all historic) | Race conditions, permissions | TOCTOU |
138| **`rand`** | 1 (RUSTSEC-2026-0097, fixed in 0.8.6) | Unsound with custom logger | Unsoundness |
139
140**🔥 High-heat components requiring Phase 3/5 attention:**
1411. **`mods.rs` (480 lines)** — Custom JAR/ZIP parser, class-file constant pool scanning, ZIP extraction. Highest complexity, most attack surface.
1422. **`routes/instances.rs` (782 lines)** — World import/export (ZIP extraction), instance lifecycle, Docker orchestration. Largest single file.
1433. **`auth.rs` (199 lines)** — Session management, password hashing, cookie handling. Custom session cookie parsing (no library).
1444. **`docker.rs` (209 lines)** — Docker API calls with bollard, container creation with user-controlled env vars.
1455. **`rcon.rs` (127 lines)** — Raw TCP protocol implementation, password handling.
146
147### 2b. Bug Type Recurrence
148
149| Bug Class | CWEs | Historic Count | Examples | Recurring? |
150|-----------|------|---------------|----------|------------|
151| **Race condition / TOCTOU** | CWE-362 | 8+ | rusqlite data races, tokio races, tempfile | ⚠️ **Recurring** |
152| **Path traversal** | CWE-22 | 2 | zip (CVE-2025-29787), tower-http (RUSTSEC-2022-0043) | ⚠️ **Recurring** |
153| **Resource exhaustion / DoS** | CWE-400, CWE-770 | 3 | h2 CONTINUATION flood, cookie Max-Age, h2 general | ⚠️ **Recurring** |
154| **Use-after-free** | CWE-416 | 8 | rusqlite UAF, tracing UAF | ⚠️ **Recurring** |
155| **Improper input validation** | CWE-20 | 2 | cookie, zip | — |
156| **Unsoundness** | — | 2 | tokio broadcast, rand | — |
157
158**Key insight**: Path traversal recurs across the dependency tree (zip and tower-http). The project's custom ZIP extraction code (`routes/instances.rs:import_world`) should be audited for the same pattern.
159
160### 2c. Attack Surface Trends
161
162| Input Vector | Where Exploited | Risk Level |
163|-------------|----------------|------------|
164| **Uploaded ZIP/JAR files** | `routes/mods.rs`, `routes/instances.rs` (world import) | 🔴 **Critical** |
165| **HTTP requests** (form data, multipart) | All routes via axum | 🟡 High |
166| **Docker container env vars** | `docker.rs:recreate_container` — user-controlled (version, MOTD, etc.) | 🟡 High |
167| **RCON network protocol** | `rcon.rs` — custom TCP implementation | 🟡 High |
168| **SQL queries via rusqlite** | All DB operations (uses params! macro — safe) | 🟢 Low |
169| **Cookie header parsing** | `auth.rs:require_auth` — manual cookie split | 🟡 Medium |
170| **Template rendering** | Askama templates (compile-time, no injection) | 🟢 Low |
171| **Config file** | `config.toml` parsed via toml crate | 🟢 Low |
172
173**Highest-risk attack surface**: The mod upload/validation pipeline (`routes/mods.rs` + `mods.rs`) accepts arbitrary JAR/ZIP uploads with a 500MB size limit and performs custom bytecode-level class scanning.
174
175### 2d. Patch Quality Signals
176
177No patch-bypass history exists for this project (new codebase, no patched vulnerabilities). However, several **architectural concerns** warrant attention:
178
1791. **`mods.rs` class-file scanner** — Custom constant pool parser (not using a library). Any bug in this parser could lead to mis-validation (accepting client-side mods that crash the server, or rejecting valid server mods). No fuzzing evidence exists.
180
1812. **`auth.rs` cookie session parsing** — Manual `split(';')` + `split_once('=')` parsing of the Cookie header. Equivalent to CWE-172 (Encoding Error). No library cookie parser used despite `cookie` crate being a dependency.
182
1833. **`docker.rs` env injection** — Instance name, MOTD, version, seed, etc. are directly interpolated into Docker container env vars. No sanitization of values that could contain shell-special characters or newlines (env var injection risk).
184
1854. **`rcon.rs` packet parsing** — Custom binary protocol parser with `i32::from_le_bytes` length prefix. Potential for integer underflow/overflow in length handling.
186
187---
188
189## Architecture Inventory
190
191### Components
192
193| Component | File(s) | Purpose | Security Profile |
194|-----------|---------|---------|-----------------|
195| **HTTP Router** | `routes/mod.rs` | Axum router with auth middleware | Cookie-based session auth on protected routes |
196| **Auth module** | `auth.rs` | Password hashing (Argon2), session creation, login/logout | Custom cookie parsing; sessions in SQLite |
197| **Dashboard** | `routes/dashboard.rs` | Landing page showing active instance | Reads-only |
198| **Instance Manager** | `routes/instances.rs` | CRUD for server instances, world import/export, activation | World ZIP import/extract; Docker lifecycle |
199| **Mod Manager** | `routes/mods.rs`, `mods.rs` | JAR/ZIP upload, validation, class-file scanning | Largest attack surface: custom bytecode parser |
200| **Player Manager** | `routes/players.rs` | RCON-based whitelist/OP/ban management | Sends commands via RCON to Minecraft server |
201| **Backup Manager** | `routes/backups.rs` | Create/list/restore/download backups | ZIP creation, file read for download |
202| **Account Manager** | `routes/accounts.rs` | Account CRUD, password change | Superuser-only; session invalidation on password change |
203| **Docker Manager** | `docker.rs` | Container lifecycle via bollard API | Image pull, container create/start/stop with env vars |
204| **RCON Client** | `rcon.rs` | Raw TCP RCON protocol implementation | Custom binary protocol parser |
205| **Database** | `db.rs` | SQLite via rusqlite with connection pool (Arc<Mutex>) | WAL mode, foreign keys, migrations |
206| **Config** | `config.rs` | TOML config loader | Static config, parsed at startup |
207
208### Transports
209
210| Transport | Direction | Protocol | Used By |
211|-----------|-----------|----------|---------|
212| **HTTP** | User ↔ WebUI | HTTP/1.1 (via axum/hyper) | All routes |
213| **Docker socket** | WebUI ↔ Docker daemon | Unix socket (bollard) | `docker.rs` |
214| **RCON** | WebUI ↔ Minecraft server | TCP, custom binary protocol on port 25575 | `rcon.rs` |
215| **Mojang API** | WebUI → piston-meta.mojang.com | HTTPS (reqwest) | Fetching version manifest |
216| **File system** | WebUI ↔ Disk | Standard I/O | World export/import, mod uploads, backups |
217
218### Trust Boundaries
219
220```
221┌─────────────────────────────────────────────────────────────────┐
222│ Internet / LAN │
223│ ┌────────────┐ HTTP :3000 ┌────────────────────────┐ │
224│ │ Browser │ ──────────────▶ │ WebUI (axum) │ │
225│ │ (HTMX/JS) │ │ minecraft-ui process │ │
226│ └────────────┘ │ - auth middleware │ │
227│ │ - cookie sessions │ │
228│ └──────┬─────────────────┘ │
229│ │ │
230│ ┌───────────────────────────────┼──────────────┐ │
231│ │ Docker daemon socket │ RCON TCP │ │
232│ ▼ ▼ │ │
233│ ┌─────────────────────┐ ┌───────────────────┐ │ │
234│ │ Docker Container │ │ Minecraft Server │ │ │
235│ │ itzg/minecraft-srv │ │ (inside Docker) │ │ │
236│ │ port 25565 │ │ port 25575 │ │ │
237│ │ port 25575 │ └───────────────────┘ │ │
238│ └─────────────────────┘ │ │
239│ │ │
240│ ┌────────────────────────────────────────────────────┐ │ │
241│ │ File System │ │ │
242│ │ - server_directory/instances/{name}/data/ │ │ │
243│ │ - server_directory/backups/ │ │ │
244│ │ - server_directory/mcui.db (SQLite) │ │ │
245│ └────────────────────────────────────────────────────┘ │ │
246└─────────────────────────────────────────────────────────────────┘
247```
248
249### Execution Environments
250
251| Environment | Description |
252|-------------|-------------|
253| **Host OS** | Linux (presumed — Docker socket, Unix paths) |
254| **Rust runtime** | Native binary via `tokio` async runtime |
255| **Docker container** | `itzg/minecraft-server` with Java 8/17/21 depending on version |
256| **SQLite** | Embedded database (WAL mode) |
257
258### Highest-Risk Flows (Phase 3 DFD priority)
259
2601. **Mod Upload → Validation → Disk Write** — External attacker-controlled JAR/ZIP → custom bytecode parser → file written to `mods/` directory
2612. **World Import → ZIP Extraction → Disk Write** — Uploaded ZIP → `zip::ZipArchive::by_index` → `enclosed_name()` → file extraction to `world/`
2623. **Instance Create → Docker Container Start** — User-controlled form fields → env var interpolation → Docker API call
2634. **Backup Download → File Read → HTTP Response** — Backup ID → file read from disk → streamed to user (path traversal risk)
2645. **Login → Cookie → Session** — Password verification → session creation → cookie parsing in auth middleware
265
266---
267
268## Component Inventory
269
270### Runtime
271
272| Component | Version | Evidence | Security-relevant |
273|-----------|---------|----------|-------------------|
274| Rust | edition 2024 / stable | `Cargo.toml` | No |
275
276### Package Dependencies (direct)
277
278| Component | Version | Purpose | Security-relevant |
279|-----------|---------|---------|-------------------|
280| axum | 0.8.9 | HTTP framework | Yes — request parsing |
281| multer | 3.1.0 | Multipart uploads | Yes — file upload handling |
282| tokio | 1.52.3 | Async runtime | Yes — concurrency |
283| rusqlite | 0.40.1 | SQLite database | Yes — SQL injection surface |
284| bollard | 0.17.1 | Docker API client | Yes — container control |
285| argon2 | 0.5.3 | Password hashing | Yes — auth credential handling |
286| zip | 8.6.0 | ZIP archive handling | Yes — file extraction |
287| reqwest | 0.13.4 | HTTP client | No — version manifest fetches |
288| cookie | 0.18.1 | Cookie handling | Yes — session cookies |
289| serde | 1.0.228 | Serialization | No |
290| askama | 0.16.0 | Template engine | No — compile-time templates |
291| tower-http | 0.7.0 | HTTP middleware (static files) | No |
292| axum-extra | 0.12.6 | Cookie private jar | Yes — session cookie crypto |
293| chrono | 0.4.45 | Date/time | No |
294| uuid | 1.23.4 | Session ID generation | No |
295| tempfile | 3.27.0 | Temporary files | No |
296| rand | 0.8.7 | RNG for passwords | Yes — RCON password generation |
297| tracing | 0.1.44 | Logging | No |
298| toml | 1.1.2 | Config parsing | No |
299
300### Binary Dependencies
301
302| Component | Evidence | Purpose | Security-relevant |
303|-----------|----------|---------|-------------------|
304| Docker daemon | `bollard::Docker::connect_with_local_defaults()` | Container management | Yes — OS-level access |
305| Java (JRE 8/17/21) | Docker `itzg/minecraft-server` images | Minecraft runtime | Yes — runs untrusted mod code |
306| `itzg/minecraft-server` | Docker image | Minecraft server packaging | Yes — external image |
307
308### External Services
309
310| Service | Evidence | Purpose | Security-relevant |
311|---------|----------|---------|-------------------|
312| `piston-meta.mojang.com` | `reqwest::get(...)` in `fetch_minecraft_versions()` | Version manifest | No — read-only public API |
313
314### Datastores
315
316| Component | Evidence | Purpose | Security-relevant |
317|-----------|----------|---------|-------------------|
318| SQLite (rusqlite) | `db.rs` | Accounts, sessions, instances, mods | Yes — credential storage |
319| Docker volume mounts | `docker.rs` | Minecraft server data | Yes — file system access |
320| Server directory | `config.toml:server_directory` | All persistent data | Yes — file system root |
321
322### Coverage Gaps
323
324| Category | Status | Note |
325|----------|--------|------|
326| runtime | ✅ Covered | Rust edition 2024 |
327| package | ✅ Covered | All 22 direct deps from Cargo.toml |
328| framework | ✅ Covered | axum, askama |
329| datastore | ✅ Covered | SQLite, Docker volumes |
330| external-service | ✅ Covered | Mojang API |
331| container-os | ⚠️ Partial | Dockerfile not in repo; uses `itzg/minecraft-server` images |
332| build-ci | ℹ️ None detected | No CI config found in repo |
333| binary | ✅ Covered | Docker, Java (via Docker) |
334| vendored | ✅ Covered | `static/htmx.min.js` (vendored JS library) |
335
336---
337
338## Dependency Intelligence
339
340### Security-Relevant Dependencies (derived view)
341
342| Dependency | Version | Risk Factors | Pattern Cross-Reference |
343|-----------|---------|-------------|------------------------|
344| **axum** | 0.8.9 | Input parsing, multipart handling | HTTP request surface (2c) |
345| **multer** | 3.1.0 | Multipart file upload with 500MB limit | File upload attack surface (2c) |
346| **rusqlite** | 0.40.1 | →historic: data races, UAF | Bug class: race conditions (2b) |
347| **bollard** | 0.17.1 | Docker socket access, container control | Docker API attack surface (2c) |
348| **argon2** | 0.5.3 | Password hashing — credential protection | Auth security (2c) |
349| **zip** | 8.6.0 | →historic: path traversal (CVE-2025-29787) | Bug class: path traversal (2b) |
350| **cookie** | 0.18.1 | →historic: DoS via Max-Age | Session handling (2c) |
351| **axum-extra** | 0.12.6 | Cookie private jar (encryption) | Session security |
352| **rand** | 0.8.7 | RCON password generation | Password strength |
353
354### Key Risk Observations
355
3561. **No regular dependency scanning configured** — No `cargo audit` or `cargo deny` in CI. Relying on manual version choice.
3572. **`htmx.min.js` (vendored)** — Static file served via `tower-http::services::ServeDir`. Should verify integrity of vendored copy.
3583. **`itzg/minecraft-server` Docker images** — External base images with Java runtimes. Image pull happens at runtime; no image hash pinning in code.
3594. **RCON password generation** — Uses `rand::thread_rng()` (CSPRNG) with 16-char alphanumeric charset (~93 bits entropy). Adequate but no periodic rotation.
360
361---
362
363## Audit Targeting Recommendations
364
365Based on pattern analysis:
366
3671. **Phase 3 DFD priorities**:
368 - **Mod upload/validation pipeline** (`routes/mods.rs` + `mods.rs`) — highest complexity, custom bytecode parser
369 - **World import/export** (`routes/instances.rs:import_world`, `export_world`) — ZIP extraction to disk
370 - **Auth middleware** (`auth.rs:require_auth`) — custom cookie parsing without library
371
3722. **Phase 5 deep probe targets**:
373 - **Mod validation bypass** — Can a crafted JAR bypass the class-file scanner? (CWE-22, CWE-180)
374 - **Path traversal in world ZIP extraction** — `zip::ZipArchive::by_index().enclosed_name()` usage
375 - **Docker env var injection** — User-controlled values in container creation
376 - **RCON protocol weaknesses** — Integer handling in packet length parsing
377
3783. **Phase 10 chambers (mandatory attack modes)**:
379 - Path traversal (CWE-22)
380 - Injection (command/env/XML via Docker env)
381 - Deserialization (JAR/ZIP parsing)
382 - Race conditions (SQLite, file operations)
383 - Auth bypass (cookie forging)
384
3854. **Patch-bypass-checker candidates**:
386 - No structural-recurrence candidates (new codebase) — but the ZIP extraction code in both `routes/instances.rs` and `routes/backups.rs` should be compared for consistency
387
388---
389
390## References
391
392- OSV.dev queries: 22 direct deps + 5 transitive checked
393- NVD keywordSearch for "minecraft-ui": 0 results
394- Git log: 6 commits, no CVE references
395- Source analysis: 3,213 lines across 16 Rust files