An implementation of Crystade Proxy Server.
- ✅ Custom Header-Based Proxying - Uses
X-Proxy-Upstreamheader instead of CONNECT method - ✅ SSRF Protection - Comprehensive IP blacklist prevents access to private networks
- ✅ Loop Detection - Prevents infinite proxy chains
- ✅ Authentication - Optional token-based authentication with constant-time comparison
- ✅ Redirect Following - Optional same-origin and cross-origin redirect handling
- ✅ Streaming - Efficient streaming of request and response bodies
- ✅ Cloudflare Workers Compatible - Runs on edge runtimes and serverless platforms
- ✅ TypeScript - Full type safety and excellent IDE support
# Install dependencies
pnpm install
# Build TypeScript
pnpm buildConfiguration is done via environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
HOST |
No | localhost |
Server bind address (standalone mode only) |
PORT |
No | 8080 |
Server port (standalone mode only) |
AUTH_SECRET |
No | - | Authentication secret token (recommended for production) |
UPSTREAM_TIMEOUT |
No | 30000 |
Upstream request timeout in milliseconds |
DISABLE_TLS_VERIFY |
No | false |
Disable TLS verification in standalone Node mode only (development only, never use in production) |
# Copy example environment file
cp .env.example .env
# Edit .env with your configuration
# Generate a secure auth secret:
openssl rand -base64 32# Development mode (with auto-reload)
pnpm dev
# Production mode
pnpm build
pnpm startThe server will start on http://localhost:8080 (or your configured HOST:PORT).
- Configure
wrangler.tomlwith your settings - Set secrets:
wrangler secret put AUTH_SECRET
- Deploy:
pnpm worker:deploy
The target URL to proxy the request to.
X-Proxy-Upstream: https://api.example.com/data
Requirements:
- Must be a valid HTTP or HTTPS URL
- Must not contain credentials (username/password)
- Hostname must not resolve to private IP ranges (RFC 1918, loopback, link-local, etc.)
Authentication token for the proxy. Required if AUTH_SECRET is configured.
X-Proxy-Auth: your-secret-token
Security:
- Must be sent over HTTPS (TLS)
- Plain HTTP requests with this header will be rejected
- Uses constant-time comparison to prevent timing attacks
Enable automatic redirect following.
X-Proxy-Follow-Redirects: same-origin
Values:
same-origin- Follow redirects only within the same origin (scheme, host, port)cross-origin- Follow redirects across origins (cookies are stripped for cross-origin redirects)- Empty or omitted - Don't follow redirects (default)
Behavior:
- Maximum 5 redirects per request
- 301/302 redirects change method to GET (except HEAD)
- 307/308 redirects preserve the original method
- Each redirect target is validated against the IP blacklist
Indicates whether the response is from the upstream or the proxy itself.
Values:
FORWARDED- Response is from the upstream serverERROR- Response was generated by the proxy (error condition)
All proxy-generated errors include X-Proxy-State: ERROR and a JSON body:
{
"reason": "human-readable error message",
"detail": "optional technical details"
}Common Error Codes:
| Status | Reason | Description |
|---|---|---|
400 |
Bad Request | Missing or invalid X-Proxy-Upstream, invalid redirect mode, etc. |
401 |
Unauthorized | Missing or invalid X-Proxy-Auth header |
403 |
Forbidden | Upstream resolves to a blacklisted IP address |
501 |
Not Implemented | Protocol upgrade requested (WebSocket, etc.) |
502 |
Bad Gateway | Upstream unreachable (DNS failure, connection refused, etc.) |
504 |
Gateway Timeout | Upstream connection timeout |
508 |
Loop Detected | Redirect chain exceeds 5 hops or upstream contains X-Proxy-State |
curl -X GET http://localhost:8080/ \
-H "X-Proxy-Upstream: https://api.github.com/users/octocat"curl -X GET https://proxy.example.com/ \
-H "X-Proxy-Upstream: https://api.example.com/data" \
-H "X-Proxy-Auth: your-secret-token"curl -X GET http://localhost:8080/ \
-H "X-Proxy-Upstream: https://example.com/redirect-me" \
-H "X-Proxy-Follow-Redirects: cross-origin"curl -X POST http://localhost:8080/ \
-H "X-Proxy-Upstream: https://api.example.com/data" \
-H "Content-Type: application/json" \
-d '{"key": "value"}'The proxy blocks access to the following IP ranges to prevent SSRF attacks:
IPv4:
10.0.0.0/8- Private network (RFC 1918)172.16.0.0/12- Private network (RFC 1918)192.168.0.0/16- Private network (RFC 1918)127.0.0.0/8- Loopback169.254.0.0/16- Link-local0.0.0.0/8- Current network
IPv6:
fc00::/7- Unique Local Addresses (RFC 4193)fe80::/10- Link-local (RFC 4291)::1/128- Loopback (RFC 4291)
The proxy performs fresh DNS resolution for each request and validates the resolved IP address against the blacklist before establishing a connection. This prevents DNS rebinding attacks where a hostname initially resolves to a public IP but later changes to a private IP.
- Always use
AUTH_SECRETin production - Use HTTPS for the client-proxy connection when authentication is enabled
- Generate strong secrets: Use at least 128 bits of entropy
openssl rand -base64 32
- Rotate secrets regularly
- Use additional network-layer protections (VPC, firewall rules, mTLS)
- Never expose the proxy to the public internet without authentication
- Use network segmentation (VPC, VLAN) to isolate the proxy
- Apply firewall rules to restrict ingress
- Monitor for unusual traffic patterns
- Set appropriate
UPSTREAM_TIMEOUTvalues to prevent resource exhaustion
# Install dependencies
pnpm install
# Run in development mode with auto-reload
pnpm dev
# Build TypeScript
pnpm build
# Run tests
pnpm test
# Run tests with coverage
pnpm test:coverage
# Lint code
pnpm lint
# Format code
pnpm format# Run all tests
pnpm test
# Run tests in watch mode
pnpm test -- --watch
# Generate coverage report
pnpm test:coverage- Cloudflare account
- Wrangler CLI installed (
pnpm install -g wrangler)
-
Update
wrangler.toml:name = "proxy-server" main = "src/worker.ts" compatibility_flags = ["nodejs_compat"] compatibility_date = "2026-05-16" [vars] UPSTREAM_TIMEOUT = "30000"
-
Set secrets:
wrangler secret put AUTH_SECRET
# Deploy to production
pnpm worker:deploy
# Test locally
pnpm worker:dev-
Request Body Replay: For 307/308 redirects that preserve the HTTP method, the request body cannot be replayed. The redirect request will have an empty body.
-
WebSocket Not Supported: The proxy does not support protocol upgrades (WebSocket, HTTP/2 upgrade, etc.). Requests with the
Upgradeheader are rejected with501 Not Implemented. -
HTTP/3 Not Supported: The proxy does not support HTTP/3 (QUIC).
- DNS resolution uses Cloudflare's DNS over HTTPS (1.1.1.1)
DISABLE_TLS_VERIFYdoes not apply on Cloudflare Workers- Maximum request/response size limits apply per Cloudflare Workers limits
MIT License - see LICENSE file for details.