Last Security Review: July 30, 2025
This document outlines the security considerations, practices, and recommendations for the ccflare load balancer system.
IMPORTANT: ccflare is designed for local development and trusted environments. The current implementation has several security limitations:
- No Authentication: All API endpoints and the dashboard are publicly accessible
- Network Exposure: Server binds to all interfaces (0.0.0.0) by default
- Plaintext Token Storage: OAuth tokens are stored unencrypted in SQLite
- No HTTPS: Communication is over HTTP without TLS encryption
- Full Request Logging: All request/response payloads are stored (up to 10MB for streaming)
Recommended Usage:
- Run only in isolated, trusted networks
- Use firewall rules to restrict access to localhost
- Implement reverse proxy with authentication for production use
- Regularly rotate OAuth tokens
- Monitor access logs for unauthorized usage
Based on the latest security review, the following critical issues require immediate attention:
- No Authentication: All endpoints are publicly accessible. Implement API key authentication immediately.
- Network Exposure: Server binds to 0.0.0.0. Use firewall rules or bind to localhost only.
- Plaintext Tokens: OAuth tokens stored unencrypted. Implement AES-256-GCM encryption.
- No CORS Protection: Server does not set any CORS headers, allowing requests from any origin.
- Security Overview
- Threat Model
- OAuth Token Security
- Rate Limit Handling
- Network Security
- Data Privacy
- Access Control
- Security Best Practices
- Vulnerability Disclosure
- Common Security Pitfalls
ccflare is a load balancer proxy that manages multiple OAuth accounts to distribute requests to the Claude API. The system handles sensitive authentication tokens and request/response data, requiring careful security considerations.
- OAuth Token Management: Handles refresh tokens, access tokens, and token rotation using the official Anthropic OAuth flow
- Request Proxying: Forwards API requests with authentication headers, with fallback to unauthenticated mode
- Data Storage: SQLite database storing account credentials and request history
- Network Binding: Server binds to all interfaces (0.0.0.0) on port 8080 by default
- Request/Response Logging: Full payload storage for debugging and analytics with streaming response capture
- Asynchronous DB Operations: Non-blocking database writes for improved performance
- OAuth Tokens: Refresh tokens and access tokens for Claude API access
- Request Data: User prompts and API request payloads
- Response Data: Claude's responses containing potentially sensitive information
- Account Metadata: Usage statistics, rate limit information, and tier data
- External Attackers: Attempting to access the proxy from outside the local network
- Local Malicious Software: Processes on the same machine trying to access stored tokens
- Supply Chain Attacks: Compromised dependencies or packages
- Insider Threats: Users with legitimate access misusing the system
- Network Exposure: Proxy accidentally exposed to public internet
- Database Access: Direct access to SQLite database file
- Token Theft: Extraction of OAuth tokens from storage or memory
- Request Interception: MITM attacks on API requests
- Log File Access: Unauthorized access to request/response logs
// packages/database/src/migrations.ts
CREATE TABLE IF NOT EXISTS accounts (
id TEXT PRIMARY KEY,
refresh_token TEXT NOT NULL, // Stored in plaintext
access_token TEXT, // Stored in plaintext
expires_at INTEGER
)Security Concern: Tokens are currently stored in plaintext in the SQLite database.
// packages/providers/src/providers/anthropic/oauth.ts
// Uses PKCE (Proof Key for Code Exchange) for enhanced security
generateAuthUrl(config: OAuthConfig, pkce: PKCEChallenge): string {
url.searchParams.set("code_challenge", pkce.challenge);
url.searchParams.set("code_challenge_method", "S256");
// ...
}
// Session-based OAuth flow with secure verifier storage
// packages/database/src/migrations.ts
CREATE TABLE IF NOT EXISTS oauth_sessions (
id TEXT PRIMARY KEY,
account_name TEXT NOT NULL,
verifier TEXT NOT NULL, // PKCE verifier stored securely
mode TEXT NOT NULL,
tier INTEGER DEFAULT 1,
created_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL // Auto-cleanup of expired sessions
)
// Scopes requested from Anthropic
scopes: ["org:create_api_key", "user:profile", "user:inference"]Security Strengths:
- Implements PKCE flow for protection against authorization code interception
- Uses SHA256 for code challenge generation
- Requests minimal necessary scopes
// packages/proxy/src/proxy.ts
async function refreshAccessTokenSafe(account: Account, ctx: ProxyContext): Promise<string> {
// Prevents token refresh stampede with in-flight tracking
if (!ctx.refreshInFlight.has(account.id)) {
const refreshPromise = ctx.provider.refreshToken(account, ctx.runtime.clientId)
.then((result: TokenRefreshResult) => {
ctx.dbOps.updateAccountTokens(account.id, result.accessToken, result.expiresAt);
return result.accessToken;
})
.finally(() => {
ctx.refreshInFlight.delete(account.id);
});
ctx.refreshInFlight.set(account.id, refreshPromise);
}
return ctx.refreshInFlight.get(account.id)!;
}Security Strengths:
- Implements stampede prevention to avoid multiple concurrent refresh attempts
- Automatic token rotation before expiry
- In-memory tracking of ongoing refresh operations
// Proposed implementation
interface EncryptedToken {
iv: string;
encryptedData: string;
authTag: string;
}
async function encryptToken(token: string, key: Buffer): Promise<EncryptedToken> {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([cipher.update(token, 'utf8'), cipher.final()]);
const authTag = cipher.getAuthTag();
return {
iv: iv.toString('base64'),
encryptedData: encrypted.toString('base64'),
authTag: authTag.toString('base64')
};
}- Use environment variable for encryption key:
ccflare_ENCRYPTION_KEY - Implement key derivation from master password
- Consider integration with OS keychain/credential store
- Implement automatic token rotation before expiry
- Add configurable rotation intervals
- Log rotation events for audit trail
The system implements sophisticated rate limit detection and handling:
// packages/providers/src/providers/anthropic/provider.ts
parseRateLimit(response: Response): RateLimitInfo {
const statusHeader = response.headers.get("anthropic-ratelimit-unified-status");
const resetHeader = response.headers.get("anthropic-ratelimit-unified-reset");
// Distinguishes between hard limits (blocking) and soft warnings
const isRateLimited = HARD_LIMIT_STATUSES.has(statusHeader || "") || response.status === 429;
return {
isRateLimited,
resetTime: resetHeader ? Number(resetHeader) * 1000 : undefined,
statusHeader: statusHeader || undefined,
remaining: remainingHeader ? Number(remainingHeader) : undefined
};
}- Account Quarantine: Rate-limited accounts are automatically excluded from rotation
- Reset Time Tracking: Precise tracking of when accounts become available again
- Soft vs Hard Limits: Differentiates between warnings and actual blocks
- Failover Strategy: Automatically tries next available account on rate limit
// apps/server/src/server.ts
const server = serve({
port: runtime.port, // Port 8080 by default
async fetch(req) {
// Handle requests
}
});Security Concern: The server binds to port 8080 on all interfaces (0.0.0.0) by default, potentially exposing it to the network.
Important: The server currently binds to all network interfaces. To secure the deployment:
# Use firewall rules to restrict access
sudo ufw allow from 127.0.0.1 to any port 8080
sudo ufw deny 8080
# Or use iptables
iptables -A INPUT -p tcp --dport 8080 -s 127.0.0.1 -j ACCEPT
iptables -A INPUT -p tcp --dport 8080 -j DROPRecommended Enhancement: Modify the server to support a HOST environment variable:
// Proposed server.ts modification
const server = serve({
port: runtime.port,
hostname: process.env.HOST || "0.0.0.0", // Allow binding configuration
async fetch(req) {
// Handle requests
}
});# Nginx configuration example
server {
listen 443 ssl http2;
server_name ccflare.internal;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Security headers
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
}
}- Use TLS termination at reverse proxy level
- Ensure strong cipher suites (TLS 1.2+)
- Implement HSTS headers
- Consider mutual TLS for additional security
// packages/proxy/src/proxy.ts
// Standard responses
const payload = {
request: {
headers: Object.fromEntries(req.headers.entries()),
body: requestBody ? "[streamed]" : null // Request bodies marked as streamed
},
response: {
status: response.status,
headers: Object.fromEntries(response.headers.entries()),
body: responseBody ? Buffer.from(responseBody).toString("base64") : null
}
};
// Streaming responses (current implementation)
// packages/proxy/src/response-handler.ts
if (isStream && response.body) {
// Clone response for background analytics consumption
const analyticsClone = response.clone();
(async () => {
try {
const reader = analyticsClone.body?.getReader();
if (!reader) return;
while (true) {
const { value, done } = await reader.read();
if (done) break;
if (value) {
// Send chunks to worker for processing
const chunkMsg: ChunkMessage = {
type: "chunk",
requestId,
data: value,
};
ctx.usageWorker.postMessage(chunkMsg);
}
}
} catch (err) {
// Handle errors...
}
})();
// Return original response untouched
return response;
}Privacy Concerns:
- Full request/response bodies are stored, potentially containing sensitive information
- Streaming responses are cloned and processed chunk by chunk in background workers
- Chunks are accumulated in memory without explicit size limits in the worker process
- Request bodies are encoded as base64 in logs
- Error payloads include full error details and request metadata
- Asynchronous writes may delay data persistence
- Base64 Encoding: Request/response bodies are Base64 encoded but not encrypted
- Database File Access: SQLite database file can be read by any process with file system access
- No Data Sanitization: Sensitive patterns (API keys, passwords, PII) are not redacted
- Unlimited Retention: No automatic cleanup of old request payloads
- User Prompts: May contain personal information, proprietary code, or confidential data
- API Keys: While not stored in payloads, they appear in logs
- Response Content: Claude's responses may echo back sensitive information
- No automatic log rotation or cleanup
- Request payloads stored indefinitely in SQLite database
- File logs written to disk without rotation
- Implement Log Rotation
// Proposed log rotation configuration
interface LogRotationConfig {
maxAge: number; // Days to retain logs
maxSize: number; // Max size per log file in MB
compress: boolean; // Compress old logs
deleteOnRotate: boolean; // Delete after rotation
}- Data Minimization
- Add option to disable request/response body logging
- Implement selective logging based on endpoint
- Add data redaction for sensitive patterns
- Cleanup Commands
# Add to CLI
bun cli cleanup --older-than 30d
bun cli cleanup --type requests --force- No authentication required: All endpoints are publicly accessible when network-reachable
- Dashboard: Accessible without authentication at
/dashboard - API endpoints: All
/api/*endpoints are unprotected - No CORS headers: The server does not set any CORS headers, effectively allowing requests from any origin
- No rate limiting: Individual clients can make unlimited requests to API endpoints
- Proxy endpoint: The
/v1/*proxy endpoint has no authentication (relies on OAuth tokens for upstream authentication)
- Data Exposure: Anyone with network access can view account information, request logs, and analytics
- Configuration Changes: Unprotected configuration endpoints allow unauthorized strategy changes
- Account Management: Account addition/removal endpoints are exposed
- Resource Exhaustion: No rate limiting can lead to DoS vulnerabilities
// Proposed middleware
async function authenticateRequest(req: Request): Promise<boolean> {
const apiKey = req.headers.get('X-API-Key');
if (!apiKey) return false;
const hashedKey = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(apiKey)
);
return timingSafeEqual(hashedKey, storedHashedKey);
}- Implement session-based authentication
- Add rate limiting on login attempts
- Consider OAuth integration for SSO
enum Permission {
VIEW_DASHBOARD = 'dashboard.view',
MANAGE_ACCOUNTS = 'accounts.manage',
VIEW_LOGS = 'logs.view',
MAKE_REQUESTS = 'api.request'
}
interface User {
id: string;
username: string;
permissions: Permission[];
}-
Network Configuration
- Bind server to localhost only
- Configure firewall rules
- Set up reverse proxy with TLS
- Disable unnecessary network services
-
Token Security
- Store encryption key securely (environment variable or secret manager)
- Implement token encryption at rest
- Regular token rotation schedule
- Monitor for token leaks in logs
-
Access Control
- Implement authentication for all endpoints
- Use strong, unique API keys
- Enable audit logging
- Regular access reviews
-
Data Protection
- Configure log rotation
- Implement data retention policies
- Regular database backups
- Encrypt sensitive backups
-
Monitoring
- Set up alerts for suspicious activity
- Monitor rate limit patterns
- Track authentication failures
- Regular security audits
-
Dependency Management
- Regular dependency updates:
bun update - Security audit:
bun audit - Lock file verification
- Supply chain security checks
- Regular dependency updates:
-
Code Security
- Input validation on all endpoints
- Output encoding for web responses
- Parameterized database queries (already implemented)
- Secure random number generation for IDs
-
Error Handling
- Avoid exposing stack traces in production
- Generic error messages to users
- Detailed error logging internally
- Rate limit error responses
- Discovery: If you discover a security vulnerability, please report it responsibly
- Contact: Email security concerns to the project maintainers
- Information to Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fixes (if any)
- Acknowledgment: Within 48 hours
- Initial Assessment: Within 7 days
- Fix Development: Based on severity
- Disclosure: Coordinated with reporter
- Critical: Token exposure, RCE, authentication bypass
- High: Data exposure, privilege escalation
- Medium: Information disclosure, DoS
- Low: Minor information leaks
Risk: Running ccflare with default settings exposes it to the network Mitigation: Always bind to localhost in development
Risk: OAuth tokens appearing in debug logs Mitigation: Implement log sanitization, never log full tokens
Risk: Multiple users accessing the same SQLite database Mitigation: Implement proper file permissions, consider client/server database
Risk: Database backups containing plaintext tokens Mitigation: Encrypt backups, secure backup storage
Risk: Single client overwhelming the proxy Mitigation: Implement per-client rate limiting
Risk: Dashboard API accessible from unauthorized origins (currently no CORS headers are set) Mitigation: Implement CORS headers:
// Recommended implementation in server.ts or API router
function addSecurityHeaders(response: Response): Response {
const headers = new Headers(response.headers);
headers.set('Access-Control-Allow-Origin', process.env.ALLOWED_ORIGINS || 'http://localhost:8080');
headers.set('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
headers.set('Access-Control-Allow-Headers', 'Content-Type, X-API-Key');
headers.set('Access-Control-Max-Age', '86400');
headers.set('X-Content-Type-Options', 'nosniff');
headers.set('X-Frame-Options', 'DENY');
headers.set('X-XSS-Protection', '1; mode=block');
headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers
});
}Risk: Known vulnerabilities in dependencies Mitigation: Regular updates, security scanning
Risk: Predictable IDs or tokens Mitigation: Use crypto.randomUUID() and crypto.getRandomValues()
Risk: Large streaming responses consuming excessive memory/storage Mitigation: Implement size limits in worker chunk accumulation; monitor memory usage for large streams
Risk: Data loss if application crashes before async writes complete Mitigation: Graceful shutdown handlers ensure queue is flushed
- Change: Added
sanitizeProxyHeadersutility function - Security Benefit: Removes hop-by-hop headers (content-encoding, content-length, transfer-encoding) to prevent header injection attacks
- Implementation: Applied in Anthropic provider's
prepareProxyResponsemethod
- Change: Streaming responses are cloned and processed in background workers
- Security Consideration: Chunks are accumulated in memory without explicit size limits, though processed incrementally
- Implementation: Uses Response.clone() to avoid blocking the original stream
- Recommendation: Implement memory monitoring and chunk size limits in worker
- Change: Migrated from direct account creation to session-based OAuth endpoints
- Security Benefit: Improved PKCE flow with session management
- Implementation: Stores verifier securely in oauth_sessions table with expiration
- Feature: Added ability to override model selection based on agent preferences
- Security Consideration: Model modifications are tracked in request metadata
- Implementation: Intercepts and modifies request body before proxying
- Change: Introduced AsyncDbWriter for non-blocking database operations
- Security Consideration: Ensures request payloads are persisted even under high load
- Implementation: Queue-based system with graceful shutdown handling
- Feature: System can operate without any configured accounts
- Security Implication: Requests are forwarded to Claude API without authentication
- Use Case: Testing or environments where users provide their own API keys
- Implement API key authentication middleware
- Add rate limiting per client/IP
- Implement CORS headers with proper origin restrictions
- Add audit logging for all API access
- Implement AES-256-GCM encryption for stored tokens
- Add key management system (environment variable or OS keychain)
- Migration tool for existing plaintext tokens
- Secure key rotation mechanism
- Add HOST binding configuration (localhost by default)
- TLS support in proxy server
- Certificate pinning for API calls
- IP allowlisting capability
- Implement streaming response size limits
- Add memory monitoring for worker processes
- Request body size validation
- Database size management and rotation
- Hardware security module (HSM) integration
- Multi-factor authentication
- Anomaly detection system
- Security scanning integration
# Logging and Debugging
LOG_LEVEL=INFO # Set to ERROR in production
LOG_FORMAT=json # Use json for structured logging
ccflare_DEBUG=0 # Set to 1 only for debugging
# Configuration
ccflare_CONFIG_PATH=/path/to/config.json # Custom config location
CLIENT_ID=your-client-id # OAuth client ID
# Server Configuration
PORT=8080 # Server port
LB_STRATEGY=session # Load balancing strategy
# Retry Configuration
RETRY_ATTEMPTS=3 # Number of retry attempts
RETRY_DELAY_MS=1000 # Initial retry delay
RETRY_BACKOFF=2 # Backoff multiplier
SESSION_DURATION_MS=18000000 # Session duration (5 hours)- Never commit
.envfiles containing sensitive values - Use secret management tools in production (e.g., HashiCorp Vault, AWS Secrets Manager)
- Restrict file permissions on environment files:
chmod 600 .env - Audit environment access in containerized deployments
- All API requests are logged with timestamps and response codes
- Request/response payloads are stored for analysis
- Account usage and rate limit events are tracked
- Error conditions are logged with details
-
Access Patterns
- Monitor for unusual request volumes
- Track access from unexpected IP addresses
- Detect repeated failed requests
- Watch for configuration changes
-
Token Usage
- Monitor token refresh frequency
- Detect unusual account switching patterns
- Track rate limit exhaustion events
- Alert on authentication failures
-
System Health
- Database size growth
- Memory usage patterns
- Response time anomalies
- Error rate spikes
# Example monitoring queries
# Find requests from non-localhost IPs (requires reverse proxy logs)
grep -v "127.0.0.1\|::1" access.log
# Monitor for high request volumes
sqlite3 ccflare.db "SELECT COUNT(*) as count, account_used
FROM requests
WHERE timestamp > strftime('%s', 'now', '-1 hour') * 1000
GROUP BY account_used
ORDER BY count DESC"
# Check for configuration changes
sqlite3 ccflare.db "SELECT * FROM audit_log WHERE action LIKE '%config%'"-
Suspected Token Compromise
- Immediately pause affected accounts via API
- Rotate OAuth tokens through Anthropic console
- Review request logs for unauthorized usage
- Update tokens in ccflare
-
Unauthorized Access
- Implement firewall rules immediately
- Review all recent API requests
- Check for data exfiltration
- Consider rotating all tokens
-
Rate Limit Abuse
- Identify source of excessive requests
- Implement IP-based blocking
- Review load balancing strategy
- Consider implementing request queuing
- Dependency Audit
# Check for known vulnerabilities in dependencies
bun audit
# Update dependencies to latest secure versions
bun update- Code Security Analysis
# Run linting with security rules
bun run lint
# Type checking can catch security issues
bun run typecheck- Manual Security Checklist
- Verify no hardcoded credentials in code
- Check for exposed sensitive endpoints
- Review error messages for information leakage
- Test rate limiting effectiveness
- Verify token rotation works correctly
- Check database file permissions
- Review log files for sensitive data
# Test unauthorized access (should fail in secured setup)
curl http://localhost:8080/api/accounts
# Test CORS headers (should be restricted)
curl -H "Origin: http://evil.com" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: X-Requested-With" \
-X OPTIONS \
http://localhost:8080/api/accounts
# Check for exposed internal headers
curl -I http://localhost:8080/api/healthSecurity is an ongoing process. This documentation should be reviewed and updated regularly as the system evolves and new threats emerge. All contributors should familiarize themselves with these security considerations and follow the best practices outlined above.
- ccflare prioritizes functionality over security - suitable for development, not production
- Network isolation is critical - always restrict access to trusted networks
- Token security requires enhancement - implement encryption for production use
- Authentication is missing - all endpoints are currently public
- Monitoring is essential - regular review of logs can detect security issues early
- Regular updates needed - keep dependencies and documentation current
- Implement authentication middleware before exposing to any network
- Bind server to localhost only
- Set up reverse proxy with TLS
- Encrypt OAuth tokens in database
- Implement rate limiting
- Add security headers (CORS, CSP, etc.)
For security-related questions or concerns, please refer to the vulnerability disclosure process or contact the project maintainers directly.