diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c668b65..6538fb0 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -12,7 +12,7 @@ jobs: bun-version: latest - name: Install dependencies - run: bun install + run: bun install --frozen-lockfile #- name: Linting # run: bun run format diff --git a/.gitignore b/.gitignore index 7b1c024..911d5e6 100644 --- a/.gitignore +++ b/.gitignore @@ -145,4 +145,9 @@ results/ .plan.md -sec-findings.md \ No newline at end of file +sec-findings.md +sec-findings.md*.pem +VERIFICATION.md +verification-certificate.json +*.key +examples/cert.* diff --git a/AGENTS.md b/AGENTS.md index c85547f..24b34ae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -81,6 +81,7 @@ src/ ### Path Validation (input-validator.ts + utils.ts) **Two-pass validation** against double-encoding attacks: + 1. **First pass:** Check raw path against `blockedPatterns` (catches null bytes, `../`) 2. **Recursive decode:** `recursiveDecodeURIComponent()` decodes up to 5 layers until stable 3. **Second pass:** Check fully-decoded path (catches `%252f` → `%2f` → `/`) @@ -90,6 +91,7 @@ Never validate before decoding — attackers hide behind encoding layers. ### Health Checks (http-load-balancer.ts) Threshold-based to prevent flapping and cascade failures: + - `failureThreshold` (default 3): consecutive failures before marking unhealthy - `successThreshold` (default 2): consecutive successes before marking healthy again - `minHealthyTargets` (default 1): floor check — refuses to mark the last healthy target down @@ -105,6 +107,7 @@ secure header priority (`cf-connecting-ip` > `x-real-ip`). ### Error Handling Global error handler registered on the 0http-bun router. In production: + - Returns sanitized `{"error":"Internal server error"}` with status 500 - Never leaks stack traces or internal file paths - Logs full error details internally @@ -112,7 +115,15 @@ Global error handler registered on the 0http-bun router. In production: ### Blocked Patterns (config.ts) ```typescript -blockedPatterns: [/\.\./, /%2e%2e/i, /%2f/i, /%5c/i, /%00/, /\0/, /%25%32%[fF]/i] +blockedPatterns: [ + /\.\./, + /%2e%2e/i, + /%2f/i, + /%5c/i, + /%00/, + /\0/, + /%25%32%[fF]/i, +] ``` Covers: raw `..`, encoded `../`, encoded `/`, encoded `\`, null byte, double-encoded `/`. diff --git a/benchmark/bungate-gateway.ts b/benchmark/bungate-gateway.ts index af1fcec..20460f5 100644 --- a/benchmark/bungate-gateway.ts +++ b/benchmark/bungate-gateway.ts @@ -1,3 +1,11 @@ +/** + * ═══════════════════════════════════════════════════════════════════════════ + * FOR BENCHMARKING ONLY — NOT FOR PRODUCTION + * This file intentionally disables security features to maximize throughput. + * Do not use this configuration as a template for production deployments. + * ═══════════════════════════════════════════════════════════════════════════ + */ + import { BunGateway } from './src' import { BunGateLogger } from './src' import { cpus } from 'os' diff --git a/benchmark/echo-server-simple.ts b/benchmark/echo-server-simple.ts index 897e5c7..08599d2 100644 --- a/benchmark/echo-server-simple.ts +++ b/benchmark/echo-server-simple.ts @@ -1,9 +1,23 @@ +/** + * ═══════════════════════════════════════════════════════════════════════════ + * FOR BENCHMARKING ONLY — NOT FOR PRODUCTION + * ═══════════════════════════════════════════════════════════════════════════ + */ + const serverId = process.env.SERVER_ID || 'unknown' const port = parseInt(process.env.SERVER_PORT || '8080') let requestCount = 0 let startTime = Date.now() +const SENSITIVE_HEADERS = new Set([ + 'authorization', + 'cookie', + 'proxy-authorization', + 'x-api-key', + 'x-metrics-key', +]) + const server = Bun.serve({ port, fetch(req) { @@ -19,7 +33,14 @@ const server = Bun.serve({ }) } - // Echo endpoint with server info + // Echo endpoint with server info — redact sensitive headers + const safeHeaders: Record = {} + for (const [name, value] of req.headers.entries()) { + safeHeaders[name] = SENSITIVE_HEADERS.has(name.toLowerCase()) + ? '[REDACTED]' + : value + } + const response = { server_id: serverId, request_count: requestCount, @@ -27,16 +48,13 @@ const server = Bun.serve({ timestamp: now, method: req.method, url: req.url, - headers: Object.fromEntries(req.headers.entries()), + headers: safeHeaders, remote_addr: req.headers.get('x-forwarded-for') || 'unknown', } return new Response(JSON.stringify(response, null, 2), { headers: { 'Content-Type': 'application/json', - Server: `echo-server-${serverId}`, - 'X-Request-Count': requestCount.toString(), - 'X-Server-Id': serverId, }, }) }, diff --git a/bun.lock b/bun.lock index 1289187..96bf6b9 100644 --- a/bun.lock +++ b/bun.lock @@ -13,7 +13,8 @@ "prom-client": "^15.1.3", }, "devDependencies": { - "@types/bun": "latest", + "@types/bun": "^1.2.0", + "node-forge": "^1.4.0", "prettier": "^3.6.2", }, "peerDependencies": { @@ -68,6 +69,8 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "node-forge": ["node-forge@1.4.0", "", {}, "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ=="], + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index 64e93ea..0733318 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -115,6 +115,8 @@ interface ClusterConfig { respawnThresholdTime?: number // Default: 60000ms shutdownTimeout?: number // Default: 30000ms exitOnShutdown?: boolean // Default: true + allowedEnvVars?: string[] // Allow-list of env vars forwarded to workers + workerScriptAllowlist?: string[] // Allow-list of worker script paths } ``` @@ -392,12 +394,17 @@ gateway.addRoute({ ```typescript interface HealthCheckConfig { enabled: boolean - interval?: number // Default: 30000ms - timeout?: number // Default: 5000ms + interval?: number // Default: 30000ms, clamped to 1000ms–300000ms + timeout?: number // Default: 5000ms, must be less than interval path?: string // Default: '/health' + method?: 'GET' | 'HEAD' // Default: 'GET' expectedStatus?: number // Default: 200 - unhealthyThreshold?: number // Default: 3 - healthyThreshold?: number // Default: 2 + expectedBody?: string // Optional body substring required for healthy status + failureThreshold?: number // Default: 3, max 20 + successThreshold?: number // Default: 2, max 20 + minHealthyTargets?: number // Default: 1 + allowedSchemes?: string[] // Default: ['http', 'https'] + allowedHosts?: string[] // CIDR or exact hosts allowed for health checks validator?: (response: Response) => Promise | boolean } ``` diff --git a/docs/CLUSTERING.md b/docs/CLUSTERING.md index ea7cbd3..8bc77ab 100644 --- a/docs/CLUSTERING.md +++ b/docs/CLUSTERING.md @@ -141,6 +141,14 @@ interface ClusterConfig { // Exit master process after shutdown (default: true) // Set to false for testing or embedded usage exitOnShutdown: boolean + + // Allow-list of environment variable names forwarded to workers. + // If provided, only these variables (plus internal CLUSTER_* vars) are passed. + allowedEnvVars?: string[] + + // Allow-list of worker script paths. The worker script must match one of + // these paths to prevent accidental execution of arbitrary files. + workerScriptAllowlist?: string[] } ``` diff --git a/docs/LOAD_BALANCING.md b/docs/LOAD_BALANCING.md index b67fbe6..ebbf5c6 100644 --- a/docs/LOAD_BALANCING.md +++ b/docs/LOAD_BALANCING.md @@ -401,8 +401,8 @@ gateway.addRoute({ timeout: 5000, // 5 second timeout path: '/health', // Health check endpoint expectedStatus: 200, // Expected status code - unhealthyThreshold: 3, // Failures before marking unhealthy - healthyThreshold: 2, // Successes before marking healthy + failureThreshold: 3, // Failures before marking unhealthy + successThreshold: 2, // Successes before marking healthy }, }, }) @@ -594,7 +594,7 @@ gateway.addRoute({ enabled: true, interval: 10000, path: '/health', - unhealthyThreshold: 2, // Fast failover + failureThreshold: 2, // Fast failover }, }, }) @@ -650,12 +650,29 @@ healthCheck: { interval: 15000, timeout: 5000, path: '/health', - unhealthyThreshold: 3, - healthyThreshold: 2, + failureThreshold: 3, + successThreshold: 2, } ``` -### 2. Use Circuit Breakers for External Services +### 2. Restrict Health Check Targets + +For security, limit which schemes and hosts the load balancer may probe. + +```typescript +healthCheck: { + enabled: true, + interval: 15000, + timeout: 5000, + path: '/health', + failureThreshold: 3, + successThreshold: 2, + allowedSchemes: ['http', 'https'], + allowedHosts: ['10.0.0.0/8', 'api.internal.example.com'], +} +``` + +### 3. Use Circuit Breakers for External Services ```typescript circuitBreaker: { @@ -666,7 +683,7 @@ circuitBreaker: { } ``` -### 3. Configure Timeouts +### 4. Configure Timeouts ```typescript gateway.addRoute({ @@ -681,7 +698,7 @@ gateway.addRoute({ }) ``` -### 4. Monitor Target Health +### 5. Monitor Target Health ```typescript import { PinoLogger } from 'bungate' @@ -698,7 +715,7 @@ gateway.on('target-healthy', (target) => { }) ``` -### 5. Use Appropriate Strategy +### 6. Use Appropriate Strategy ```typescript // ❌ DON'T use IP hash for APIs behind NAT @@ -714,7 +731,7 @@ loadBalancer: { } ``` -### 6. Plan for Capacity +### 7. Plan for Capacity ```typescript // Configure weights based on actual capacity @@ -728,7 +745,7 @@ loadBalancer: { } ``` -### 7. Test Failover Scenarios +### 8. Test Failover Scenarios ```bash # Simulate server failure @@ -781,7 +798,7 @@ targets: [ healthCheck: { enabled: true, timeout: 10000, // Increase from 5000 - unhealthyThreshold: 5, // Require more failures + failureThreshold: 5, // Require more failures } // 2. Check health endpoint performance diff --git a/docs/index.html b/docs/index.html index 47d1162..5d256ca 100644 --- a/docs/index.html +++ b/docs/index.html @@ -17,13 +17,22 @@ Bungate — Lightning-Fast HTTP Gateway & Load Balancer - + - - + + @@ -33,374 +42,1100 @@ - - + + - - - - - - - - + + + + + + + + - -
-
- +
+
+ Open Source
- -

