# GuruConnect Audit Report — 2026-05-30 **Auditor:** Claude (claude-opus-4-8[1m]) **Passes:** Security & Remote-Session Integrity (`--pass=security` only) **Previous audit:** 2026-05-29 (the audit that defined the v2 secure-session-core work) **Scope note:** Single-pass security re-audit to formally verify the v2 secure-session-core rebuild closed the three relay CRITICALs. Independent re-derivation against the code — not an echo of the 2026-05-30 Tasks 3–5 code review. --- ## Executive Summary | Pass | Total | Critical | High | Medium | Low | Info | |------|-------|----------|------|--------|-----|------| | Security & Session | 5 | 0 | 1 | 0 | 1 | 3 | **The three 2026-05-29 audit CRITICALs are CLOSED with no bypass.** The relay/server plane is clean. The one serious finding is **net-new and outside the relay plane** — the Windows agent auto-update path disables TLS verification (MITM → RCE). **Requires action:** [HIGH] Agent auto-update disables TLS cert verification → MITM-to-RCE. --- ## The three audit CRITICALs — verification ### CRITICAL #1 — "any JWT joins any session" → **CLOSED (no bypass)** - `viewer_ws_handler` (`server/src/relay/mod.rs:463`) calls `validate_viewer_token`, which enforces signature + expiry + `purpose=="viewer"` (`server/src/auth/jwt.rs:268,284`). A login JWT lacks the `ViewerClaims` shape and is rejected (test `test_login_token_rejected_as_viewer_token`). - Session binding: `claim_session_id != requested_session_id → 403` (`relay/mod.rs:494-501`). - Mint authz: `mint_viewer_token` (`server/src/api/sessions.rs:114-131`) — `is_admin() || has_permission("control")` → Control; else `has_permission("view")` → ViewOnly; else 403. - View-only enforcement: access mode is in the **signed** claim; relay drops `MouseEvent`/`KeyEvent`/`SpecialKey` when `!access.can_control()` (`relay/mod.rs:1313-1335`). ### CRITICAL #2 — "viewer-WS blacklist bypass" → **CLOSED (no bypass)**, documented residual - `token_blacklist.is_revoked(&token)` consulted on the viewer WS path before upgrade (`relay/mod.rs:476`). - **Residual [TRACKED — v2-secure-session-core plan.md, MEDIUM]:** logout (`server/src/api/auth_logout.rs:64`) revokes only the login JWT; minted viewer tokens are not blacklisted, so a leaked viewer token stays valid until natural expiry (5-min TTL, `jwt.rs:56`). Known follow-up; the enforcement plumbing is correct for any future force-revoke. ### CRITICAL #3 — "JWT accepted as agent key" → **CLOSED (no bypass), fails closed** - `validate_agent_api_key` (`relay/mod.rs:384`) has no JWT branch — only a per-agent `cak_` key (`verify_agent_key`, `server/src/auth/agent_keys.rs:71`; SHA-256 vs `connect_agent_keys`, `revoked_at IS NULL`) or the deprecated shared `AGENT_API_KEY` (WARNING-logged). - Identity binding fails closed: unresolved machine → 503 (`relay/mod.rs:276-286`); a client-supplied `agent_id` is overridden by the key's machine identity (`relay/mod.rs:263-270`). --- ## Pass 5: Security & Remote-Session Integrity ### [HIGH] Agent auto-update disables TLS certificate verification → MITM-to-RCE **File:** `agent/src/update.rs:45` and `:111` (live path: `agent/src/session/mod.rs:461-468,695-702`) **Detail:** Both the `check_for_update` and `download_update` reqwest clients are built with `.danger_accept_invalid_certs(true)` ("for self-signed certs in dev"). This is the **live** auto-update path — the agent session loop calls `check_for_update → perform_update → download_update → install_update → restart_with_new_version` at runtime. A network MITM (the relay endpoint is a public hostname) can serve an arbitrary `guruconnect-update.exe`; the agent writes it over its own binary and executes it — full code execution on every managed endpoint. `verify_checksum` (`update.rs:294`) does NOT mitigate: the expected SHA-256 (`version_info.checksum_sha256`) arrives in the **same** `/api/version` JSON over the unverified TLS connection, so the attacker controls both binary and checksum. **Recommendation:** Remove `danger_accept_invalid_certs(true)` from both clients (gate behind a dev-only `cfg`/env flag never set in release). Defense-in-depth: sign the update binary/manifest with an embedded public key and verify the signature before `install_update`, so update trust does not rest solely on TLS. ### [LOW] Chat message content logged at INFO **File:** `server/src/relay/mod.rs:776` and `:1373` **Detail:** Full chat text is logged (`Chat from client/technician: {content}`). Support-session chat can carry sensitive data (spoken passwords, account numbers) — PII/secret-bearing content in server logs. **Recommendation:** Drop content from the log line (log length or hash, or move to `trace!`). ### [INFO] Bootstrap admin password logged on credentials-file write failure **File:** `server/src/main.rs:200-201` **Detail:** Self-documented one-time fallback at first boot if `.admin-credentials` write fails; password is meant to be changed immediately. Awareness only, not a defect. ### [INFO] Viewer-token mint not scoped to a specific machine/session ACL — **[TRACKED — SPEC-002 Phase 4]** **File:** `server/src/api/sessions.rs:27,114` **Detail:** Any `control`-permission user can mint a control token for any live session (no per-machine / per-client ACL). Intentional for Phase 1; per-tenant narrowing deferred to Phase 4. ### [INFO] Verified-sound areas Single-use support codes (atomic `consume_for_bind`, `support_codes.rs:240` / `db/support_codes.rs:110`); consent gate (viewer refused until `allows_viewer()`, `session/mod.rs:393`; deny/timeout tears down before the relay loop, no pre-consent window); frame caps (agent 4 MiB / viewer 64 KiB, `relay/mod.rs:330,524`); input throttle (200 ev/s token bucket + non-blocking `try_send`); rate limiting wired + proxy-aware (`main.rs:317-370`, `utils/ip_extract.rs:147`); `JWT_SECRET` length-checked ≥32 at startup; Argon2id passwords; no secret/token/code logging; parameterized runtime sqlx (no `format!`-built SQL); `wss://` default. --- ## Verdict | CRITICAL | Verdict | Enforced at | |----------|---------|-------------| | #1 any-JWT-joins-any-session | **CLOSED** | `api/sessions.rs:114` (authz) + `relay/mod.rs:463,494` (token type + session bind) | | #2 viewer-WS blacklist bypass | **CLOSED** (TTL-bounded residual tracked) | `relay/mod.rs:476` | | #3 JWT-accepted-as-agent-key | **CLOSED** | `relay/mod.rs:384` (no JWT branch; fail-closed binding `:276`) | **Net-new findings:** CRITICAL 0 · HIGH 1 · MEDIUM 0 · LOW 1 · INFO 2. **Overall posture:** The relay/server plane is clean — all three CRITICALs genuinely closed, fail-closed session/consent/code paths, bounded abuse surface, correct proxy-aware rate limiting, no SQL injection or credential logging. The one serious net-new issue is on the Windows **agent** (auto-update TLS bypass → MITM-RCE), independent of the v2 secure-session-core work and in no existing spec — fix before the next agent release. --- ## Recommended Action Order 1. **[HIGH]** Remove `danger_accept_invalid_certs(true)` from `agent/src/update.rs` (+ sign update binary). 2. **[LOW]** Stop logging chat content (`relay/mod.rs:776,1373`). 3. **[TRACKED/MEDIUM]** Viewer-token logout revocation (existing plan.md follow-up). *Note: only `--pass=security` was run. The API-surface, Rust-quality, TypeScript, protocol-integrity, docs-reconciliation, and CI/CD passes were not executed this run.*