feat: security hardening, splash screen, mobile auth, and connectivity fixes#21
Merged
my-claude-utils merged 5 commits intomainfrom Mar 16, 2026
Merged
feat: security hardening, splash screen, mobile auth, and connectivity fixes#21my-claude-utils merged 5 commits intomainfrom
my-claude-utils merged 5 commits intomainfrom
Conversation
- 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Consolidated PR merging the full security + UX stack to main:
Security Hardening (Steps 4.1a + 4.1b)
.gitignorecovers.envand secretsverifyClientwith dynamic allowed origins)trust proxyfor rate-limit behind tunnelsQR Scanner + PWA Install Banner
{ ideal: 'environment' }constraint)Splash Screen + Mobile Auth UX
Connectivity Fixes
allowedOrigins(phones on same Wi-Fi)PORT=Nfor multiple instances)history.replaceStatewrapped in try/catch (PWA standalone mode)changeOrigin: truefor tunnel compatibility<body>Test plan
npx turbo run typecheck lint buildpassesPORT=4032 npm run dev) work🤖 Generated with Claude Code