- Bungate — Lightning-Fast HTTP Gateway + +

+ Bungate — Lightning-Fast + HTTP Gateway

-

- Enterprise-grade HTTP gateway & load balancer built on Bun. TLS 1.3, JWT key rotation, 8+ load balancing strategies, and zero-config simplicity. +

+ Enterprise-grade HTTP gateway & load balancer built on Bun. TLS 1.3, JWT + key rotation, 8+ load balancing strategies, and zero-config simplicity.

-
- - +
-
18K+
Requests / second
-
<1ms
Routing Overhead
-
8+
LB Strategies
-
TLS 1.3
Enterprise Security
-
98.9%
Test Coverage
+
+
18K+
+
Requests / second
+
+
+
<1ms
+
Routing Overhead
+
+
+
8+
+
LB Strategies
+
+
+
TLS 1.3
+
Enterprise Security
+
+
+
98.9%
+
Test Coverage
+
-
+
-

Built for developers who demand performance

-

A gateway that combines Bun's native speed with enterprise security — no compromises, no config overhead.

- -
-
-
-

Bun-Native Performance

-

Optimized for Bun's runtime. 18K+ req/s, single-digit ms latency, sub-30ms p99 response times in production benchmarks against nginx and envoy.

+

+ Built for developers who demand performance +

