refactor: comprehensive code review fixes and security hardening
Security: - Add CSP headers for HTML reports (style-src 'unsafe-inline') - Restrict origin validation to specific .kappa-d8e.workers.dev domain - Add base64 size limit (100KB) for report data parameter - Implement rejection sampling for unbiased password generation - Add SQL LIKE pattern escaping for tech specs query - Add security warning for plaintext password storage (TODO: encrypt) Performance: - Add Telegram API timeout (10s) with AbortController - Fix rate limiter sorting by resetTime for proper cleanup - Use centralized TIMEOUTS config for VPS provider APIs Features: - Add admin SSH key support for server recovery access - ADMIN_SSH_PUBLIC_KEY for Linode (public key string) - ADMIN_SSH_KEY_ID_VULTR for Vultr (pre-registered key ID) - Add origin validation middleware - Add idempotency key migration Code Quality: - Return 404 status when no servers found - Consolidate error logging to single JSON.stringify call - Import TECH_CATEGORY_WEIGHTS from config.ts - Add escapeLikePattern utility function Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,18 @@ export function generateCacheKey(req: RecommendRequest): string {
|
||||
|
||||
// In-memory fallback for rate limiting when CACHE KV is unavailable
|
||||
const inMemoryRateLimit = new Map<string, { count: number; resetTime: number }>();
|
||||
const MAX_IN_MEMORY_ENTRIES = 10000;
|
||||
|
||||
/**
|
||||
* Clean up expired entries from in-memory rate limit map
|
||||
*/
|
||||
function cleanupExpiredEntries(now: number): void {
|
||||
for (const [key, record] of inMemoryRateLimit) {
|
||||
if (record.resetTime < now) {
|
||||
inMemoryRateLimit.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rate limiting check using KV storage with in-memory fallback
|
||||
@@ -94,6 +106,18 @@ export async function checkRateLimit(clientIP: string, env: Env): Promise<{ allo
|
||||
|
||||
// Use in-memory fallback if CACHE unavailable
|
||||
if (!env.CACHE) {
|
||||
// Cleanup expired entries if map is getting too large
|
||||
if (inMemoryRateLimit.size >= MAX_IN_MEMORY_ENTRIES) {
|
||||
cleanupExpiredEntries(now);
|
||||
// If still too large after cleanup, remove oldest 10% by resetTime
|
||||
if (inMemoryRateLimit.size >= MAX_IN_MEMORY_ENTRIES) {
|
||||
const entries = Array.from(inMemoryRateLimit.entries())
|
||||
.sort((a, b) => a[1].resetTime - b[1].resetTime)
|
||||
.slice(0, Math.floor(MAX_IN_MEMORY_ENTRIES * 0.1));
|
||||
entries.forEach(([key]) => inMemoryRateLimit.delete(key));
|
||||
}
|
||||
}
|
||||
|
||||
const record = inMemoryRateLimit.get(clientIP);
|
||||
|
||||
if (!record || record.resetTime < now) {
|
||||
|
||||
@@ -164,6 +164,20 @@ export function validateRecommendRequest(body: unknown, lang: string = 'en'): Va
|
||||
}
|
||||
}
|
||||
|
||||
// Validate cdn_enabled if provided
|
||||
if (req.cdn_enabled !== undefined && typeof req.cdn_enabled !== 'boolean') {
|
||||
invalidFields.push({ field: 'cdn_enabled', reason: 'must be a boolean' });
|
||||
}
|
||||
|
||||
// Validate cdn_cache_hit_rate if provided
|
||||
if (req.cdn_cache_hit_rate !== undefined) {
|
||||
if (typeof req.cdn_cache_hit_rate !== 'number') {
|
||||
invalidFields.push({ field: 'cdn_cache_hit_rate', reason: 'must be a number' });
|
||||
} else if (req.cdn_cache_hit_rate < 0 || req.cdn_cache_hit_rate > 1) {
|
||||
invalidFields.push({ field: 'cdn_cache_hit_rate', reason: 'must be between 0.0 and 1.0' });
|
||||
}
|
||||
}
|
||||
|
||||
// Return error if any issues found
|
||||
if (missingFields.length > 0 || invalidFields.length > 0) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user