Seams Web Proxy#
A client-side web proxy using wabac.js for annotating any webpage without installing an extension.
Architecture#
This approach uses:
- CORS Proxy (
cors-proxy/) - Node.js server that adds CORS headers with SSRF protection - wabac.js Service Worker - Intercepts requests in the browser and routes through CORS proxy
- Script Injection -
seams-client.jsis injected into proxied pages
The user's browser does all the heavy lifting - the server just adds CORS headers.
Setup#
# Install dependencies
npm install
# Also install cors-proxy dependencies
cd cors-proxy && npm install && cd ..
Development#
# Start both servers (cors-proxy on 8082 via Caddy, static on 8081)
npm run dev
Then visit: http://localhost:8081/#https://example.com
How It Works#
- User visits
http://localhost:8081/#https://example.com loadwabac.jsregisters the wabac.js service worker- Service worker intercepts requests to
/w/liveproxy/mp_/https://example.com - Requests are routed through
http://localhost:8082/proxy/https://example.com - CORS proxy fetches the page and adds necessary headers
seams-client.jsis injected into the page for annotation functionality
Directory Structure#
proxy/
├── cors-proxy/
│ ├── index.ts # Hono-based CORS proxy server
│ ├── package.json
│ └── tsconfig.json
│
├── src/
│ ├── index.html # Main page with iframe
│ ├── loadwabac.js # Initialize wabac.js service worker
│ └── client-metadata.json # OAuth metadata (for production use)
│
├── html/
│ ├── seams-shell.html # Shell with sidebar (built by vite)
│ └── oauth-callback.html # OAuth callback handler
│
├── Caddyfile
├── package.json
└── README.md
Ports#
- 8081 - Static site (main page with iframe) via Caddy
- 8082 - CORS proxy (Caddy reverse proxies to Node on 8083)
- 8083 - CORS proxy Node.js server (internal)
Environment Variables#
The CORS proxy supports these environment variables:
| Variable | Default | Description |
|---|---|---|
CORS_ALLOWED_ORIGINS |
http://127.0.0.1:8081 |
Comma-separated allowed origins |
CORS_PROXY_PORT |
8083 |
Port for Node.js server |
CORS_PROXY_MAX_BODY_SIZE |
10485760 (10MB) |
Max request body size |
CORS_PROXY_TIMEOUT_MS |
30000 |
Request timeout in ms |
CORS_PROXY_RATE_WINDOW_MS |
60000 |
Rate limit window in ms |
CORS_PROXY_RATE_LIMIT |
100 |
Max requests per window |
CORS_PROXY_RATE_MAX_CLIENTS |
10000 |
Max tracked clients (memory bound) |
CORS Proxy Details#
The CORS proxy handles:
- Redirects: Returns 200 with
x-redirect-status,x-orig-locationheaders - Cookies: Proxies via
x-proxy-cookieandx-proxy-set-cookieheaders - Referer: Accepts
x-proxy-refererheader - User-Agent: Accepts
x-proxy-user-agentheader
Security Features#
- SSRF Protection: Blocks private IPs, cloud metadata endpoints
- DNS Rebinding Mitigation: Validates resolved IPs before fetch (see note below)
- Rate Limiting: Configurable per-origin request limits with bounded memory
- Origin Validation: Requires Origin header (no Referer fallback)
- Audit Logging: Logs all proxy requests with request IDs
DNS Rebinding Limitation#
The DNS validation provides defense-in-depth but cannot fully prevent DNS rebinding
attacks due to TOCTOU (time-of-check-time-of-use) - fetch() does its own DNS
resolution after our validation. For full protection in production, deploy behind
a network-level firewall that blocks outbound connections to private IP ranges
(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16).
Next Steps#
- Deploy CORS proxy to Cloudflare Workers or similar edge runtime
- Deploy static site to CDN