+

+ A gateway that combines Bun's native speed with enterprise security — + no compromises, no config overhead. +

+ +
+
+
+ ⚡ +
+

+ Bun-Native Performance +

+

+ Optimized for Bun's runtime. 18K+ req/s, single-digit ms latency, + sub-30ms p99 response times in production benchmarks against nginx + and envoy. +

-
-
🧠
-

Smart Load Balancing

-

Round-robin, least-connections, weighted, ip-hash, random, power-of-two-choices, latency, weighted-least-connections. Cookie-based sticky sessions.

+
+
+ 🧠 +
+

+ Smart Load Balancing +

+

+ Round-robin, least-connections, weighted, ip-hash, random, + power-of-two-choices, latency, weighted-least-connections. + Cookie-based sticky sessions. +

-
-
🔒
-

Enterprise Security

-

TLS 1.3 with auto HTTP redirect, JWT key rotation with JWKS, input validation, CSRF protection, security headers, trusted proxy validation, OWASP Top 10 coverage.

+
+
+ 🔒 +
+

+ Enterprise Security +

+

+ TLS 1.3 with auto HTTP redirect, JWT key rotation with JWKS, input + validation, CSRF protection, security headers, trusted proxy + validation, OWASP Top 10 coverage. +

-
-
-
-

TypeScript First

-

Complete type definitions for every API. IDE autocomplete, type safety, and inline documentation from bun add bungate to production.

+
+
+
+ ⌨ +
+

