Claude Code integration as the first Wraith plugin: terminal I/O, SFTP
file access, CodeMirror handoff, and session context awareness. Proves
the plugin architecture and serves as the reference implementation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keyboard events now check if focus is on an input, textarea, select, or
contenteditable element and let the browser handle them normally. Also
fixes connectRdp type (Awaited<>) and async onMounted in RdpCanvas.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Frontend sends verifyId with host-key-accept so backend can correlate the
verification response. Backend tracks pre-ready connections in pendingClients
map, destroys on error/disconnect, and guards against calling verify() after
the connection has already timed out or errored (_destruct crash fix).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
63 tests across 8 spec files, all passing. Removes 2 stale stub files from
backend/test/ that were incompatible with the current async EncryptionService
and 3-argument AuthService constructor. New suite lives in src/ co-located
with source files per NestJS convention.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
28 tests across 4 spec files. Vitest + happy-dom configured with Nuxt auto-import
shims ($$fetch, navigateTo, defineNuxtRouteMiddleware) so stores and composables
resolve cleanly outside the Nuxt runtime.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Default protocol color strips on all host cards (wraith-blue for SSH, purple for RDP)
- Deterministic tag colors from 8-color palette (teal, amber, violet, rose, emerald, sky, orange, indigo)
- Last-connected recency coloring (green=today, amber=this week, gray=older)
- Section header dots (wraith-400 for Recent, gray for All Hosts)
- Active nav link highlighting (wraith-400)
- Group headers get subtle wraith-500 left border accent
- Tree host dots default to protocol color instead of gray
- Fixed rogue modal using hardcoded #1a1a2e/#e94560 — now uses design system
- Fixed sky-600 save buttons → wraith-600 for brand consistency
- Credential type badges: SSH Key=wraith, Password=amber (was purple/blue)
- Colored tags in right sidebar detail panel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
C-2: JWT moved from localStorage to httpOnly cookie (eliminates XSS token theft)
C-3: WebSocket auth via short-lived single-use tickets (JWT no longer in URLs)
H-1: JWT expiry reduced from 7 days to 4 hours
H-3: TOTP secrets encrypted at rest with vault EncryptionService (auto-migrates plaintext)
H-6: Rate limiting via @nestjs/throttler (60 req/min global, tighten on auth)
H-8: Constant-time login — Argon2id verify runs against dummy hash for non-existent users
H-9: Password hashing upgraded from bcrypt(10) to Argon2id (auto-upgrades on login)
H-10: Credential list API no longer returns encrypted blobs
H-16: Admin pages use Nuxt route middleware instead of client-side guard
Plus: auth bootstrap plugin, cookie-parser middleware, all frontend Authorization headers removed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- H-5: Redact keystroke data from WS message logs — log type/sessionId/bytes only
- H-4: Remove private key content/length/passphrase logging, replace with safe single line
- H-14: Remove username@hostname from password auth log, use hostId only
- M-1: Enforce session ownership in data/resize/disconnect handlers via clientSessions map
- C-5: Real host key verification flow — MITM protection blocks changed keys immediately,
new hosts ask user via host-key-verify WS message with 30s timeout, pending map resolves on
host-key-accept/host-key-reject response
- H-13: Shell PROMPT_COMMAND/precmd injection is now opt-in via options.enableCwdTracking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BREAKING CHANGE (forward-only): New credentials/keys encrypted with v2
(Argon2id-derived AES-256-GCM). Existing v1 records decrypt transparently.
- Argon2id params: 64 MiB memory, 3 iterations, 4 parallelism (OWASP)
- Per-record 16-byte salt stored in ciphertext format
- v2 format: v2:<salt>:<iv>:<authTag>:<ciphertext>
- Backwards compatible: v1 records still decrypt with raw key
- Admin endpoint POST /api/credentials/migrate-v2 upgrades all v1→v2
- Added docs/FUTURE-FEATURES.md with remaining spec gaps
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Monaco can't mount in a popup window — it references document.activeElement
from the main window context, causing cross-window DOM errors.
Replaced with a fullscreen overlay teleported to <body>:
- Same dark theme toolbar with save/close/dirty indicator
- Ctrl+S to save, Esc to close
- Status bar shows language and keyboard shortcuts
- File tree stays visible underneath (overlay dismisses to it)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Multi-session tabs + home navigation:
- Tab bar with Home button persists above sessions
- Clicking Home shows the underlying page (hosts, vault, etc.)
- Clicking a session tab switches back to that session
- Header nav links also trigger home view
- Sessions stay alive in background when viewing home
Monaco editor in popup window:
- Opening a file in SFTP launches a detached popup with Monaco
- Full syntax highlighting, minimap, Ctrl+S save
- File tree stays visible while editing
- Toolbar with save/close buttons and dirty indicator
Drag-and-drop upload:
- Drop files anywhere on the SFTP sidebar to upload
- Visual overlay with dashed border on drag-over
- Supports multiple files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add admin-only "Users" nav link in header
- Create /admin/users page with full CRUD:
create user, edit, delete, reset password, reset TOTP
- Matches existing wraith dark theme
- Client-side admin guard redirects non-admins
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full per-user data isolation across all tables:
- Migration adds userId FK to hosts, host_groups, credentials, ssh_keys,
connection_logs. Backfills existing data to admin@wraith.local.
- All services scope queries by userId from JWT (req.user.sub).
Users can only see/modify their own data. Cross-user access returns 403.
- Two roles: admin (full access + user management) and user (own data only).
- Admin endpoints: list/create/edit/delete users, reset password, reset TOTP.
Protected by AdminGuard. Admins cannot delete themselves or remove own role.
- JWT payload now includes role. Frontend auth store exposes isAdmin getter.
- Seed script fixed: checks for admin@wraith.local specifically (not any user).
Uses upsert, seeds with role=admin. Migration cleans up duplicate users.
- Connection logs now attributed to the connecting user via WS auth.
- Deleting a user CASCADEs to all their hosts, credentials, keys, and logs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Inject shell integration (PROMPT_COMMAND/precmd) on SSH connect that
emits OSC 7 escape sequences reporting the working directory on every
prompt. Supports bash and zsh.
- Frontend captures OSC 7 via xterm.js parser, updates session store CWD.
- SFTP sidebar watches session CWD and navigates when it changes.
- SFTP starts at ~/ (user home) instead of / on initial connect, resolved
via SFTP realpath('.') on the backend.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove CSS width/height !important override that broke Guacamole's
internal rendering pipeline. Replace with display.scale() auto-fitting
using ResizeObserver for responsive container sizing. Scale mouse
coordinates back to remote display space to keep input accurate.
Clean up diagnostic instruction logging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Log first 50 instructions, then every 200th, plus any draw operation
targeting layer 0 (main display). Need to determine if RDPGFX desktop
frames are arriving or if only cursor operations are being received.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Temporary diagnostics to debug blank screen after successful RDP connection.
Logs first 30 instruction opcodes and display dimensions on ready.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ROOT CAUSE: guacd showed "User resolution is 0x0 at 0 DPI" and
immediately killed every RDP connection.
The Guacamole protocol requires five client capability instructions
(size, audio, video, image, timezone) BETWEEN receiving 'args' and
sending 'connect'. Our handshake skipped all five and jumped straight
to CONNECT. guacd never received the display dimensions, defaulted to
0x0, and terminated the connection.
Now sends the complete handshake:
select → (receive args) → size → audio → video → image → timezone → connect
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three bugs fixed:
1. TCP stream fragmentation — guacd→browser data pipe treated each TCP
chunk as a complete instruction. TCP is a stream protocol; instructions
WILL be split across chunks (especially display/image data). Added
instruction buffer that accumulates data and only forwards complete
instructions (terminated by ';').
2. Missing client.onerror — when guacd fails the RDP connection (NLA,
auth, TLS), it sends a Guacamole error instruction. No handler was
registered, so errors were silently swallowed. User saw blank canvas
with no feedback. Now surfaces errors via console and gateway callback.
3. Missing client.onstatechange — no connection state tracking. Added
state transition logging for diagnostics.
Also improved CONNECT handshake logging to surface connection parameters
(host, port, user, domain, security mode) without exposing passwords.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ENTRYPOINT not CMD — guacamole/guacd image sets its own entrypoint,
so command override was being appended as args to guacd binary.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
guacd was dying silently with no error instruction sent back.
Enable -L debug -f for verbose FreeRDP diagnostics and log
first 5 guacd→browser instructions plus connection parameters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Echo VERSION_X_Y_Z args back to guacd in CONNECT handshake
- Set guacd to network_mode: host so it can reach RDP targets on
NetBird/Tailscale overlay networks (100.64.x.x)
- App container uses host.docker.internal to reach guacd on host
- Add diagnostic logging for guacd→browser instruction relay
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>