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:
kappa
2026-01-25 23:50:37 +09:00
parent 9f3d3a245a
commit 3a8dd705e6
47 changed files with 2031 additions and 2459 deletions

View File

@@ -1,5 +1,4 @@
import type { VaultClient } from './vault';
import type { VaultCredentials, RegionInput, InstanceTypeInput } from '../types';
import type { RegionInput, InstanceTypeInput } from '../types';
import { logger } from '../utils/logger';
/**
@@ -123,126 +122,7 @@ export class RateLimiter {
}
/**
* CloudConnector - Abstract base class for cloud provider connectors
*
* Implements common authentication, rate limiting, and data fetching patterns.
* Each provider (Linode, Vultr, etc.) extends this class and implements
* provider-specific API calls and data normalization.
*
* @abstract
*
* @example
* class LinodeConnector extends CloudConnector {
* provider = 'linode';
*
* async fetchRegions() {
* await this.rateLimiter.waitForToken();
* // Fetch regions from Linode API
* }
*
* normalizeRegion(raw: RawRegion): RegionInput {
* // Transform Linode region data to standard format
* }
* }
* Note: CloudConnector base class has been deprecated.
* Each connector now uses Env directly for credentials instead of Vault.
* See LinodeConnector, VultrConnector, AWSConnector for current implementation patterns.
*/
export abstract class CloudConnector {
/**
* Provider identifier (e.g., 'linode', 'vultr', 'aws')
* Must be implemented by subclass
*/
abstract provider: string;
/**
* Cached credentials from Vault
* Populated after calling authenticate()
*/
protected credentials: VaultCredentials | null = null;
/**
* Rate limiter for API requests
* Configured with provider-specific limits
*/
protected rateLimiter: RateLimiter;
/**
* Create a new cloud connector
*
* @param vault - VaultClient instance for credential management
*/
constructor(protected vault: VaultClient) {
// Default rate limiter: 10 requests, refill 2 per second
this.rateLimiter = new RateLimiter(10, 2);
}
/**
* Authenticate with provider using Vault credentials
* Fetches and caches credentials for API calls
*
* @throws ConnectorError if authentication fails
*/
async authenticate(): Promise<void> {
try {
logger.info('[CloudConnector] Authenticating', { provider: this.provider });
this.credentials = await this.vault.getCredentials(this.provider);
if (!this.credentials || !this.credentials.api_token) {
throw new ConnectorError(
this.provider,
'authenticate',
undefined,
'Invalid credentials received from Vault'
);
}
logger.info('[CloudConnector] Authentication successful', { provider: this.provider });
} catch (error) {
logger.error('[CloudConnector] Authentication failed', { provider: this.provider, error });
throw new ConnectorError(
this.provider,
'authenticate',
undefined,
`Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Fetch raw region data from provider API
* Must be implemented by subclass
*
* @returns Array of raw region objects from provider API
* @throws ConnectorError on API failure
*/
abstract fetchRegions(): Promise<RawRegion[]>;
/**
* Fetch raw instance type data from provider API
* Must be implemented by subclass
*
* @returns Array of raw instance type objects from provider API
* @throws ConnectorError on API failure
*/
abstract fetchInstanceTypes(): Promise<RawInstanceType[]>;
/**
* Normalize raw region data to standard format
* Transforms provider-specific region structure to RegionInput
* Must be implemented by subclass
*
* @param raw - Raw region data from provider API
* @returns Normalized region data ready for database insertion
*/
abstract normalizeRegion(raw: RawRegion): RegionInput;
/**
* Normalize raw instance type data to standard format
* Transforms provider-specific instance structure to InstanceTypeInput
* Must be implemented by subclass
*
* @param raw - Raw instance type data from provider API
* @returns Normalized instance type data ready for database insertion
*/
abstract normalizeInstance(raw: RawInstanceType): InstanceTypeInput;
}