~/teardowns/TD-045.md
←Back to teardowns
# TD-045MediumArchitecture

The Type System That Was Turned Off

any plus @ts-ignore at the payment boundary

Published Oct 29, 2026

Executive Summary

A vibe-coded webhook handler is typed event: any and sprinkled with @ts-ignore. The compiler is silenced exactly where it matters — the boundary with the payment provider. When the provider changes its payload shape, nothing catches it, and the handler marks the wrong order paid.

The Prompt

“Wire the payment provider's webhook into our existing checkout handler.”

The Flaw

any plus @ts-ignore disables the type system exactly where it is needed most — at the boundary with an external system. The compiler cannot check the provider's payload, so when the provider renames a field or sends metadata as null, order_id is undefined and the handler marks the wrong order paid (or throws mid-webhook). The types were supposed to catch this; they were turned off.

Risk & Impact

[Correctness] A provider payload change silently corrupts checkout: the wrong order is marked paid, or the handler crashes on a shape it was never checked against.

Cost: Incorrect payment state that surfaces days later during reconciliation.

Code Comparison

❌ Vulnerable (AI generated)

webhook.tstypescript
1
2
3
4
5
6
7
export async function handleWebhook(event: any): Promise<void> {
// @ts-ignore
const order = await db.orders.find(event.data.object.metadata.order_id);
// @ts-ignore
await db.orders.update(order.id, { status: 'paid' });
}

The Fix

Define the provider's contract with a runtime schema (Zod) instead of any/@ts-ignore — a shape change then fails loudly at the boundary, not silently downstream.

Prevention

  • [linter] typescript-eslint's ban-ts-comment and no-explicit-any rules make suppression a build failure.
  • [ci] A fixture webhook missing a field must throw — proving the schema catches real provider drift.
  • [manual] Require runtime validation at every external boundary (rule 3), and forbid type suppression.

Why This Happened

Root cause: the prompt omitted Rule 3 — the guardrails that would have caught this. See the full guide →

Sources

Is your codebase vulnerable to this?

Run Diagnostic on My Project →