2.2 KiB
title, description
| title | description |
|---|---|
| Session Cache | How TRAWL caches solved browser sessions in Redis to avoid unnecessary challenge work. |
Session Cache
The session cache is what makes Tier 2 possible. After a successful Tier 3 or Tier 4 solve, TRAWL saves the extracted cookies and browser user agent in Redis. The next request to the same hostname injects that state into a browser context and attempts to reuse the solved session.
Storage format
Key: session:{hostname} (e.g. session:nowsecure.nl)
Value (JSON):
interface SessionData {
cookies: Cookie[]
userAgent: string
savedAt: number // unix timestamp ms
}
TTL: SESSION_TTL_SECONDS (default 3600 seconds / 1 hour).
Session key
The key is the hostname only — no path, no port, no protocol. This means all pages on a domain share one session:
https://example.com/ → session:example.com
https://example.com/page → session:example.com (same key)
https://sub.example.com/ → session:sub.example.com (different key)
Subdomains have separate sessions because WAF and application cookies can differ per subdomain.
Lifecycle
Tier 3 succeeds
│
├── extract cookies from browser context
├── REDIS SET session:hostname → JSON EX SESSION_TTL_SECONDS
│
└── next request to same domain:
REDIS GET session:hostname
├── hit → Tier 2: inject cookies and navigate
└── miss → Tier 3: fresh solve, save to cache
Invalidation
If Tier 2 navigates with cached state and still receives a recognized challenge wall, the orchestrator:
- Calls
sessionCache.invalidate(domain)— deletes the Redis key - Escalates to Tier 3 to get a fresh session
This handles provider cookies expiring or being rejected before the Redis TTL ends.
Redis
TRAWL's cache backend is Redis 8.8. TRAWL talks to it with new RedisClient(REDIS_URL) from Bun's native Redis client (not ioredis).
import { RedisClient } from 'bun'
const redis = new RedisClient('redis://localhost:6379')
await redis.set('session:example.com', JSON.stringify(data), 'EX', 3600)
const raw = await redis.get('session:example.com')