refactor: comprehensive code review fixes (security, performance, QA)
## Security Improvements - Fix timing attack in verifyApiKey with fixed 256-byte buffer - Fix sortOrder SQL injection with whitelist validation - Fix rate limiting bypass for non-Cloudflare traffic (fail-closed) - Remove stack trace exposure in error responses - Add request_id for audit trail (X-Request-ID header) - Sanitize origin header to prevent log injection - Add content-length validation for /sync endpoint (10KB limit) - Replace Math.random() with crypto.randomUUID() for sync IDs - Expand sensitive data masking patterns (8 → 18) ## Performance Improvements - Reduce rate limiter KV reads from 3 to 1 per request (66% reduction) - Increase sync batch size from 100 to 500 (80% fewer batches) - Fix health check N+1 query with efficient JOINs - Fix COUNT(*) Cartesian product with COUNT(DISTINCT) - Implement shared logger cache pattern across repositories - Add CacheService singleton pattern in recommend.ts - Add composite index for recommendation queries - Implement Anvil pricing query batching (100 per chunk) ## QA Improvements - Add BATCH_SIZE bounds validation (1-1000) - Add pagination bounds (page >= 1, MAX_OFFSET = 100000) - Add min/max range consistency validation - Add DB reference validation for singleton services - Add type guards for database result validation - Add timeout mechanism for external API calls (10-60s) - Use SUPPORTED_PROVIDERS constant instead of hardcoded list ## Removed - Remove Vault integration (using Wrangler secrets) - Remove 6-hour pricing cron (daily sync only) ## Configuration - Add idx_instance_types_specs_filter composite index - Add CORS Access-Control-Expose-Headers Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,15 +32,19 @@ import {
|
||||
*/
|
||||
let cachedQueryService: QueryService | null = null;
|
||||
let cachedCacheService: CacheService | null = null;
|
||||
let cachedDb: D1Database | null = null;
|
||||
|
||||
/**
|
||||
* Get or create QueryService singleton
|
||||
* Lazy initialization on first request, then reused for subsequent requests
|
||||
* Invalidates cache if database binding changes (rolling deploy scenario)
|
||||
*/
|
||||
function getQueryService(db: D1Database, env: Env): QueryService {
|
||||
if (!cachedQueryService) {
|
||||
// Invalidate cache if db binding changed (rolling deploy scenario)
|
||||
if (!cachedQueryService || cachedDb !== db) {
|
||||
cachedQueryService = new QueryService(db, env);
|
||||
logger.debug('[Instances] QueryService singleton initialized');
|
||||
cachedDb = db;
|
||||
logger.debug('[Instances] QueryService singleton initialized/refreshed');
|
||||
}
|
||||
return cachedQueryService;
|
||||
}
|
||||
@@ -288,6 +292,31 @@ function parseQueryParams(url: URL): {
|
||||
params.offset = offset;
|
||||
}
|
||||
|
||||
// Range consistency validation
|
||||
if (params.min_vcpu !== undefined && params.max_vcpu !== undefined) {
|
||||
if (params.min_vcpu > params.max_vcpu) {
|
||||
return {
|
||||
error: {
|
||||
code: 'INVALID_RANGE',
|
||||
message: 'min_vcpu cannot be greater than max_vcpu',
|
||||
parameter: 'min_vcpu',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (params.min_memory_gb !== undefined && params.max_memory_gb !== undefined) {
|
||||
if (params.min_memory_gb > params.max_memory_gb) {
|
||||
return {
|
||||
error: {
|
||||
code: 'INVALID_RANGE',
|
||||
message: 'min_memory_gb cannot be greater than max_memory_gb',
|
||||
parameter: 'min_memory_gb',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { params };
|
||||
}
|
||||
|
||||
@@ -463,8 +492,8 @@ export async function handleInstances(
|
||||
success: false,
|
||||
error: {
|
||||
code: 'QUERY_FAILED',
|
||||
message: 'Instance query failed',
|
||||
details: error instanceof Error ? error.message : 'Unknown error',
|
||||
message: 'Instance query failed. Please try again later.',
|
||||
request_id: crypto.randomUUID(),
|
||||
},
|
||||
},
|
||||
{ status: HTTP_STATUS.INTERNAL_ERROR }
|
||||
|
||||
Reference in New Issue
Block a user