Ruview/docs/qe-reports/02-security-review.md
rUv 81cc241b9e
chore(repo): move v1/ → archive/v1/ + add archive/README.md (#430)
The Rust port at v2/ has been the primary codebase since the rename
in #427. The Python implementation at v1/ is no longer the active
target; the only load-bearing path is the deterministic proof bundle
at v1/data/proof/ (per ADR-011 / ADR-028 witness verification).

Move the whole Python tree into archive/v1/ and document the policy
in archive/README.md: no new features, bug fixes only when they affect
a still-load-bearing path (currently just the proof), CI continues to
verify the proof on every push and PR.

Path references updated in 26 files via path-pattern sed (only
matches v1/<known-child> patterns, never bare v1 or API URLs like
/api/v1/). Two double-prefix typos (archive/archive/v1/) caught and
hand-fixed in verify-pipeline.yml and ADR-011.

Validated:
- Python proof verify.py imports cleanly at archive/v1/data/proof/
  (numpy/scipy still required; CI installs requirements-lock.txt
  from archive/v1/ now)
- cargo test --workspace --no-default-features → 1,539 passed,
  0 failed, 8 ignored (unaffected by Python tree relocation)
- ESP32-S3 on COM7 untouched (no firmware paths changed)

After-merge: contributors should re-run any local `python v1/...`
commands as `python archive/v1/...` (CLAUDE.md and CHANGELOG already
updated).
2026-04-25 23:07:52 -04:00

30 KiB

Security Review Report -- wifi-densepose

Date: 2026-04-05 Reviewer: QE Security Reviewer (V3) Scope: Full codebase -- Python API, Rust crates, ESP32 C firmware Severity Weights: CRITICAL=3, HIGH=2, MEDIUM=1, LOW=0.5, INFORMATIONAL=0.25 Weighted Finding Score: 19.25 (minimum required: 3.0)


Executive Summary

This security review examined all security-sensitive code across the wifi-densepose project: the Python FastAPI backend (authentication, rate limiting, CORS, WebSocket, API endpoints), Rust workspace crates (API, DB, config, WASM), and ESP32-S3 C firmware (NVS credentials, OTA update, WASM upload, swarm bridge, UDP streaming).

Recommendation: CONDITIONAL PASS -- No critical data-exfiltration or remote code execution vulnerabilities were found in the production code paths. However, 3 HIGH severity findings and several MEDIUM issues require remediation before any production deployment. The codebase demonstrates solid security awareness in many areas (constant-time OTA PSK comparison, Ed25519 WASM signature verification, parameterized queries via SQLAlchemy/sqlx, bcrypt password hashing), but gaps remain in WebSocket security, rate limiting bypass vectors, and firmware transport encryption.


Vulnerability Summary

Severity Count Categories
CRITICAL 0 --
HIGH 3 Auth bypass, information disclosure, IP spoofing
MEDIUM 7 CORS, token lifecycle, transport security, memory growth
LOW 5 Deprecated APIs, logging, configuration hardening
INFORMATIONAL 3 Best practice improvements

Detailed Findings

HIGH-001: WebSocket Authentication Token Passed in URL Query String (CWE-598)

Severity: HIGH OWASP: A07:2021 -- Identification and Authentication Failures Files:

  • archive/v1/src/api/routers/stream.py:74 (WebSocket token query parameter)
  • archive/v1/src/middleware/auth.py:243 (fallback to request.query_params.get("token"))
  • archive/v1/src/api/middleware/auth.py:173 (request.query_params.get("token"))

Description: JWT tokens are accepted via URL query parameters for WebSocket connections. URL parameters are logged in web server access logs, browser history, proxy logs, and HTTP Referer headers. This creates multiple credential leakage vectors.

# archive/v1/src/api/routers/stream.py:74
token: Optional[str] = Query(None, description="Authentication token")
# archive/v1/src/middleware/auth.py:243
if request.url.path.startswith("/ws"):
    token = request.query_params.get("token")

Impact: JWT tokens may be captured from server logs, proxy caches, or browser history, enabling session hijacking.

Remediation:

  1. Use the WebSocket Sec-WebSocket-Protocol header to pass tokens during the upgrade handshake.
  2. Alternatively, require clients to send the token as the first WebSocket message after connection, then authenticate before processing further messages.
  3. If query parameter tokens must be supported during a transition, ensure all web server and reverse proxy log configurations redact the token parameter.

HIGH-002: Rate Limiter Trusts X-Forwarded-For Header Without Validation (CWE-348)

Severity: HIGH OWASP: A05:2021 -- Security Misconfiguration File: archive/v1/src/middleware/rate_limit.py:200-206

Description: The _get_client_ip method trusts the X-Forwarded-For header without any validation. An attacker can spoof this header to bypass IP-based rate limiting entirely by rotating forged IP addresses on each request.

# archive/v1/src/middleware/rate_limit.py:200-206
def _get_client_ip(self, request: Request) -> str:
    forwarded_for = request.headers.get("X-Forwarded-For")
    if forwarded_for:
        return forwarded_for.split(",")[0].strip()

    real_ip = request.headers.get("X-Real-IP")
    if real_ip:
        return real_ip

    return request.client.host if request.client else "unknown"

Impact: Complete rate limiting bypass for unauthenticated requests. An attacker can send unlimited requests by setting arbitrary X-Forwarded-For values.

Remediation:

  1. Only trust X-Forwarded-For when the application is deployed behind a known reverse proxy. Configure a trusted proxy allowlist.
  2. Use the uvicorn/Starlette --proxy-headers flag only when behind a trusted proxy, and strip these headers at the edge.
  3. Consider using a middleware like starlette.middleware.trustedhost.TrustedHostMiddleware and validating the number of proxy hops.

HIGH-003: Error Responses Leak Internal Exception Details in Non-Production (CWE-209)

Severity: HIGH OWASP: A09:2021 -- Security Logging and Monitoring Failures Files:

  • archive/v1/src/api/routers/pose.py:140-141 -- detail=f"Pose estimation failed: {str(e)}"
  • archive/v1/src/api/routers/pose.py:176-177 -- detail=f"Pose analysis failed: {str(e)}"
  • archive/v1/src/api/routers/stream.py:297 -- detail=f"Failed to get stream status: {str(e)}"
  • All exception handlers in archive/v1/src/api/routers/stream.py (lines 326, 351, 404, 442, 463)
  • archive/v1/src/middleware/error_handler.py:101-104 -- traceback in development mode

Description: Multiple API endpoints directly interpolate Python exception messages into HTTP error responses. While the global error handler in error_handler.py correctly suppresses details in production, the per-endpoint HTTPException handlers bypass this and always expose str(e) regardless of environment.

# archive/v1/src/api/routers/pose.py:140-141
raise HTTPException(
    status_code=500,
    detail=f"Pose estimation failed: {str(e)}"
)

Impact: Internal error messages (including database connection strings, file paths, stack traces, and library-specific error codes) are exposed to unauthenticated callers. This aids reconnaissance for targeted attacks.

Remediation:

  1. Replace all endpoint-level detail=f"...{str(e)}" patterns with a generic message: detail="Internal server error".
  2. Log the full exception server-side with logger.exception().
  3. Rely on the centralized ErrorHandler class for all error formatting, which already has production-safe behavior.

MEDIUM-001: CORS Allows Wildcard Origins with Credentials in Development (CWE-942)

Severity: MEDIUM OWASP: A05:2021 -- Security Misconfiguration Files:

  • archive/v1/src/config/settings.py:33-34 -- defaults: cors_origins=["*"], cors_allow_credentials=True
  • archive/v1/src/middleware/cors.py:255-256 -- development config combines allow_origins=["*"] + allow_credentials=True

Description: The default settings allow CORS from all origins (*) with credentials (allow_credentials=True). Per the CORS specification, Access-Control-Allow-Origin: * cannot be used with Access-Control-Allow-Credentials: true. However, the CORSMiddleware implementation echoes the requesting origin header verbatim, effectively granting credentialed access from any origin.

# archive/v1/src/middleware/cors.py:255-256 (development_config)
"allow_origins": ["*"],
"allow_credentials": True,

The validate_cors_config function at line 354 correctly flags this combination but is only advisory -- it does not prevent the configuration from being applied.

Impact: Any website can make authenticated cross-origin requests to the API when running in development mode. If development defaults leak to production, this becomes a credential theft vector via CSRF-like attacks.

Remediation:

  1. Change the default cors_origins to [] (empty list) and require explicit configuration.
  2. Make validate_cors_config enforce the rule by raising an exception rather than returning warnings.
  3. In the CORSMiddleware.__init__, reject the combination of allow_credentials=True with wildcard origins at construction time.

MEDIUM-002: WebSocket Connections Lack Message Size Limits (CWE-400)

Severity: MEDIUM OWASP: A04:2021 -- Insecure Design Files:

  • archive/v1/src/api/routers/stream.py:127-128 -- message = await websocket.receive_text() with no size limit
  • archive/v1/src/api/websocket/connection_manager.py -- no max_size configuration

Description: WebSocket endpoints accept incoming messages of arbitrary size. The receive_text() call at stream.py:127 has no size limit, allowing a client to send extremely large messages that consume server memory.

Additionally, the ConnectionManager does not enforce a maximum number of connections. An attacker could open thousands of WebSocket connections to exhaust server resources.

Impact: Denial of service through memory exhaustion or connection pool exhaustion.

Remediation:

  1. Configure websocket.accept(max_size=...) or use Starlette's WebSocket max_size parameter (default is 16 MB -- reduce to 64 KB or less for control messages).
  2. Add a maximum connection limit in ConnectionManager.connect() and reject new connections when the limit is reached.
  3. Implement per-client message rate limiting in the WebSocket handler.

MEDIUM-003: Token Blacklist Uses Periodic Full Clear Instead of Per-Token Expiry (CWE-613)

Severity: MEDIUM OWASP: A07:2021 -- Identification and Authentication Failures File: archive/v1/src/api/middleware/auth.py:246-252

Description: The TokenBlacklist class clears all blacklisted tokens every hour, regardless of their actual expiry time. This means:

  1. A revoked token could be re-usable after the next hourly clear.
  2. Tokens revoked just before a clear cycle have nearly zero effective blacklist time.
# archive/v1/src/api/middleware/auth.py:246-252
def _cleanup_if_needed(self):
    now = datetime.utcnow()
    if (now - self._last_cleanup).total_seconds() > self._cleanup_interval:
        self._blacklisted_tokens.clear()  # Clears ALL tokens
        self._last_cleanup = now

Furthermore, the TokenBlacklist is not consulted in the AuthMiddleware.dispatch() or AuthenticationMiddleware._authenticate_request() flows -- the token_blacklist global instance exists but is never checked during token validation.

Impact: Token revocation (logout) is not enforceable. A stolen JWT remains valid until its natural expiry.

Remediation:

  1. Store each blacklisted token with its exp claim timestamp. Only remove entries whose exp has passed.
  2. Integrate the blacklist check into _verify_token() / verify_token() so that blacklisted tokens are rejected.
  3. For production, replace the in-memory set with a Redis-backed store for cross-process consistency.

MEDIUM-004: OTA Update Endpoint Has No Authentication by Default (CWE-306)

Severity: MEDIUM OWASP: A07:2021 -- Identification and Authentication Failures File: firmware/esp32-csi-node/main/ota_update.c:44-49

Description: The OTA firmware update endpoint (POST /ota on port 8032) has authentication disabled unless an OTA pre-shared key (PSK) is manually provisioned into NVS. The ota_check_auth function returns true when no PSK is configured, allowing unauthenticated firmware uploads.

// firmware/esp32-csi-node/main/ota_update.c:44-49
static bool ota_check_auth(httpd_req_t *req)
{
    if (s_ota_psk[0] == '\0') {
        /* No PSK provisioned -- auth disabled (permissive for dev). */
        return true;
    }
    ...
}

The firmware logs a warning about this (ESP_LOGW(..., "OTA authentication DISABLED")), but it is the default state for all new devices.

Impact: Any device on the same network can flash arbitrary firmware to the ESP32 without authentication, enabling persistent compromise of the sensing node.

Remediation:

  1. Require PSK provisioning as part of the mandatory device setup flow. Reject OTA uploads if no PSK is provisioned (fail-closed).
  2. Alternatively, require physical button press confirmation for OTA updates when no PSK is set.
  3. Document the PSK provisioning step prominently in the deployment guide.

MEDIUM-005: ESP32 UDP CSI Stream Has No Encryption or Authentication (CWE-319)

Severity: MEDIUM OWASP: A02:2021 -- Cryptographic Failures File: firmware/esp32-csi-node/main/stream_sender.c:66-106

Description: CSI data frames are transmitted via plain UDP (SOCK_DGRAM, IPPROTO_UDP) with no encryption, authentication, or integrity protection. An attacker on the same network segment can:

  1. Eavesdrop on CSI data (potentially revealing occupancy/activity information).
  2. Inject forged CSI frames to manipulate pose estimation.
  3. Replay captured frames.
// firmware/esp32-csi-node/main/stream_sender.c:92-93
int sent = sendto(s_sock, data, len, 0,
                  (struct sockaddr *)&s_dest_addr, sizeof(s_dest_addr));

Impact: CSI data exposure and injection on the local network. The severity is moderated by the fact that CSI data requires specialized knowledge to interpret, but the UDP transport provides zero confidentiality for the sensor data.

Remediation:

  1. Implement DTLS (Datagram TLS) for the UDP stream, using mbedTLS which is already available in ESP-IDF.
  2. At minimum, add HMAC authentication to each frame using a pre-shared key to prevent injection.
  3. Consider adding a sequence number and replay window to detect replayed frames.

MEDIUM-006: Swarm Bridge Seed Token Transmitted in Cleartext HTTP (CWE-319)

Severity: MEDIUM OWASP: A02:2021 -- Cryptographic Failures File: firmware/esp32-csi-node/main/swarm_bridge.c:211-229

Description: The swarm bridge HTTP client configuration does not enforce TLS. The esp_http_client_config_t struct at line 211 specifies only .url and .timeout_ms without setting .transport_type = HTTP_TRANSPORT_OVER_SSL or .cert_pem. If the seed_url uses http:// rather than https://, the Bearer token is transmitted in cleartext.

// firmware/esp32-csi-node/main/swarm_bridge.c:211-216
esp_http_client_config_t http_cfg = {
    .url            = url,
    .method         = HTTP_METHOD_POST,
    .timeout_ms     = SWARM_HTTP_TIMEOUT,
};
// firmware/esp32-csi-node/main/swarm_bridge.c:226-229
if (s_cfg.seed_token[0] != '\0') {
    char auth_hdr[80];
    snprintf(auth_hdr, sizeof(auth_hdr), "Bearer %s", s_cfg.seed_token);
    esp_http_client_set_header(client, "Authorization", auth_hdr);
}

Impact: Bearer token can be sniffed on the local network, enabling unauthorized access to the Cognitum Seed ingest API.

Remediation:

  1. Validate that seed_url starts with https:// in swarm_bridge_init() and reject http:// URLs.
  2. Configure TLS certificate verification in the HTTP client config.
  3. Consider certificate pinning for the Seed server.

MEDIUM-007: In-Memory Rate Limiter Does Not Bound Memory Growth (CWE-400)

Severity: MEDIUM OWASP: A04:2021 -- Insecure Design Files:

  • archive/v1/src/api/middleware/rate_limit.py:28-29 -- self.request_counts = defaultdict(lambda: deque())
  • archive/v1/src/middleware/rate_limit.py:132 -- self._sliding_windows: Dict[str, SlidingWindowCounter] = {}

Description: Both rate limiter implementations store per-client sliding window data in unbounded in-memory dictionaries. An attacker sending requests from many spoofed IPs (see HIGH-002) can create millions of entries, each containing a deque of timestamps. The cleanup tasks run only periodically (every 5 minutes or on-demand) and cannot keep pace with a high-rate attack.

Impact: Memory exhaustion denial of service through rate limiter state amplification.

Remediation:

  1. Cap the total number of tracked clients (e.g., 100,000 entries). Use an LRU eviction policy.
  2. Use a fixed-size data structure (e.g., a counter array with hash bucketing) instead of per-client deques.
  3. For production, use Redis-backed rate limiting with automatic key expiry.

LOW-001: Test Script Contains Hardcoded Placeholder Secret (CWE-798)

Severity: LOW OWASP: A07:2021 -- Identification and Authentication Failures File: v1/test_auth_rate_limit.py:26

Description: A test script in the repository contains a hardcoded JWT secret key placeholder:

SECRET_KEY = "your-secret-key-here"  # This should match your settings

While marked with a comment indicating it should be changed, this file is checked into the repository and could be mistaken for a real configuration.

Impact: Low -- this is a test file, not production configuration. However, if a developer copies this value into production settings, JWT tokens become trivially forgeable.

Remediation:

  1. Replace with an environment variable reference: SECRET_KEY = os.environ.get("SECRET_KEY", "").
  2. Add a validation check that fails if the secret is the placeholder value.

LOW-002: User Information Exposed in Response Headers (CWE-200)

Severity: LOW OWASP: A01:2021 -- Broken Access Control Files:

  • archive/v1/src/middleware/auth.py:298-299 -- response.headers["X-User"] = user_info["username"] and response.headers["X-User-Roles"] = ",".join(user_info["roles"])
  • archive/v1/src/api/middleware/auth.py:111 -- response.headers["X-User-ID"] = request.state.user.get("id", "")

Description: Authenticated user information (username, roles, user ID) is included in HTTP response headers. These headers are visible to any intermediary (CDN, reverse proxy, browser extensions) and in browser developer tools.

Impact: Information disclosure of user identity and authorization roles to intermediaries and client-side code.

Remediation:

  1. Remove X-User, X-User-Roles, and X-User-ID response headers, or restrict them to internal/debug environments only.
  2. If needed for debugging, use a configuration flag to enable these headers.

LOW-003: Deprecated datetime.utcnow() Usage (CWE-1235)

Severity: LOW Files: Throughout the Python codebase (auth.py, rate_limit.py, connection_manager.py, pose_stream.py, error_handler.py, stream.py)

Description: datetime.utcnow() is deprecated in Python 3.12+ in favor of datetime.now(datetime.timezone.utc). While not a security vulnerability per se, timezone-naive datetimes can cause token expiry comparison bugs in environments where the system clock timezone differs from UTC.

Remediation: Replace all instances of datetime.utcnow() with datetime.now(datetime.timezone.utc).


LOW-004: JWT Algorithm Not Restricted to Asymmetric in Production (CWE-327)

Severity: LOW OWASP: A02:2021 -- Cryptographic Failures File: archive/v1/src/config/settings.py:30 -- jwt_algorithm: str = Field(default="HS256")

Description: The default JWT algorithm is HS256 (HMAC-SHA256), a symmetric algorithm. This means the same secret is used for both signing and verification, requiring the secret to be distributed to every service that needs to verify tokens. For multi-service architectures, asymmetric algorithms (RS256, ES256) are preferred.

Additionally, the jwt_algorithm setting is not validated against a safe algorithm allowlist, leaving open the possibility of configuration to none (no signature).

Remediation:

  1. Validate jwt_algorithm against an allowlist of safe algorithms: ["HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "ES256", "ES384", "ES512"].
  2. Explicitly reject the none algorithm.
  3. For production deployments with multiple services, recommend RS256 or ES256.

LOW-005: No Password Complexity Validation (CWE-521)

Severity: LOW OWASP: A07:2021 -- Identification and Authentication Failures File: archive/v1/src/middleware/auth.py:115 -- create_user() method

Description: The create_user() method accepts any password without minimum length, complexity, or entropy requirements. Test credentials in v1/test_auth_rate_limit.py:21-23 demonstrate weak passwords ("admin123", "user123").

Remediation:

  1. Enforce minimum password length (12+ characters).
  2. Check passwords against a common-password blocklist.
  3. Require mixed character classes or calculate entropy.

INFORMATIONAL-001: Rust API, DB, and Config Crates Are Stubs

Files:

  • v2/crates/wifi-densepose-api/src/lib.rs -- //! WiFi-DensePose REST API (stub)
  • v2/crates/wifi-densepose-db/src/lib.rs -- //! WiFi-DensePose database layer (stub)
  • v2/crates/wifi-densepose-config/src/lib.rs -- //! WiFi-DensePose configuration (stub)

Description: The Rust API, database, and configuration crates contain only single-line stub comments. No security review of Rust API endpoints, database queries, or configuration handling was possible because no implementation exists. The wifi-densepose-sensing-server crate contains the actual Rust server implementation.

Note: The sensing server (crates/wifi-densepose-sensing-server/src/main.rs) was checked for SQL injection patterns, CORS issues, and authentication concerns. No SQL injection risks were found (no string-formatted queries). The server appears to use in-memory data structures rather than a database.


INFORMATIONAL-002: Rust unsafe Blocks in WASM Edge Crate

Files: v2/crates/wifi-densepose-wasm-edge/src/*.rs (multiple files)

Description: The wifi-densepose-wasm-edge crate contains approximately 40 unsafe blocks, primarily for:

  1. Writing to static mutable event arrays (static mut EVENTS: [...])
  2. Raw pointer casts for repr(C) struct serialization in rvf.rs

These patterns are common in no_std WASM edge environments where heap allocation is unavailable. The static event arrays use a fixed-size pattern (EVENTS[..n]) that prevents out-of-bounds writes as long as n is bounded correctly. Visual inspection of the bounds checks suggests they are correct, but formal verification or fuzzing of the bounds logic is recommended.

The main workspace crate (wifi-densepose-train) explicitly notes it avoids unsafe blocks.


INFORMATIONAL-003: ESP32 Firmware C Code Uses Safe String Handling

Files: firmware/esp32-csi-node/main/*.c

Description: The firmware codebase consistently uses strncpy with explicit null termination, snprintf (not sprintf), and proper bounds checking throughout. No instances of strcpy, strcat, sprintf, or gets were found. Buffer sizes are defined via #define constants. The rvf_parser.c performs thorough size validation before any pointer arithmetic.

This is a positive finding reflecting good security practices.


Dependency Analysis

Python Dependencies (requirements.txt)

Package Version Spec Risk
python-jose[cryptography]>=3.3.0 MEDIUM -- python-jose has had JWT confusion vulnerabilities. Consider migrating to PyJWT or authlib.
paramiko>=3.0.0 LOW -- SSH library. Ensure latest minor version for CVE patches.
fastapi>=0.95.0 LOW -- Version floor is old. Pin to latest stable for security patches.

Recommendation: Run pip audit or safety check against the locked dependency file (archive/v1/requirements-lock.txt) to identify known CVEs.

Rust Dependencies (Cargo.toml)

Crate Version Notes
sqlx 0.7 OK -- uses parameterized queries by design.
axum 0.7 OK -- current major version.
wasm-bindgen 0.2 OK -- standard WASM interface.

Recommendation: Run cargo audit against Cargo.lock to check for known advisories.


Positive Security Practices Observed

The following areas demonstrate security-conscious design:

  1. OTA PSK constant-time comparison (firmware/esp32-csi-node/main/ota_update.c:66-72): Uses XOR-accumulator pattern to prevent timing attacks on authentication.

  2. WASM signature verification (firmware/esp32-csi-node/main/wasm_upload.c:112-137): Ed25519 signature verification is enabled by default (wasm_verify=1). Unsigned uploads are rejected unless explicitly disabled via Kconfig.

  3. RVF build hash validation (firmware/esp32-csi-node/main/rvf_parser.c:126-137): SHA-256 hash of the WASM payload is verified against the manifest before loading, preventing tampered module execution.

  4. Password hashing with bcrypt (archive/v1/src/middleware/auth.py:21): Proper use of passlib with bcrypt scheme.

  5. Protected user fields (archive/v1/src/middleware/auth.py:139): update_user() prevents modification of username, created_at, and hashed_password.

  6. Production error suppression (archive/v1/src/middleware/error_handler.py:214-218): The centralized error handler correctly suppresses internal details in production mode.

  7. No hardcoded secrets in source (verified via entropy-based search across entire repository): No API keys, passwords, or tokens found in source files (the test script placeholder at test_auth_rate_limit.py:26 is marked as requiring replacement).

  8. .env file excluded via .gitignore (.gitignore:171): Environment files are properly excluded from version control.

  9. C string safety (all firmware/esp32-csi-node/main/*.c): Consistent use of strncpy, snprintf, and null-termination guards. No unsafe C string functions.

  10. NVS input validation (firmware/esp32-csi-node/main/nvs_config.c): Bounds checking on all NVS-loaded values (channel range, dwell time minimums, array index clamping).


Files Examined

Python (archive/v1/src/)

  • archive/v1/src/middleware/auth.py (457 lines) -- JWT auth, user management, middleware
  • archive/v1/src/middleware/rate_limit.py (465 lines) -- Rate limiting with sliding window
  • archive/v1/src/middleware/cors.py (375 lines) -- CORS middleware and validation
  • archive/v1/src/middleware/error_handler.py (505 lines) -- Error handling middleware
  • archive/v1/src/api/middleware/auth.py (303 lines) -- API-layer JWT auth
  • archive/v1/src/api/middleware/rate_limit.py (326 lines) -- API-layer rate limiting
  • archive/v1/src/api/websocket/connection_manager.py (461 lines) -- WebSocket manager
  • archive/v1/src/api/websocket/pose_stream.py (384 lines) -- Pose streaming handler
  • archive/v1/src/api/routers/pose.py (420 lines) -- Pose API endpoints
  • archive/v1/src/api/routers/stream.py (465 lines) -- Streaming API endpoints
  • archive/v1/src/config/settings.py (436 lines) -- Application settings
  • archive/v1/src/sensing/rssi_collector.py (partial) -- Subprocess usage review
  • archive/v1/src/tasks/backup.py (partial) -- Subprocess command construction
  • v1/test_auth_rate_limit.py (partial) -- Test credentials review

Rust (v2/)

  • crates/wifi-densepose-api/src/lib.rs (1 line -- stub)
  • crates/wifi-densepose-db/src/lib.rs (1 line -- stub)
  • crates/wifi-densepose-config/src/lib.rs (1 line -- stub)
  • crates/wifi-densepose-wasm/src/lib.rs (133 lines) -- WASM bindings
  • crates/wifi-densepose-wasm/src/mat.rs (partial) -- MAT dashboard
  • crates/wifi-densepose-wasm-edge/src/*.rs (unsafe block audit)
  • crates/wifi-densepose-sensing-server/src/main.rs (SQL injection pattern search)
  • Cargo.toml (workspace dependencies)

C Firmware (firmware/esp32-csi-node/main/)

  • main.c (302 lines) -- Application entry point
  • nvs_config.c (333 lines) -- NVS configuration loading
  • nvs_config.h (77 lines) -- Configuration struct definitions
  • stream_sender.c (117 lines) -- UDP stream sender
  • ota_update.c (267 lines) -- OTA firmware update
  • wasm_upload.c (433 lines) -- WASM module management
  • rvf_parser.c (169+ lines) -- RVF container parser
  • swarm_bridge.c (328 lines) -- Cognitum Seed bridge

Configuration & Dependencies

  • requirements.txt (47 lines)
  • .gitignore (verified .env exclusion)

Patterns Checked

Check Category Patterns Searched Result
Hardcoded secrets password=, secret_key=, api_key=, high-entropy strings Clean (1 test placeholder found)
SQL injection String-formatted SQL queries (format! + SQL keywords, f-string + SQL) Clean
Command injection subprocess with user input, os.system, eval Safe (fixed command arrays only)
Path traversal User-controlled file paths without sanitization Not applicable (no file serving endpoints)
Insecure deserialization pickle.loads, yaml.unsafe_load, eval on user input Clean
Weak cryptography md5, sha1 for security, DES, RC4 Clean (uses bcrypt, SHA-256, Ed25519)
Unsafe C functions strcpy, strcat, sprintf, gets Clean (uses safe alternatives throughout)
Unsafe Rust blocks unsafe { ... } in workspace crates ~40 in wasm-edge (acceptable for no_std)
.env files committed .env, .env.local, .env.production Clean (properly gitignored)
CORS misconfiguration Wildcard + credentials Found (MEDIUM-001)

Remediation Priority

Priority Finding Effort Impact
1 HIGH-002: Rate limiter IP spoofing Low Eliminates rate limiting bypass
2 HIGH-001: WebSocket token in URL Medium Prevents credential leakage
3 HIGH-003: Error detail exposure Low Prevents information disclosure
4 MEDIUM-003: Token blacklist not enforced Medium Enables logout functionality
5 MEDIUM-004: OTA default no-auth Low Prevents unauthorized firmware flash
6 MEDIUM-002: WebSocket message limits Low Prevents DoS via large messages
7 MEDIUM-001: CORS wildcard + credentials Low Prevents CSRF-like attacks
8 MEDIUM-005: UDP stream no encryption High Adds transport security
9 MEDIUM-006: Swarm bridge cleartext Medium Protects Seed authentication
10 MEDIUM-007: Rate limiter memory growth Medium Prevents state amplification DoS

Security Score

Category Score Max Notes
Authentication 6/10 10 Good JWT implementation; token blacklist non-functional
Authorization 8/10 10 Role-based access control present; missing RBAC on some endpoints
Input Validation 8/10 10 Pydantic models, NVS bounds checks; WebSocket lacks size limits
Cryptography 7/10 10 bcrypt, Ed25519, SHA-256; UDP transport unencrypted
Configuration 6/10 10 Good validation functions; unsafe defaults for development
Error Handling 7/10 10 Centralized handler good; per-endpoint leaks
Transport Security 5/10 10 No TLS enforcement for firmware; no DTLS for UDP
Dependency Security 7/10 10 Reasonable version floors; no pinned versions
Firmware Security 7/10 10 OTA auth optional; WASM verification strong
Logging/Monitoring 7/10 10 Comprehensive logging; token blacklist not wired

Overall Security Score: 68/100


Generated by QE Security Reviewer (V3) -- Domain: security-compliance (ADR-008)