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:
@@ -1,5 +1,4 @@
|
||||
import type { Env, RegionInput, InstanceTypeInput, InstanceFamily, GpuInstanceInput } from '../types';
|
||||
import { VaultClient, VaultError } from './vault';
|
||||
import { RateLimiter } from './base';
|
||||
import { createLogger } from '../utils/logger';
|
||||
import { HTTP_STATUS } from '../constants';
|
||||
@@ -53,18 +52,18 @@ interface VultrApiResponse<T> {
|
||||
* - Rate limiting: 3000 requests/hour
|
||||
* - Data normalization for database storage
|
||||
* - Comprehensive error handling
|
||||
* - Vault integration for credentials
|
||||
* - Credentials from environment variables
|
||||
*
|
||||
* @example
|
||||
* const vault = new VaultClient(vaultUrl, vaultToken);
|
||||
* const connector = new VultrConnector(vault);
|
||||
* const connector = new VultrConnector(env);
|
||||
* await connector.initialize();
|
||||
* const regions = await connector.fetchRegions();
|
||||
*
|
||||
* @example
|
||||
* // Using custom relay URL
|
||||
* const connector = new VultrConnector(vault, 'https://custom-relay.example.com');
|
||||
* const connector = new VultrConnector(env, 'https://custom-relay.example.com');
|
||||
*
|
||||
* @param vaultClient - Vault client for credential management
|
||||
* @param env - Environment with credentials
|
||||
* @param relayUrl - Optional relay server URL (defaults to 'https://vultr-relay.anvil.it.com')
|
||||
*/
|
||||
export class VultrConnector {
|
||||
@@ -76,9 +75,8 @@ export class VultrConnector {
|
||||
private apiKey: string | null = null;
|
||||
|
||||
constructor(
|
||||
private vaultClient: VaultClient,
|
||||
relayUrl?: string,
|
||||
env?: Env
|
||||
private env: Env,
|
||||
relayUrl?: string
|
||||
) {
|
||||
// Use relay server by default, allow override via parameter or environment variable
|
||||
// Relay server mirrors Vultr API structure: /v2/regions, /v2/plans
|
||||
@@ -92,36 +90,21 @@ export class VultrConnector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize connector by fetching credentials from Vault
|
||||
* Initialize connector by loading credentials from environment
|
||||
* Must be called before making API requests
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
this.logger.info('Fetching credentials from Vault');
|
||||
this.logger.info('Loading credentials from environment');
|
||||
|
||||
try {
|
||||
const credentials = await this.vaultClient.getCredentials(this.provider);
|
||||
|
||||
// Vultr uses 'api_key' field (unlike Linode which uses 'api_token')
|
||||
const apiKey = credentials.api_key || null;
|
||||
|
||||
if (!apiKey || apiKey.trim() === '') {
|
||||
throw new VultrError(
|
||||
'Vultr API key is missing or empty. Please configure api_key in Vault.',
|
||||
HTTP_STATUS.INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
this.apiKey = apiKey;
|
||||
this.logger.info('Credentials loaded successfully');
|
||||
} catch (error) {
|
||||
if (error instanceof VaultError) {
|
||||
throw new VultrError(
|
||||
`Failed to load Vultr credentials: ${error.message}`,
|
||||
error.statusCode
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
if (!this.env.VULTR_API_KEY) {
|
||||
throw new VultrError(
|
||||
'VULTR_API_KEY not found in environment',
|
||||
HTTP_STATUS.INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
this.apiKey = this.env.VULTR_API_KEY;
|
||||
this.logger.info('Credentials loaded successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user