fix(security): Resolve 21 SQL injection vulnerabilities and add transactions

Security fixes (v1.2.2):
- Fix SQL injection in analytics.ts (16 occurrences)
- Fix SQL injection in advanced-search.ts (1 occurrence)
- Fix SQL injection in search-queries.ts (1 occurrence)
- Add validateDaysInterval(), isValidISODate(), validatePeriod() to security.ts
- Use make_interval(days => N) for safe PostgreSQL intervals
- Validate UUIDs BEFORE string construction

Transaction support:
- bulk-operations.ts: 6 atomic operations with withTransaction()
- desk-sync.ts: 2 operations with transactions
- export-import.ts: 1 operation with transaction

Rate limiting:
- Add automatic cleanup of expired entries (every 5 minutes)

Audit:
- Archive previous audit docs to docs/audits/2026-01-31-v1.2.1/
- Create new AUDIT-REQUEST.md for v1.2.2 verification

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 14:47:41 +00:00
parent 7895f31394
commit 7c83a9e168
14 changed files with 3195 additions and 487 deletions

View File

@@ -113,3 +113,50 @@ export function validateSortField(field: string | undefined, allowedFields: stri
if (!field) return defaultField;
return allowedFields.includes(field) ? field : defaultField;
}
/**
* Validate and sanitize days interval for SQL INTERVAL
* Prevents SQL injection by ensuring the value is a safe integer
*/
export function validateDaysInterval(days: unknown, defaultDays = 30, maxDays = 365): number {
const parsed = parseInt(String(days), 10);
if (isNaN(parsed) || parsed < 1) return defaultDays;
return Math.min(parsed, maxDays);
}
/**
* Validate ISO date format
* Accepts: YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss(.sss)?Z?
*/
export function isValidISODate(date: string): boolean {
const isoRegex = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?)?$/;
if (!isoRegex.test(date)) return false;
const d = new Date(date);
return !isNaN(d.getTime());
}
/**
* Validate period parameter against allowed values
*/
export function validatePeriod(period: string | undefined, allowedPeriods: string[], defaultPeriod: string): string {
if (!period) return defaultPeriod;
return allowedPeriods.includes(period) ? period : defaultPeriod;
}
// Rate limit store cleanup interval (5 minutes)
const RATE_LIMIT_CLEANUP_INTERVAL = 300000;
/**
* Clean up expired rate limit entries
*/
function cleanupRateLimitStore(): void {
const now = Date.now();
for (const [key, entry] of rateLimitStore.entries()) {
if (now > entry.resetAt) {
rateLimitStore.delete(key);
}
}
}
// Start cleanup interval
setInterval(cleanupRateLimitStore, RATE_LIMIT_CLEANUP_INTERVAL);