Addresses findings from Devin and Codex reviews on the merged PR #10:
- CRITICAL: /ws/terminal accepted connections from any origin - Starlette's
CORSMiddleware does not protect WebSocket handshakes, so any webpage could
open a socket to the dashboard's terminal and get an interactive shell on
the user's machine. Now validates the Origin header against the same
allowed-origins list used for CORS before accepting.
- PtySession.close() sent SIGKILL to the shell's PID but never reaped it via
os.waitpid(), leaking a zombie process per closed terminal session.
- hermes_available() ran a real subprocess (possibly bridged through WSL) on
every /api/status poll, which the dashboard hits every 15s. Added a 60s
TTL cache.
- create_skill() now also creates learnings.md and the context/ directory,
matching the standard skill template (_template/) instead of only
writing SKILL.md.
- The '+ New Skill' button stayed visible in the Skills Hub detail view
since only its sibling filter input was hidden; both now live under a
shared #skillActions container that's hidden/shown together.
Verified: malicious/missing-origin WebSocket connections are rejected at
the handshake (HTTP 403) before any shell spawns; a valid dashboard origin
still connects and works; closing a session leaves no zombie/orphaned
process; the hermes availability cache avoids repeat subprocess spawns.