Skip to content

feat: security hardening, splash screen, mobile auth, and connectivity fixes#21

Merged
my-claude-utils merged 5 commits intomainfrom
feat/splash-auth-ux
Mar 16, 2026
Merged

feat: security hardening, splash screen, mobile auth, and connectivity fixes#21
my-claude-utils merged 5 commits intomainfrom
feat/splash-auth-ux

Conversation

@my-claude-utils
Copy link
Owner

Summary

Consolidated PR merging the full security + UX stack to main:

Security Hardening (Steps 4.1a + 4.1b)

  • C2: One-time bootstrap tokens (consumed on use, 5-min TTL)
  • C3: .gitignore covers .env and secrets
  • C4: WebSocket origin validation (verifyClient with dynamic allowed origins)
  • H1: CORS restricted to allowed origins (no more wildcard)
  • H2: Rate limiting on auth endpoint (10 req / 15 min)
  • H3: WebSocket max payload 64KB
  • H5: JWT sent as first WS message (not in URL)
  • H6: Bootstrap token in URL hash fragment (not query param)
  • H7: Terminal resize validation
  • H9: SSE endpoint removed
  • Express trust proxy for rate-limit behind tunnels
  • Security headers (X-Frame-Options, CSP, nosniff, referrer-policy)

QR Scanner + PWA Install Banner

  • In-app QR scanner with rear camera (jsQR)
  • iOS Safari getUserMedia fix ({ ideal: 'environment' } constraint)
  • PWA install banner above workspace bar (mobile only)

Splash Screen + Mobile Auth UX

  • Animated CLSH ASCII logo reveal (staggered lines, glow, shimmer, fadeout)
  • Mobile auth simplified to QR-only (no token paste clutter)
  • Desktop auth unchanged (paste-token form)
  • Token removed from terminal output (embedded in QR only)

Connectivity Fixes

  • Local network IP added to allowedOrigins (phones on same Wi-Fi)
  • Dynamic web port in origins (supports PORT=N for multiple instances)
  • WS origin rejection logging for debugging
  • Connection status banner in GridView (reconnecting / disconnected)
  • iOS history.replaceState wrapped in try/catch (PWA standalone mode)
  • Vite proxy changeOrigin: true for tunnel compatibility
  • Client-side WS close/error logging
  • Dev script runs agent in foreground (stdin passthrough for Enter key QR regen)
  • White flash fix: inline dark background on <body>

Test plan

  • npx turbo run typecheck lint build passes
  • QR scan from phone Camera app auto-authenticates
  • In-app QR scanner works on iOS Safari (HTTPS)
  • Splash animation plays and fades to grid on success
  • Mobile auth shows QR-only, desktop shows paste form
  • Connection status banner shows when WS disconnected
  • Multiple dev instances (PORT=4032 npm run dev) work
  • SSH tunnel mode: QR regeneration + re-scan works
  • Local Wi-Fi mode: phone connects and creates sessions

🤖 Generated with Claude Code

my-claude-utils and others added 5 commits March 16, 2026 00:46
- C2: One-time bootstrap tokens (deleted after JWT exchange) + Enter key regeneration
- C3: Pre-commit hook to reject staged .env files
- C4: WebSocket origin verification (verifyClient callback)
- H1: Dynamic CORS (restricted to allowed origins, no more wildcard)
- QR scanner uses rear camera + jsQR to scan bootstrap tokens from terminal QR codes
- Supports both ?token= and #token= URL formats
- Mobile: "Scan QR Code" is the primary auth action, paste-token secondary
- PWA install banner shows after 30s in mobile browsers (not in standalone PWA)
- Android: native install prompt via beforeinstallprompt
- iOS: manual instructions (Share > Add to Home Screen)
- Dismissible with localStorage persistence
- H2: Rate limit /api/auth/bootstrap (10 req/15min) via express-rate-limit
- H3: WebSocket maxPayload 64KB
- H4: Session limit (max 8 concurrent PTY sessions)
- H5: JWT auth via first WS message instead of URL query param
- H6: Bootstrap token in hash fragment (#token=) instead of query string
- H7: Resize validation (clamp cols 1-500, rows 1-200)
- H9: Remove dead SSE handler
- L5: JSON body size limit (16KB)
- L6: Security headers (X-Frame-Options, CSP, X-Content-Type-Options, Referrer-Policy)
Splash Screen:
- New SplashScreen component with animated CLSH ASCII logo reveal
- 6 lines stagger in with slide + fade (200ms delay, 400ms duration each)
- Pulsing orange background glow, subtle shimmer loop after reveal
- 500ms fade-out transition reveals content underneath (no flash)
- Renders as z-50 overlay so all hooks run unconditionally
- Dark placeholder (#060606) underneath prevents auth screen flash

Mobile Auth (QR-only):
- Simplified mobile layout: only "Scan QR Code" button (no token input,
  no paste button, no keyboard toggle, no Connect button)
- Desktop auth unchanged (paste-token form remains)
- "Need help?" footer links to GitHub repo
- Error messages shown above the action button

QR Scanner iOS Fix:
- Changed getUserMedia from exact facingMode: 'environment' to
  { ideal: 'environment' } with fallback to { video: true }
- Fixes "The string did not match the expected pattern" on iOS Safari

Terminal Output:
- Removed raw bootstrap token from printAccessInfo output (security)
- Token is embedded in QR code, no need to print separately

Express Trust Proxy:
- Added app.set('trust proxy', 1) for express-rate-limit behind tunnels

PWA Install Banner:
- Redesigned as inline element above WorkspaceBar (not floating overlay)
- Shows immediately on first render (no delay), mobile-only
- Dark card with orange gradient, app icon, iOS Share instructions

Dev Workflow:
- Changed root dev script from turbo to direct process management
- Agent runs in foreground (gets stdin for Enter key QR regeneration)
- Web dev server runs in background, both killed on Ctrl+C via trap
- Stdin listener works without TTY guard (try/catch for raw mode)
- Dynamic ports via PORT env var (default 4030, web = PORT+1)
- Vite proxy reads AGENT_PORT env var, enabling multiple instances
  (e.g. PORT=4040 npm run dev for a second instance)

Other:
- Increased rename pencil icon from 11px to 18px for better tap target
- Inline background:#060606 on body to prevent white flash in PWA

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n status UI

- Add local network IP to allowedOrigins (fixes phones on same Wi-Fi getting WS rejected)
- Dynamic web port in allowedOrigins (supports PORT=N for multiple dev instances)
- Log rejected WS origins for easier debugging
- Show connection status banner in GridView when WS is disconnected/reconnecting
- Wrap history.replaceState in try/catch (iOS Safari throws in PWA standalone mode)
- Add changeOrigin to vite proxy for better tunnel compatibility
- Add WS close/error logging on client side

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@my-claude-utils my-claude-utils merged commit ab82d89 into main Mar 16, 2026
2 checks passed
@my-claude-utils my-claude-utils deleted the feat/splash-auth-ux branch March 16, 2026 01:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant