# Security Audit — Officer.dev Pi-Monorepo **Date:** 2026-02-25 **Scope:** Full-stack audit of server, frontend, and infrastructure code. --- ## Executive Summary The codebase has solid foundations (JWT + argon2 auth, WebAuthn passkeys, token blacklisting, Docker-sandboxed terminals, role-based access control). However, several critical and high-severity vulnerabilities exist — primarily around CORS misconfiguration, weak secret management, unauthenticated admin routes, XSS via `rehype-raw`, and token exposure in URLs. **Totals:** 5 Critical, 13 High, 14 Medium, 9 Low, 5 Info --- ## CRITICAL ### C1. Weak JWT Secret — `"officer"` **Files:** `.env` (line 2), `src/servers/jwt.ts` (lines 2, 25) The live `.env` contains `JWT_SECRET="officer"` — a single dictionary word. The `.env.example` uses `"change-me"`. The server uses a non-null assertion (`JWT_SECRET!`) with no startup validation for length or complexity. **Impact:** Complete authentication bypass. Anyone who knows/guesses the secret can forge JWTs for any user including Super Admin. **Remediation:** - Generate a cryptographically random secret: `openssl rand -base64 32` - Add a startup guard: throw if `JWT_SECRET` is absent or shorter than 32 characters - Update `.env.example` with a placeholder: `JWT_SECRET=` --- ### C2. Global CORS Wildcard on All API Routes **File:** `src/servers/hono.ts` (lines 36–42) ```ts cors({ origin: '*', allowHeaders: ['Content-Type', 'Authorization'] }) ``` Applied to every route. The `Authorization` header allowlist means any website can make cross-origin requests using a victim's token (obtained via XSS or other leaks). **Impact:** Full cross-origin request forgery against all API endpoints for users tricked into visiting a malicious page. **Remediation:** Replace `origin: '*'` with the allowlist from `origin-validation.ts` (`ALLOWED_ORIGINS`). Use a dynamic origin validation function in the CORS middleware. --- ### C3. Server-Settings Routes Entirely Unauthenticated **Files:** `src/servers/hono.ts` (lines 46–48), `src/servers/api/server-settings/server-settings.ts` The `serverSettingsRouter` is mounted on the **public** router (outside `protectedRouter`). All sub-routes — SMTP, TTS, STT, OCR, SearXNG, Pi-Mono, Claude Code, OpenCode, applications, resources — have no auth checks. Any unauthenticated request can read settings (including masked credentials), write new settings, and trigger software installs. **Impact:** Full server configuration takeover by any unauthenticated user. **Remediation:** Move `serverSettingsRouter` inside `protectedRouter`. Add Super Admin role checks on all write operations. Exempt only `GET /api/server-settings/onboarding-complete` as public. --- ### C4. Unauthenticated Dev-Server WebSocket Proxy **Files:** `src/server.tsx` (lines 114–139), `src/servers/api/dev-server/router.ts` (lines 241–268), `src/servers/hono.ts` (line 49) The dev-server proxy (`/api/dev-server-proxy/*`) is mounted on the public router. WebSocket upgrades carry no authentication. The HTTP proxy has no auth gate either. Additionally, the Referer header fallback allows probing any running dev server. **Impact:** Unauthenticated access to dev-servers spawned for any user, which may expose internal APIs, HMR endpoints, or local services. **Remediation:** Require a valid JWT (via query param) before upgrading WebSocket connections. Apply `userMiddleware` to the HTTP proxy router. --- ### C5. AI Chat Messages Rendered with `rehype-raw` — LLM-Driven XSS **File:** `src/workspaces/officerdev/src/apps/Chat/components/MessageBubble.tsx` (lines 132, 181) ```tsx {assistantText} ``` AI-generated assistant responses containing HTML tags (`