8.1 KiB
Chat Summary: Native WebSocket Investigation - 2025-10-31 09:43:00
Session Overview
Date: October 31, 2025, 09:43:00
Task: Reverse engineer Kosmi WebSocket API to replace ChromeDP with native Go client
Status: ⚠️ BLOCKED - WebSocket server requires browser context
Problem Statement
The goal was to replace the resource-heavy ChromeDP implementation (~100-200MB RAM, 3-5s startup) with a lightweight native Go WebSocket client (~10-20MB RAM, <1s startup).
Investigation Summary
Phase 1: Authentication Data Capture ✅
Created cmd/capture-auth/main.go to intercept and log all authentication data from a working ChromeDP session.
Key Findings:
-
JWT Token Discovery: WebSocket uses JWT token in
connection_initpayload -
Token Structure:
{ "aud": "kosmi", "exp": 1793367309, // 1 YEAR expiration! "sub": "a067ec32-ad5c-4831-95cc-0f88bdb33587", // Anonymous user ID "typ": "access" } -
Connection Init Format:
{ "type": "connection_init", "payload": { "token": "eyJhbGc...", // JWT token "ua": "TW96aWxs...", // Base64-encoded User-Agent "v": "4364", // App version "r": "" // Room (empty for anonymous) } } -
No Cookies Required: The
g_statecookie is not needed for WebSocket auth
Output: auth-data.json with 104 WebSocket frames captured, 77 network requests logged
Phase 2: Direct Connection Tests ❌
Created three test programs to attempt native WebSocket connections:
Test 1: cmd/test-websocket/main.go
- Mode 1: With JWT token
- Mode 2: No authentication
- Mode 3: Origin header only
Test 2: cmd/test-websocket-direct/main.go
- Direct WebSocket with captured JWT token
- All required headers (Origin, User-Agent, etc.)
Test 3: cmd/test-session/main.go
- Visit room page first to establish session
- Use cookies from session
- Connect WebSocket with token
Results: ALL tests returned 403 Forbidden during WebSocket handshake
Phase 3: Root Cause Analysis 🔍
The Problem:
- 403 occurs during WebSocket handshake, BEFORE
connection_init - This means the server rejects the connection based on the CLIENT, not the authentication
- ChromeDP works because it's a real browser
- Native Go client is detected and blocked
Likely Causes:
- TLS Fingerprinting: Go's TLS implementation has a different fingerprint than Chrome
- Cloudflare Protection: Server uses bot detection (Captcha/challenge)
- WebSocket Extensions: Browser sends specific extensions we're not replicating
- CDN Security: Via header shows "1.1 Caddy" - reverse proxy with security rules
Evidence:
Response headers from 403:
Cache-Control: [max-age=0, private, must-revalidate]
Server: [Cowboy]
Via: [1.1 Caddy]
Alt-Svc: [h3=":443"; ma=2592000]
Files Created
cmd/capture-auth/main.go- Authentication data capture toolcmd/test-websocket/main.go- Multi-mode WebSocket test toolcmd/test-websocket-direct/main.go- Direct token-based testcmd/test-session/main.go- Session-based connection testAUTH_FINDINGS.md- Detailed authentication documentationWEBSOCKET_403_ANALYSIS.md- Comprehensive 403 error analysisauth-data.json- Captured authentication data (104 WS frames)
Key Insights
What We Learned
- Kosmi uses standard JWT authentication - Well-documented format
- Tokens are long-lived - 1 year expiration means minimal refresh needs
- Anonymous access works - No login credentials needed
- GraphQL-WS protocol - Standard protocol, not proprietary
- The blocker is NOT authentication - It's client detection/fingerprinting
Why ChromeDP Works
ChromeDP bypasses all protection because it:
- ✅ Is literally Chrome (correct TLS fingerprint)
- ✅ Executes JavaScript (passes challenges)
- ✅ Has complete browser context
- ✅ Sends all expected headers/extensions
- ✅ Looks like a real user to security systems
Recommendations
Option A: Optimize ChromeDP (RECOMMENDED ⭐)
Rationale:
- It's the ONLY approach that works 100%
- Security bypass is likely impossible without reverse engineering Cloudflare
- 100-200MB RAM is acceptable for a bridge service
- Startup time is one-time cost
Optimizations:
// Use headless-shell instead of full Chrome (~50MB savings)
FROM chromedp/headless-shell:latest
// Reduce memory footprint
chromedp.Flag("single-process", true),
chromedp.Flag("disable-dev-shm-usage", true),
chromedp.Flag("disable-gpu", true),
// Keep instance alive (avoid restart cost)
type ChromeDPPool struct {
instance *ChromeDPClient
mu sync.Mutex
}
Expected Results:
- Memory: ~100MB (vs ~200MB currently)
- Startup: 3-5s (one-time, then instant)
- Reliability: 100%
Option B: Hybrid Token Caching
IF we could bypass 403 (which we can't):
// Get token via ChromeDP once per year
token := getTokenViaChromeDPOnce()
cacheToken(token, 11*months)
// Use native WebSocket with cached token
conn := nativeWebSocketConnect(token)
Problem: Still returns 403, so this doesn't help
Option C: HTTP POST Polling (FALLBACK)
From FINDINGS.md - HTTP POST works without authentication:
curl -X POST https://engine.kosmi.io/ \
-H "Content-Type: application/json" \
-d '{"query": "{ messages { id body } }"}'
Pros:
- ✅ No browser needed
- ✅ Lightweight
- ✅ No 403 errors
Cons:
- ❌ Not real-time (need to poll)
- ❌ Higher latency (1-2s minimum)
- ❌ More bandwidth
- ❌ Might still be rate-limited
Decision Point
Question for User: Which approach do you prefer?
-
Keep and optimize ChromeDP (reliable, heavier)
- Stick with what works
- Optimize for memory/startup
- Accept ~100MB overhead
-
Try HTTP POST polling (lighter, but not real-time)
- Abandon WebSocket
- Poll every 1-2 seconds
- Accept latency trade-off
-
Continue native WebSocket investigation (might be futile)
- Attempt TLS fingerprint spoofing
- Try different Go TLS libraries
- Reverse engineer Cloudflare protection
- Warning: May never succeed
Current Status
Completed ✅
- Capture authentication data from ChromeDP
- Create test programs for direct WebSocket
- Test all authentication combinations
- Document findings and analysis
Blocked ⚠️
- Implement native WebSocket client (403 Forbidden)
- Test message flow with native client (can't connect)
- Replace ChromeDP (no working alternative)
Pending User Decision 🤔
- Which approach to pursue?
- Accept ChromeDP optimization?
- Try HTTP polling instead?
- Invest more time in security bypass?
Files for Review
- AUTH_FINDINGS.md - Complete authentication documentation
- WEBSOCKET_403_ANALYSIS.md - Why native WebSocket fails
- auth-data.json - Raw captured data
- cmd/capture-auth/ - Authentication capture tool
- cmd/test-*/ - Various test programs
Next Steps (Pending Decision)
If Option A (Optimize ChromeDP):
- Research chromedp/headless-shell
- Implement memory optimizations
- Add Chrome instance pooling
- Benchmark improvements
- Update documentation
If Option B (HTTP Polling):
- Test HTTP POST queries
- Implement polling loop
- Handle rate limiting
- Test latency impact
- Document trade-offs
If Option C (Continue Investigation):
- Set up Wireshark to analyze browser traffic
- Research TLS fingerprinting bypass
- Test with different TLS libraries
- Attempt Cloudflare bypass techniques
- Warning: Success not guaranteed
Conclusion
After extensive testing, native Go WebSocket connections are blocked by Kosmi's infrastructure (likely Cloudflare or similar). The ChromeDP approach, while heavier, is currently the ONLY working solution for real-time WebSocket communication.
Recommendation: Optimize ChromeDP rather than trying to bypass security measures.
Time Spent: ~2 hours
Tests Performed: 7 different connection methods
Lines of Code: ~800 (test tools + analysis)
Outcome: ChromeDP remains necessary for WebSocket access