platform-tooling/run/core/response-validator.ts
Quinn Ftw 75114e72b5 chore(core): 🔧 Update URL/response validation rules to handle edge cases in strict patterns
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-02-01 02:21:18 -08:00

84 lines
2.4 KiB
TypeScript

/**
* Response Body Validator
*
* Detects "soft failures" where an HTTP 200 response actually contains
* an error page. For example, Vite returns 200 OK with a "Blocked request"
* HTML page when a host isn't in allowedHosts.
*/
// =============================================================================
// Types
// =============================================================================
interface SoftFailurePattern {
test: RegExp;
reason: string;
}
// =============================================================================
// Known Patterns
// =============================================================================
/**
* Patterns that indicate an HTTP 200 response is actually an error.
* Each pattern is tested against the first 2KB of the response body.
*/
const SOFT_FAILURE_PATTERNS: SoftFailurePattern[] = [
{
test: /Blocked request[\s\S]*?allowedHosts/,
reason: 'Vite allowedHosts block detected',
},
{
test: /Host header validation failed/,
reason: 'Vite host validation failed',
},
];
/** Maximum bytes to read from response body for validation */
const MAX_BODY_BYTES = 2048;
// =============================================================================
// Validation
// =============================================================================
/**
* Check a successful HTTP response for known error patterns in the body.
*
* Clones the response before reading the body, so the original response
* can still be consumed by the caller if needed.
*
* @returns The reason string if a soft failure is detected, or null if clean
*/
export async function detectSoftFailure(response: Response): Promise<string | null> {
try {
const cloned = response.clone();
const reader = cloned.body?.getReader();
if (!reader) return null;
// Read up to MAX_BODY_BYTES
let text = '';
const decoder = new TextDecoder();
while (text.length < MAX_BODY_BYTES) {
const { done, value } = await reader.read();
if (done) break;
text += decoder.decode(value, { stream: true });
}
reader.cancel();
// Trim to limit
const sample = text.slice(0, MAX_BODY_BYTES);
for (const pattern of SOFT_FAILURE_PATTERNS) {
if (pattern.test.test(sample)) {
return pattern.reason;
}
}
return null;
} catch {
// If body reading fails, don't block the health check
return null;
}
}