+ TypeScript First +

+

+ Complete type definitions for every API. IDE autocomplete, type + safety, and inline documentation from + bun add bungate + to production. +

-
-
🔧
-

Production Ready

-

Circuit breakers, health checks with configurable intervals, auto-failover, timeout management, cluster mode with zero-downtime rolling restarts. Built for reliability.

+
+
+ 🔧 +
+

+ Production Ready +

+

+ Circuit breakers, health checks with configurable intervals, + auto-failover, timeout management, cluster mode with zero-downtime + rolling restarts. Built for reliability. +

-
-
🎯
-

Zero Config

-

Works out of the box with sensible defaults. Get started in seconds — production-ready from day one with Prometheus metrics, structured logging, and health endpoints.

+
+
+ 🎯 +
+

+ Zero Config +

+

+ Works out of the box with sensible defaults. Get started in + seconds — production-ready from day one with Prometheus metrics, + structured logging, and health endpoints. +

@@ -411,7 +1146,10 @@

Zero Co

Production-ready in seconds

-

Install, configure, deploy. A complete API gateway with load balancing, auth, and security — from a single file.

+

+ Install, configure, deploy. A complete API gateway with load + balancing, auth, and security — from a single file. +

@@ -420,44 +1158,59 @@

Production-ready in seconds

-
-
-// gateway.ts — production-ready in one file -import { BunGateway } from 'bungate' - -const gateway = new BunGateway({ - server: { port: 3000 }, - cluster: { enabled: true, workers: 4 }, - auth: { secret: process.env.JWT_SECRET }, - metrics: { enabled: true }, -}) - -gateway.addRoute({ - pattern: '/api/*', - loadBalancer: { - strategy: 'least-connections', - targets: [ - { url: 'http://api1.example.com' }, - { url: 'http://api2.example.com' }, - ], - healthCheck: { enabled: true, interval: 15000, path: '/health' }, - }, - circuitBreaker: { enabled: true, failureThreshold: 5 }, -}) - -await gateway.listen() -console.log('🚀 Bungate cluster running on :3000') +
+
+ // gateway.ts — production-ready in one file + import { BunGateway } + from 'bungate' + + const gateway = new + BunGateway({ server: { port: + 3000 }, cluster: { enabled: + true, workers: 4 }, + auth: { secret: process.env.JWT_SECRET }, + metrics: { enabled: true }, }) gateway.addRoute({ pattern: '/api/*', loadBalancer: { + strategy: 'least-connections', targets: [ { + url: 'http://api1.example.com' }, { url: + 'http://api2.example.com' }, ], + healthCheck: { enabled: true, interval: + 15000, path: + '/health' }, }, circuitBreaker: { enabled: + true, failureThreshold: + 5 }, }) + + await gateway.listen() console.log('🚀 Bungate cluster running on :3000')

-
+

Battle-tested security defaults

-

Every request passes through a defense-in-depth pipeline — from TLS termination to JWT validation to input sanitization.

+

+ Every request passes through a defense-in-depth pipeline — from TLS + termination to JWT validation to input sanitization. +

✓ TLS 1.3 @@ -472,136 +1225,378 @@

Battle-tested security defaults

✓ CSRF Protection
-
-
-// TLS 1.3 + JWT key rotation — zero-downtime -const gateway = new BunGateway({ - security: { - tls: { - enabled: true, - cert: './cert.pem', - key: './key.pem', - minVersion: 'TLSv1.3', - cipherSuites: ['TLS_AES_256_GCM_SHA384', 'TLS_CHACHA20_POLY1305_SHA256'], - redirectHTTP: true, - }, - jwtKeyRotation: { - secrets: [ - { key: process.env.JWT_NEW, kid: '2025-05', primary: true }, - { key: process.env.JWT_OLD, kid: '2025-04', deprecated: true }, - ], - jwksUri: 'https://auth.example.com/.well-known/jwks.json', - jwksRefreshInterval: 3600000, - }, - inputValidation: { - maxPathLength: 2048, - maxHeaderSize: 16384, - blockedPatterns: [/\\.\\./, /%00/, /<script>/i], - }, - }, -}) +
+
+ // TLS 1.3 + JWT key rotation — zero-downtime + const gateway = new + BunGateway({ security: { tls: { enabled: + true, cert: + './cert.pem', key: + './key.pem', minVersion: + 'TLSv1.3', cipherSuites: ['TLS_AES_256_GCM_SHA384', 'TLS_CHACHA20_POLY1305_SHA256'], + redirectHTTP: true, }, jwtKeyRotation: { + secrets: [ { key: process.env.JWT_NEW, kid: + '2025-05', primary: + true }, { key: process.env.JWT_OLD, kid: '2025-04', deprecated: + true }, ], jwksUri: + 'https://auth.example.com/.well-known/jwks.json', jwksRefreshInterval: 3600000, }, + inputValidation: { maxPathLength: 2048, + maxHeaderSize: 16384, blockedPatterns: + [/\\.\\./, /%00/, + /<script>/i], }, }, })
-
+
-

