Skip to content

Commit 59a8262

Browse files
committed
fix(security): add CSWSH protection, header redaction, and patch CVEs
Security Improvements: - Add WebSocket Origin validation to prevent Cross-Site WebSocket Hijacking - Implement header sanitization with token/cookie redaction for logging - Add security event logging for attack detection (invalid origin, auth failures, rate limits, oversized messages, disallowed field updates) Dependency Patches: - hono 4.9.6 → 4.10.6 (JWT audience + Vary header CVEs) - glob 10.4.5 → 10.5.0 (CLI command injection CVE) - js-yaml 4.1.0 → 4.1.1 (prototype pollution CVE) [override] - esbuild 0.18.20 → 0.25.9 (dev server CORS CVE) [override] Other Changes: - Improve auth middleware error handling and logging - Simplify OTEL logs setup (HTTP exporter for Alloy compatibility) - Add request headers to Grafana logs (with sensitive values redacted) - Standardize log message prefixes ([API], [WS], [CACHE], [AUTH], etc.)
1 parent 129aad2 commit 59a8262

File tree

22 files changed

+1223
-1060
lines changed

22 files changed

+1223
-1060
lines changed

.env.example

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ API_URL=http://localhost:3000
5656
# SECURITY CONFIGURATION
5757
# ================================================================
5858

59-
# WebSocket Message Authentication
59+
# Message Authentication Secret
6060
# Generate with: openssl rand -hex 32
6161
# CRITICAL: Use a strong random string (minimum 32 characters) in production
6262
MESSAGE_AUTH_SECRET=your-very-secure-random-string-here-min-32-chars
@@ -72,17 +72,9 @@ MESSAGE_AUTH_SECRET=your-very-secure-random-string-here-min-32-chars
7272
# HTTP_RATE_LIMIT_MAX_REQUESTS=1000 # Max requests per window per user/IP
7373
# HTTP_FILE_RATE_LIMIT_MAX=100 # Max file operations per window
7474

75-
# WebSocket Rate Limiting
76-
WS_RATE_LIMIT_WINDOW_MS=60000# Time window: 1 minute (in milliseconds)
77-
WS_RATE_LIMIT_MAX_MESSAGES=300# Max messages per window per connection
78-
WS_MAX_CONNECTIONS_PER_USER=20# Max concurrent connections per user
79-
WS_AUTH_TIMEOUT_MS=30000# Authentication timeout: 30 seconds
80-
8175
# Production Rate Limiting Recommendations:
8276
# - HTTP: 500-1000 requests per 15 minutes per user
83-
# - WebSocket: 100-300 messages per minute per connection
8477
# - File uploads: 50-100 operations per 15 minutes per user
85-
# - Connections: 10-20 per user depending on multi-device usage
8678

8779
# ================================================================
8880
# FILE UPLOAD CONFIGURATION
@@ -98,15 +90,15 @@ MAX_FILE_SIZE_MB=50# Maximum size per file (default: 50MB)
9890
# Add new types in: src/lib/validation.ts
9991

10092
# ================================================================
101-
# CODE EXECUTION CONFIGURATION (Judge0 API)
93+
# CODE EXECUTION CONFIGURATION (Self-hosted Piston)
10294
# ================================================================
10395

104-
# Judge0 API Configuration (OPTIONAL - for code execution features)
105-
# Get your API key from: https://rapidapi.com/judge0-official/api/judge0-ce
106-
# JUDGE0_API_KEY=your_judge0_rapidapi_key_here
96+
# Piston API Configuration (OPTIONAL - for code execution features)
97+
# Self-hosted Piston: https://github.com/engineer-man/piston
98+
# Run with Docker: docker run -d --name piston -p 2000:2000 ghcr.io/engineer-man/piston
99+
# PISTON_API_URL=http://localhost:2000
107100

108101
# Code execution rate limits
109-
# Adjust based on your Judge0 plan limits (defaults: development=200, production=50)
110102
# CODE_EXEC_RATE_LIMIT_MAX=50 # Max code executions per 15 minutes per user
111103

112104
# ================================================================
@@ -132,24 +124,33 @@ FREE_TIER_NOTE_LIMIT=100# Note count limit for free users (default: 100)
132124
# 3. Copy the export commands provided (they will look like the examples below)
133125
# 4. Set the environment variables in your deployment
134126

