AI code smells
Updated 2026-05-12What this is
Two specific code shapes PreFlight scans for because they correlate with under-validated code:
Empty catch blocks.
try {
await chargeCard(amount);
} catch {
// ...
}
The error from chargeCard is gone. The function returned. Whatever called it sees success. The user is told the charge worked. The charge did not work.
Empty catches in security-touching code are particularly bad because the failure mode of the call is the case where the rest of the program assumes success. A signature-verification function that throws gets swallowed, and the caller proceeds as if the signature was valid.
Heavy any usage in TypeScript.
function handleWebhook(payload: any) {
const order = payload.order;
await db.orders.update({ id: order.id, paid: order.paid });
}
any disables every type guarantee. The compiler will not catch a missing field, a wrong type, a nested undefined, or a malicious shape. Input validation that would normally happen via the type system happens nowhere. The function trusts the shape of payload. The shape of payload is whatever an attacker can post to the webhook.
Why it matters
Industry studies on AI-generated code show ~45% of samples introduce at least one OWASP Top 10 issue. Empty catches and pervasive any are not the issues themselves; they are markers for "this section of the code was generated without consideration of what failure modes look like." Findings here often co-locate with the actual vulnerabilities.
The patterns also explicitly defeat defense-in-depth. An empty catch turns "verify signature, then process" into "process unconditionally." A pervasive any turns "validate input shape at the type boundary" into "trust whatever came in."
What the failure looks like
PreFlight scans for:
catch {}orcatch (e) {}with no body other than whitespace or a comment.: anyin TypeScript function parameters, especially handlers, route handlers, and webhook endpoints.
Note: the AI Code Smells probe is informational. It surfaces patterns worth a second look, not patterns that are themselves exploits. The expected response is "go look at this code path more carefully" rather than "patch immediately."
What the fix looks like
Empty catches: decide what should happen on failure and write it.
try {
await chargeCard(amount);
} catch (e) {
log.error('charge failed', { error: e, amount, userId });
throw new ChargeFailedError({ cause: e });
}
Three rules:
- Log the error with enough context to debug.
- Either rethrow so the caller knows something failed, or take a deliberate compensating action (refund, retry, alert).
- Never
catch (e) {}to suppress noisy errors. If the error is genuinely safe to ignore, write that in a comment and narrow the catch to the specific error type.
Heavy any: add type definitions matching the actual input shape and validate at the boundary.
import { z } from 'zod';
const WebhookSchema = z.object({
order: z.object({
id: z.string(),
paid: z.boolean(),
}),
});
function handleWebhook(payload: unknown) {
const validated = WebhookSchema.parse(payload); // throws on bad shape
await db.orders.update({ id: validated.order.id, paid: validated.order.paid });
}
Two rules:
- The handler signature takes
unknown, notany. The compiler then requires you to validate before accessing fields. - The validation happens via a schema library (Zod, Yup, valibot) that throws on shape mismatch. The validation result is fully typed.
The pattern applies the same way to body parsing in route handlers, query-string parsing, third-party API responses, and webhook payloads. Anywhere data enters the program, validate at the boundary.
Related
- Auth weaknesses covers
eval()anddangerouslySetInnerHTML, two specific shapes that often co-locate withanyusage. - Webhook validation covers the input-validation discipline for webhook handlers specifically.
Sources
CWE-390 covers the empty-catch class. The TypeScript handbook is the authoritative reference for type-safety patterns. OWASP A04:2021 covers insecure design, which is the broader category these smells fall under.
RELATED PROBES
- · AI Code Smells