XyloLabs API — Comprehensive Code Review¶
Date: 2026-03-30 Scope: Full codebase (~36,000 LOC Rust + React/TypeScript frontend) Crate Count: 12 workspace crates + frontend SPA + infrastructure Files Reviewed: 140+
Executive Summary¶
The XyloLabs API is a well-architected IoT data-ingestion platform with a Rust backend (axum), multi-MCU embedded SDK, and React admin frontend. The codebase demonstrates strong engineering practices: parameterized SQL throughout, proper Argon2id password hashing, timing-oracle-protected login, cross-language codec parity testing, and comprehensive RBAC.
89 findings were identified across 6 review domains. The distribution:
| Severity | Count | Action |
|---|---|---|
| CRITICAL | 10 | Must fix before production |
| HIGH | 22 | Should fix in next iteration |
| MEDIUM | 33 | Address during regular development |
| LOW | 24 | Optional improvements |
The most urgent issues center on unsafe unwrap() calls in binary protocol parsing, secrets management in configuration files, and race conditions in auth/session handling. No fundamental architectural problems were found — the crate decomposition, trait design, and layering are sound.
Table of Contents¶
- Architecture Overview
- Critical Findings
- High Findings
- Medium Findings
- Low Findings
- Positive Observations
- Recommendations by Priority
1. Architecture Overview¶
Repository layout summary:
- crates/xylolabs-core — models, DTOs, protocol encode/decode, downsampling
- crates/xylolabs-db — PostgreSQL pool + repository layer
- crates/xylolabs-server — Axum server, routes, middleware, auth
- crates/xylolabs-protocol — XAP/XMBP binary codec (no_std)
- crates/xylolabs-sdk — embedded SDK: client, session, transport, codecs
- crates/xylolabs-hal-* — per-platform HAL crates
- crates/xylolabs-storage — S3-compatible storage client
- crates/xylolabs-transcode — FFmpeg pipeline + async worker
- frontend/ — React SPA
- deploy/nginx/ — NGINX reverse proxy configs
- docker-compose*.yml — deployment/test orchestration
- tests/ — integration and performance suites
Key Strengths¶
- Clean workspace separation: core types are dependency-free, protocol/sdk are
no_std - All SQL uses parameterized queries — zero injection vectors found
- Argon2id with timing-oracle protection on login
- Family-based refresh token rotation with automatic revocation on reuse
- Cross-language codec parity tests (Rust ↔ C) ensure protocol compatibility
- Proper
SKIP LOCKEDfor concurrent job claiming - Role-based access control with facility-level tenant isolation
2. Critical Findings¶
C-01. Unchecked unwrap() in XMBP decode can panic on malformed input¶
File: crates/xylolabs-protocol/src/decode.rs:213
Component: Protocol
The try_into().unwrap() on fixed-size array conversion can panic if the byte slice has an unexpected length. While read_bytes validates length, the implicit safety contract is fragile.
// Panics if read_bytes returns wrong size (defensive programming failure)
Ok(SampleValue::F64(f64::from_be_bytes(bytes.try_into().unwrap()))
Fix: Replace with explicit error propagation or expect() with a safety justification:
Ok(SampleValue::F64(f64::from_be_bytes(
bytes.try_into().expect("read_bytes guarantees 8 bytes for F64")
)))
C-02. Integer overflow in JSON content-length decode¶
File: crates/xylolabs-protocol/src/decode.rs:462
Component: Protocol
The val variable is clamped to MAX_CONTENT_LENGTH but subsequent buf[pos + val] can still overflow if pos + val > buf.len(). The overflow handling truncates the value but doesn't fix the buffer access.
Fix: Guard against buffer bounds explicitly:
C-03. SQL injection pattern in test cleanup¶
File: crates/xylolabs-server/tests/common/mod.rs:260
Component: Server (Tests)
Dynamic SQL via format!("DELETE FROM {table}"). Currently safe because table names are hardcoded, but the pattern is dangerous if extended.
for table in tables {
let query = format!("DELETE FROM {table}");
sqlx::query(&query).execute(pool).await.ok();
}
Fix: Use an allowlist of known table names or sqlx::query! macro.
C-04. HeaderValue::from_static() may panic at runtime¶
File: crates/xylolabs-server/src/routes/metadata_query.rs:240
Component: Server
from_static requires &'static str but is called where a dynamic value could be passed in future refactors.
Fix: Use HeaderValue::from_str() for non-static strings.
C-05–C-08. Secrets exposed in .env.example and docker-compose.test.yml¶
Files: .env.example:15-16,25-26,47-48 and docker-compose.test.yml:7-8,20-21
Component: Infrastructure
- Weak example secrets that could be copy-pasted to production
- Default admin credentials are guessable
- Test environment secrets hardcoded in version control
Fix: Replace actual secrets with <generate-strong-secret-here> placeholders. Use Docker secrets or CI/CD environment variables for test credentials.
C-09. Auth token refresh race condition¶
File: frontend/src/api/client.ts:54-56, 90-94
Component: Frontend
The setAccessToken interceptor checks isLoggingOut but the Zustand state snapshot may be stale between the check and the refresh call, creating a window where concurrent refreshes can occur.
Fix: Capture the logout flag atomically before calling tryRefreshToken. Use a mutex or refresh-in-progress flag.
C-10. FormatConfig::content_type() method does not exist¶
File: crates/xylolabs-transcode/src/pipeline.rs:129
Component: Transcode
Code calls format.content_type() but the method doesn't exist on FormatConfig. This will fail to compile.
Fix: Either add a content_type() method to FormatConfig or call it on the inner format field:
3. High Findings¶
H-01. Unsafe from_raw_parts in SDK client¶
File: crates/xylolabs-sdk/src/client.rs:71, 178
Component: SDK
Uses unsafe { core::slice::from_raw_parts(samples.as_ptr() as *const u8, byte_len) } to reinterpret i16 samples as bytes. While i16 has no padding, the byte length calculation can overflow u16 on large buffers.
Fix: Add bounds check: let byte_len = (samples.len() * 2).min(u16::MAX as usize) as u16; or use bytemuck::cast_slice.
H-02. Refresh token family not validated against user¶
File: crates/xylolabs-server/src/routes/auth.rs:194-200
Component: Server
The refresh() endpoint validates user_id and family_id match the stored record, but doesn't verify the family_id belongs to the user. A leaked token could mint tokens for another user's family.
Fix: Store and validate user_id per refresh token family, rejecting cross-family reuse.
H-03. TOCTOU race in WebSocket session validation¶
File: crates/xylolabs-server/src/routes/ingest.rs:464-474
Component: Server
Time gap between session ownership check (DB) and session registration (in-memory manager). A malicious client could register a session between these checks.
Fix: Validate inside the registration transaction atomically.
H-04. Rate limiter race condition¶
File: crates/xylolabs-server/src/router.rs:63-69
Component: Server
The check_and_increment method has a TOCTOU between reading and writing the count. Two concurrent requests can both pass the limit check.
let count = self.attempts.get(&ip).unwrap_or(0);
if count >= AUTH_RATE_LIMIT { return false; }
self.attempts.insert(ip, count + 1);
Fix: Use HashMap::entry().and_modify() for atomic read-modify-write.
H-05. update_password lacks authorization context¶
File: crates/xylolabs-db/src/repo/user.rs:119-126
Component: Database
The function updates any user's password given just a UUID. No facility_id or old-password verification at the repository layer.
Fix: Add facility_id parameter for tenant isolation, or enforce old-password verification at the service layer.
H-06. Unsafe std::env::set_var in async context¶
File: crates/xylolabs-server/src/config.rs:326-341
Component: Server
std::env::set_var is not thread-safe and can cause UB in multi-threaded async contexts. The unsafe blocks are unnecessary (the functions are safe in Rust).
Fix: Remove unsafe wrappers. Use a config struct passed via State instead of mutating global environment.
H-07. Open redirect vulnerability in login page¶
File: frontend/src/pages/LoginPage.tsx:13-17
Component: Frontend
safeRedirect only blocks // prefixes but doesn't validate window.location.origin, allowing redirects to external domains.
Fix: Use new URL(redirect) and verify origin === window.location.origin.
H-08. Significant code duplication between HAL modem drivers¶
Files: crates/xylolabs-hal-rp/src/modem.rs:278-413 and crates/xylolabs-hal-stm32/src/modem.rs:238-351
Component: HAL
~175 lines of identical AT command helpers (format_tcp_open_cmd, parse_csq_rssi, format_u16) duplicated across RP and STM32 crates.
Fix: Extract to a shared xylolabs-hal-common crate or feature-gated module in xylolabs-core.
H-09–H-12. Additional HIGH findings (summary)¶
| ID | File | Component | Issue |
|---|---|---|---|
| H-09 | sdk/src/transport.rs:389 |
SDK | Array indexing without bounds check on HTTP response parsing |
| H-10 | sdk/src/client.rs:763 |
SDK | elapsed_ms can overflow near u64::MAX |
| H-11 | sdk/src/session.rs:494 |
SDK | Session ID validation allows short/test strings — should enforce UUID-like format |
| H-12 | sdk/src/codec/lc3.rs:103 (XAP encoder) |
SDK | sample_rate * frame_duration can overflow u32 — widen to u64 |
4. Medium Findings¶
Security & Auth¶
| ID | File | Issue |
|---|---|---|
| M-01 | server/src/routes/tags.rs:59 |
Tag color validation allows arbitrary-length hex strings; should enforce ^#[0-9a-fA-F]{6}$ |
| M-02 | frontend/src/api/client.ts:85 |
No explicit CSRF header for state-changing operations |
| M-03 | server/src/ingest/manager.rs:526 |
Session flush retry has no backoff or max retry count |
Data Integrity¶
| ID | File | Issue |
|---|---|---|
| M-04 | core/src/protocol.rs:140 |
encode_batch flag/device_id consistency not enforced — device_id: Some(x) with flags: 0 silently skips encoding |
| M-05 | core/src/protocol.rs:273 |
encode_value uses unwrap_or_default() for JSON serialization — silent data corruption if serialization fails |
| M-06 | server/src/routes/metadata_query.rs:401 |
CSV export doesn't fully escape header columns |
Performance¶
| ID | File | Issue |
|---|---|---|
| M-07 | core/src/downsample.rs:86 |
downsample_samples takes ownership but creates a new Vec — confusing; take &[MetadataSample] instead |
| M-08 | server/src/routes/ingest.rs:230 |
Excessive clone operations in ingest hot path |
| M-09 | storage/src/client.rs:94 |
Entire file buffered in memory before upload — rejects files >64MB with warning but still buffers |
| M-10 | transcode/src/worker.rs:94 |
No backoff for repeated DB errors in job claiming loop |
Configuration & Validation¶
| ID | File | Issue |
|---|---|---|
| M-11 | sdk/src/config.rs:40 |
Missing validation for audio_batch_ms, max_retries, retry_max_ms, http_timeout_ms |
| M-12 | hal-rp/src/decimator.rs:160 |
Hardcoded frame sizes (960/320) — panics if hardware config changes |
| M-13 | transcode/src/ffmpeg.rs:129 |
Codec/container allowlists hardcoded — should be configurable |
Frontend¶
| ID | File | Issue |
|---|---|---|
| M-14 | frontend/src/i18n/index.ts:564 |
Corrupted Korean translation string |
| M-15 | frontend/src/App.tsx |
No ErrorBoundary component wrapping Routes |
| M-16 | Multiple pages | Using browser confirm() instead of existing ConfirmDialog component |
| M-17 | Multiple pages | Missing loading/disabled states on delete buttons during mutation |
| M-18 | frontend/src/stores/localeStore.ts:46 |
localStorage preferences have no expiration or migration |
Code Quality¶
| ID | File | Issue |
|---|---|---|
| M-19 | server/src/routes/devices.rs:112 |
Collapsible if statements (Clippy warnings) |
| M-20 | server/src/routes/facilities.rs:89 |
Multiple collapsible if statements (Clippy warnings) |
| M-21 | sdk/src/session.rs:639 |
Stream name silently truncated at 31 bytes — should return error |
| M-22 | sdk/src/ring_buffer.rs:27 |
Overly strict Acquire ordering in available() — Relaxed sufficient for SPSC reads |
| M-23 | hal-rp/src/decimator.rs:19 |
Filter coefficients lack generation script reference |
| M-24 | transcode/src/worker.rs:221 |
expect() on signal handler installation can panic on startup |
5. Low Findings¶
Rust Backend¶
| ID | File | Issue |
|---|---|---|
| L-01 | core/src/protocol.rs:14 |
Magic number 10 for header size — define as const MIN_XMBP_HEADER_SIZE |
| L-02 | core/src/protocol.rs:330 |
Production unwrap() lacks context messages — use expect("why this is safe") |
| L-03 | db/src/repo/*.rs |
SELECT * used throughout (27 occurrences) — fragile to schema changes |
| L-04 | db/ |
Zero unit tests for repository functions — bulk ops, tenant isolation untested |
| L-05 | protocol/src/decode.rs:104 |
Redundant duplicate data.len() < 10 check |
| L-06 | sdk/src/transport.rs:380 |
Inefficient string building via many small copy_from_slice calls |
| L-07 | sdk/src/codec/lc3.rs:126 (XAP encoder) |
Magic value 32768.0f32 for Q15 scaling not documented |
| L-08 | sdk/src/client.rs:195 |
SdkError::InvalidState doesn't distinguish session states |
| L-09 | sdk/src/session.rs:408 |
reconnect_attempts uses wrapping add — should use saturating_add |
| L-10 | hal-rp/modem.rs:372 vs hal-stm32/modem.rs:313 |
Inconsistent naming: parse_u8_from_ascii vs parse_u8_ascii |
Frontend¶
| ID | File | Issue |
|---|---|---|
| L-11 | Multiple pages | Hardcoded fontSize: '16px' duplicated — extract to CSS class |
| L-12 | UploadsPage.tsx:34, TranscodePage.tsx:40 |
Magic number per_page: 20 — define as constant |
| L-13 | api/uploads.ts:119 and api/transcode.ts:92 |
Duplicate mapPage function — extract to shared utility |
| L-14 | pages/SettingsPage.tsx:183 |
animate-[fadeIn_0.15s_ease] assumes Tailwind animation configured |
Infrastructure¶
| ID | File | Issue |
|---|---|---|
| L-15 | deploy/nginx/*.conf:86 |
WebSocket timeout 3600s (1 hour) is excessive — reduce to 600s |
| L-16 | docker-compose.yml:3 |
Using latest tag — pin to specific semantic version |
| L-17 | docker-compose.test.yml:11 |
3s timeout may fail on slow CI runners |
6. Positive Observations¶
Security¶
- Zero SQL injection vectors — all 27+ repository functions use parameterized
$Nbinding - Argon2id password hashing with timing-oracle protection (dummy hash for missing users)
- Family-based refresh token rotation with automatic revocation on reuse
- API key hashing (SHA256) before storage; plaintext shown only once on creation
- Comprehensive security headers: HSTS, X-Frame-Options, X-Content-Type-Options, CSP
- Decompression bomb protection: 64 MiB limit in
decode_chunk_compressed - Allocation DoS protection:
sample_count * 8 > remainingcheck in protocol decode - CORS origin allowlist (rejects wildcard outside dev/test)
Architecture¶
- Clean workspace decomposition with
no_stdprotocol/SDK crates - Platform trait abstraction allows same SDK to run on STM32/ESP/nRF/RP2350
- Feature-flagged codec implementations (ADPCM, XAP)
SKIP LOCKEDfor concurrent worker job claiming- Broadcast channels for SSE live streaming
spawn_blockingfor CPU-intensive compression
Testing¶
- Cross-language parity tests (Rust ↔ C SDK) ensure byte-identical encoding
- Codec stress tests validate correctness under high throughput
- Integration tests cover auth, devices, facilities, health, config, ingest, and metadata query
- Ring buffer SPSC correctness validated with concurrent tests
Data Layer¶
- Sensible connection pool timeouts (
acquire_timeout,idle_timeout,max_lifetime) LIMITcaps on all list operations (500–10,000 by entity)unnest()with array binding for bulk inserts- Partial indexes for efficient querying
- Tenant isolation enforced at repository layer with
facility_idon all delete/revoke operations
7. Recommendations by Priority¶
Immediate (Block Production)¶
- Fix all
unwrap()calls in protocol decode/encode with explicit error handling orexpect()with justification - Remove actual secrets from
.env.example— use placeholders - Fix
FormatConfig::content_type()in transcode pipeline (compile error) - Add origin validation to login redirect
- Fix auth token refresh race — add refresh-in-progress mutex
Next Sprint¶
- Harden rate limiter with atomic
entry().and_modify() - Add
facility_idtoupdate_passwordfor tenant isolation - Remove
std::env::set_varfrom config — use State-passed config struct - Strengthen refresh token family validation against cross-family attacks
- Add
#[must_use]to allbool-returning HAL methods - Gate incomplete nRF/ESP HAL stubs with feature flags or
compile_error!
Ongoing¶
- Extract shared modem AT code from RP and STM32 HALs
- Add repository unit tests (bulk ops, tenant isolation, tag transactions)
- Replace
SELECT *with explicit column lists in hot-path queries - Add ErrorBoundary to React app
- Replace browser
confirm()with existingConfirmDialogcomponent - Make transcode allowlists configurable via environment or database
- Implement storage streaming upload for files >16MB
Review Metrics¶
| Component | Files | CRITICAL | HIGH | MEDIUM | LOW | Total |
|---|---|---|---|---|---|---|
| xylolabs-core | 27 | 1 | 2 | 3 | 2 | 8 |
| xylolabs-db | 23 | 0 | 1 | 1 | 3 | 5 |
| xylolabs-server | 35 | 2 | 5 | 8 | 2 | 17 |
| protocol + SDK | 16 | 2 | 8 | 7 | 6 | 23 |
| HAL + transcode + storage | 28 | 1 | 3 | 6 | 4 | 14 |
| Frontend + infra | 45 | 4 | 3 | 8 | 7 | 22 |
| Total | 174 | 10 | 22 | 33 | 24 | 89 |
This review was generated through automated static analysis and manual code inspection. All findings should be verified against the current codebase state before acting on them.