Penetration tested. 803 tests. Zero failures.

-

- Bungate underwent a comprehensive security audit in an isolated Docker environment. - Every vulnerability found was fixed and verified before shipping. Here's the real data. +

+ Penetration tested. 803 tests. Zero failures. +

+

+ Bungate underwent a comprehensive security audit in an isolated Docker + environment. Every vulnerability found was fixed and verified before + shipping. Here's the real data.

-
-
-
🛡
-

Pentest Verified

-

- Full security audit with exploit simulation: double-encoding traversal, health check cascade DoS, X-Forwarded-For rate limit bypass, CORS evasion. All 4 vulnerabilities found and fixed. +

+
+
🛡
+

+ Pentest Verified +

+

+ Full security audit with exploit simulation: double-encoding + traversal, health check cascade DoS, X-Forwarded-For rate limit + bypass, CORS evasion. All 4 vulnerabilities + found and fixed.

-
-
-

Zero Regressions

-

- Every security fix validated against the full 803-test suite. 633 to 803 tests during coverage improvement. Zero pre-existing tests broken by security patches. +

+
+

+ Zero Regressions +

+

+ Every security fix validated against the full 803-test suite. + 633 to 803 tests during + coverage improvement. Zero pre-existing tests broken by security + patches.

-
-
🔬
-

Recursive Decode Engine

-

- Custom recursiveDecodeURIComponent() defeats multi-layer encoding attacks (%252f to %2f to /). Two-pass validation: raw path then fully-decoded path. +

+
🔬
+

+ Recursive Decode Engine +

+

+ Custom + recursiveDecodeURIComponent() + defeats multi-layer encoding attacks (%252f to + %2f to /). Two-pass validation: raw path + then fully-decoded path.

-
-
-
-
98.97%
-
Line Coverage
+
+
+
+
+ 98.97% +
+
+ Line Coverage +
-
-
94.55%
-
Function Coverage
+
+
+ 94.55% +
+
+ Function Coverage +
-
-
803
-
Tests (44 files)
+
+
+ 803 +
+
+ Tests (44 files) +
-
-
0
-
Failures
+
+
+ 0 +
+
+ Failures +
-
-
-
- 🔒 - Input Validation - FIXED +
+
+
+ 🔒 + Input Validation + FIXED
-

- Double-encoding (%252f) and quad-dot traversal now defeated by recursive decode + two-pass validation + expanded blocked patterns. +

+ Double-encoding (%252f) and quad-dot traversal now + defeated by recursive decode + two-pass validation + expanded + blocked patterns.

-
-
- - Cascade Failure - FIXED +
+
+ + Cascade Failure + FIXED
-

- Threshold-based health checks: 3 consecutive failures to mark unhealthy, 2 successes to recover. Min-healthy floor prevents complete cascade. +

+ Threshold-based health checks: 3 consecutive failures to mark + unhealthy, 2 successes to recover. Min-healthy floor prevents + complete cascade.

-
-
- 📈 - Rate Limit Bypass - FIXED +
+
+ 📈 + Rate Limit Bypass + FIXED
-

- Rate limiter now keys on the gateway's getClientIP() via trusted proxy validator. X-Forwarded-For rotation no longer bypasses limits. +

+ Rate limiter now keys on the gateway's + getClientIP() via trusted proxy validator. + X-Forwarded-For rotation no longer bypasses limits.

-
-
- 🌐 - Error Handler - FIXED +
+
+ 🌐 + Error Handler + FIXED
-

- Global error handler properly catches exceptions. CORS preflight returns clean 204. No more stack trace or internal file path leakage. +

+ Global error handler properly catches exceptions. CORS preflight + returns clean 204. No more stack trace or internal file path + leakage.

@@ -609,74 +1604,160 @@

Recursive Decode Engine

-
-

Ready to ship faster?

-

Enterprise security, zero config. Install and deploy in minutes.

- - +
+

+ Ready to ship faster? +

+

+ Enterprise security, zero config. Install and deploy in minutes. +

+
+ + + View on GitHub
-