Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@
"WebFetch(domain:docs.stripe.com)",
"Bash(bun info:*)",
"Bash(ss:*)",
"Bash(git config:*)"
"Bash(git config:*)",
"Bash(git fetch:*)",
"Bash(timeout 30 bun run build)"
],
"deny": []
}
Expand Down
62 changes: 46 additions & 16 deletions api-dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,39 +206,54 @@

// In development, provide more details (but still sanitized)
if (isDevelopment && CONFIG.NODE_ENV === 'development') {
// Remove sensitive paths and internal details but keep useful info
// Remove ALL sensitive information including paths, stack traces, and internal details
const sanitized = errorMessage
.replace(/\/[^/\s]*\/([^/\s]*\/)*[^/\s]*(\.(js|ts|json))/g, '[FILE_PATH]')
.replace(/\/[^/\s]*\/([^/\s]*\/)*[^/\s]*(\.(js|ts|json|jsx|tsx|mjs|cjs))/g, '[FILE_PATH]')

Check failure

Code scanning / ESLint

Detects potentially unsafe regular expressions, which may take a very long time to run, blocking the event loop. Error

Unsafe Regular Expression
.replace(/at\s+[^\s]+\s+\([^)]+\)/g, '[STACK_TRACE]')
.replace(/\/home\/[^/\s]*\/[^\s]*\s*/g, '[USER_PATH]')
.replace(/\/Users\/[^/\s]*\/[^\s]*\s*/g, '[USER_PATH]')
.replace(/\/var\/[^\s]*\s*/g, '[SYSTEM_PATH]')
.replace(/\/tmp\/[^\s]*\s*/g, '[TEMP_PATH]')
.replace(/process\.env\.[A-Z_]+/g, '[ENV_VAR]')
.replace(/password[:=][^\s]*/gi, 'password=[REDACTED]')
.replace(/token[:=][^\s]*/gi, 'token=[REDACTED]')
.replace(/key[:=][^\s]*/gi, 'key=[REDACTED]')
.replace(/secret[:=][^\s]*/gi, 'secret=[REDACTED]')
.replace(/ENOENT.*'/g, 'File not found')
.replace(/EACCES.*'/g, 'Permission denied')
.replace(/Error:\s*/g, '');

return {
error: sanitized.length > 200 ? sanitized.substring(0, 200) + '...' : sanitized,
error: sanitized.length > 150 ? sanitized.substring(0, 150) + '...' : sanitized,
code: 'DEVELOPMENT_ERROR'
};
}

// In production, return generic errors for security
// In production, return only generic errors for maximum security
const commonErrors: Record<string, string> = {
'Request timeout': 'Request timed out',
'Invalid API handler export': 'Service temporarily unavailable',
'ENOENT': 'Resource not found',
'EACCES': 'Access denied',
'ETIMEDOUT': 'Request timed out',
'ECONNRESET': 'Connection interrupted'
'ECONNRESET': 'Connection interrupted',
'Module not found': 'Service temporarily unavailable',
'Cannot resolve module': 'Service temporarily unavailable',
'Permission denied': 'Access denied',
'File not found': 'Resource not found'
};

// Check for known error patterns
// Check for known error patterns (case insensitive)
const errorLower = errorMessage.toLowerCase();
for (const [pattern, safeMessage] of Object.entries(commonErrors)) {
if (errorMessage.includes(pattern)) {
return { error: safeMessage };
if (errorLower.includes(pattern.toLowerCase())) {
return { error: safeMessage, code: 'SERVICE_ERROR' };
}
}

// Default safe error message
return { error: 'Internal server error' };
// For ALL other production errors, return completely generic message
// This prevents ANY internal information leakage
return { error: 'Service temporarily unavailable', code: 'GENERIC_ERROR' };
}

// Rate limiting with secure IP validation and bounded storage
Expand Down Expand Up @@ -642,16 +657,31 @@

// Production-Ready Server with PostHog Analytics
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
// Enhanced CORS (only allow credentialed requests if origin matches sanitized whitelist)
// Enhanced CORS with secure credentials handling
const origin = req.headers.origin;
// Only allow origins that are explicitly whitelisted and valid
const allowedOrigin = origin && CONFIG.CORS_ORIGINS.includes(origin) ? origin : null;

if (allowedOrigin) {
res.setHeader('Access-Control-Allow-Origin', allowedOrigin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
// Define trusted origins that can use credentials
const TRUSTED_ORIGINS_FOR_CREDENTIALS = [
'http://localhost:3000',
'http://localhost:5173',
'http://localhost:8080',
'https://zapdev.vercel.app',
'https://zapdev.link'
];

// Only allow credentialed requests from explicitly trusted origins
const isTrustedOrigin = origin && TRUSTED_ORIGINS_FOR_CREDENTIALS.includes(origin);
const isAllowedOrigin = origin && CONFIG.CORS_ORIGINS.includes(origin);

if (isAllowedOrigin) {
res.setHeader('Access-Control-Allow-Origin', origin);

Check failure

Code scanning / CodeQL

CORS misconfiguration for credentials transfer High

Credential
leak vulnerability due to a
misconfigured CORS header value
.
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');

// Only enable credentials for trusted origins to prevent credential hijacking
if (isTrustedOrigin) {
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
}

if (req.method === 'OPTIONS') {
Expand Down
35 changes: 23 additions & 12 deletions convex/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,19 +149,30 @@ const sanitizeMetadata = (metadata: unknown) => {
throw new Error('Diagram text too long (maximum 10,000 characters)');
}

// Reject disallowed patterns
// Reject disallowed patterns using safer regex patterns
const disallowedPatterns = [
/<script[^>]*>.*?<\/script>/gi, // Script tags (safer pattern)
/<[^>]*on\w+\s*=/gi, // Event handlers (onclick, etc.)
/data:\s*[^;]*;base64/gi, // Data URLs
/https?:\/\/[^\s]+/gi, // HTTP(S) URLs
/javascript:/gi, // JavaScript protocol
/vbscript:/gi, // VBScript protocol
/<embed[^>]*>/gi, // Embed tags (safer pattern)
/<object[^>]*>/gi, // Object tags (safer pattern)
/<iframe[^>]*>/gi, // Iframe tags (safer pattern)
/@@\w+/gi, // Potential template injection
/\$\{\w+\}/gi, // Template literals
// Script tag detection (simplified for security)
/<script\b[^>]*>/gi,
// Event handlers (safer pattern)
/<[^>]*\son\w+\s*=/gi,
// Data URLs
/data:\s*[^;]*;base64/gi,
// HTTP(S) URLs
/https?:\/\/[^\s<>"']+/gi,
// JavaScript protocol
/javascript:\s*[^"'\s]*/gi,
// VBScript protocol
/vbscript:\s*[^"'\s]*/gi,
// Embed tags
/<embed\b[^>]*>/gi,
// Object tags
/<object\b[^>]*>/gi,
// Iframe tags
/<iframe\b[^>]*>/gi,
// Template injection patterns
/@@\w+/gi,
// Template literals
/\$\{\w+\}/gi,
];

for (const pattern of disallowedPatterns) {
Expand Down
Loading