135-
# OpenTelemetry Configuration (use values from Grafana Cloud setup)
136-
# Copy the export commands from Grafana Cloud OpenTelemetry setup page
137-
# OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-prod-<region>.grafana.net/otlp
138-
# OTEL_EXPORTER_OTLP_HEADERS=Authorization=Basic%20<base64-encoded-token>
127+
# OpenTelemetry Configuration (via Grafana Alloy)
128+
# IMPORTANT: Logs/traces/metrics are sent to local Grafana Alloy, which forwards to Grafana Cloud
129+
# For local dev: docker-compose -f docker-compose.alloy.yml up -d
130+
# For production: Run Alloy as ECS sidecar container (see docker-compose.alloy.yml)
131+
132+
# OTEL Configuration - Point to Alloy (localhost for both dev and prod sidecar)
133+
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
134+
# OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
139135
# OTEL_TRACES_EXPORTER=otlp
140136
# OTEL_METRICS_EXPORTER=otlp
141137
# OTEL_LOGS_EXPORTER=otlp
142138
# OTEL_NODE_RESOURCE_DETECTORS=env,host,os
143139
# OTEL_SERVICE_NAME=typelets-api
144-
# OTEL_RESOURCE_ATTRIBUTES=service.name=typelets-api,deployment.environment=production
140+
# OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.name=typelets-api
141+
142+
# Grafana Cloud Configuration (required for Alloy container, NOT in app)
143+
# Get these from Grafana Cloud: Connections > Add new connection > OpenTelemetry
144+
# GRAFANA_CLOUD_ENDPOINT=your_otlp_gateway_endpoint_here
145+
# GRAFANA_CLOUD_INSTANCE_ID=your_instance_id_here
146+
# GRAFANA_CLOUD_TOKEN=your_grafana_cloud_token_here
145147

146148
# Application Logging
147149
# Structured JSON logs are automatically generated for:
148150
# - Authentication events
149151
# - Rate limiting violations
150152
# - Security events (failed auth, suspicious activity)
151153
# - Billing limit violations
152-
# - WebSocket connection events
153154
# - File upload events
154155
# - HTTP requests and responses
155156
# - Database queries
@@ -178,7 +179,6 @@ FREE_TIER_NOTE_LIMIT=100# Note count limit for free users (default: 100)
178179

179180
# API Testing
180181
# Health check: GET http://localhost:3000/health
181-
# WebSocket status: GET http://localhost:3000/websocket/status
182182
# API documentation: https://github.com/typelets/typelets-api
183183

184184
# ================================================================
@@ -188,7 +188,7 @@ FREE_TIER_NOTE_LIMIT=100# Note count limit for free users (default: 100)
188188
# 🔒 NEVER commit actual secrets to version control
189189
# 🔒 Use different secrets for development and production
190190
# 🔒 Rotate secrets regularly in production
191-
# 🔒 MESSAGE_AUTH_SECRET is critical for WebSocket security
191+
# 🔒 MESSAGE_AUTH_SECRET is critical for message authentication
192192
# 🔒 CLERK_SECRET_KEY controls all authentication
193193
# 🔒 DATABASE_URL contains database credentials
194194

@@ -197,12 +197,11 @@ FREE_TIER_NOTE_LIMIT=100# Note count limit for free users (default: 100)
197197
# API Keys: Use your service provider's dashboard
198198
# Database passwords: Use password managers
199199

200-
# Judge0 API Key Setup:
201-
# 1. Go to https://rapidapi.com/judge0-official/api/judge0-ce
202-
# 2. Sign up/login to RapidAPI
203-
# 3. Subscribe to Judge0 CE (free tier available)
204-
# 4. Copy your API key and uncomment JUDGE0_API_KEY above
205-
# 5. Code execution endpoints will be available: /api/code/*
200+
# Piston Setup (Self-hosted code execution):
201+
# 1. Run Piston with Docker: docker run -d --name piston -p 2000:2000 ghcr.io/engineer-man/piston
202+
# 2. Set PISTON_API_URL=http://localhost:2000
203+
# 3. Code execution endpoints will be available: /api/code/*
204+
# See: https://github.com/engineer-man/piston
206205

207206
# ================================================================
208207
# PRODUCTION DEPLOYMENT NOTES
@@ -234,7 +233,6 @@ FREE_TIER_NOTE_LIMIT=100# Note count limit for free users (default: 100)
234233
# 2. "Authentication failed" → Verify CLERK_SECRET_KEY is correct
235234
# 3. "CORS error" → Add your frontend URL to CORS_ORIGINS
236235
# 4. "Too many requests" → Increase rate limits or check for loops
237-
# 5. "WebSocket connection failed" → Check authentication and rate limits
238236

239237
# Useful Commands:
240238
# Check database connection: npx drizzle-kit studio

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,7 @@ deploy.local.ps1
8181
*.local.bat
8282
*.local.ps1
8383
deployment-scripts/
84+
85+
# ECS task definitions - contain secrets, DO NOT COMMIT
86+
ecs-task-definition*.json
87+
*task-definition*.json

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ RUN pnpm install --frozen-lockfile --prod
3838

3939
# Copy built application from builder stage
4040
COPY --from=builder /app/dist ./dist
41-
# Application configuration complete
41+
42+
# Copy instrumentation.js (needed for OTEL auto-instrumentation)
43+
COPY --from=builder /app/instrumentation.js ./instrumentation.js
4244

4345
# Change ownership to non-root user
4446
RUN chown -R typelets:nodejs /app

Dockerfile.alloy

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Grafana Alloy with embedded configuration for ECS
2+
FROM grafana/alloy:latest
3+
4+
# Copy the Alloy configuration into the image
5+
COPY alloy-config.alloy /etc/alloy/config.alloy
6+
7+
# Default command (can be overridden in ECS task definition)
8+
CMD ["run", "/etc/alloy/config.alloy", "--server.http.listen-addr=0.0.0.0:12345"]

0 commit comments

Comments
 (0)