~/teardowns/TD-035.md
←Back to teardowns
# TD-035HighRuntime

The ID That Lost Its Last Digits

Casting a provider's string ID to int corrupts the charge

Published Oct 29, 2026

Executive Summary

A payment webhook handler casts the provider's charge ID to (int). The provider sends IDs as strings because they exceed what an int or float can hold exactly — and the cast silently truncates the value. The refund flag then lands on the wrong charge, with no error.

The Prompt

“Handle the payment webhook and mark the charge as refunded by its ID.”

The Flaw

The provider sends charge_id as a string because 19- to 20-digit IDs exceed what a 64-bit int — and certainly what a JSON float — can represent exactly. Casting to (int) truncates the value. The corrupted ID can still match a different charge row, so the UPDATE succeeds silently against the wrong customer's record. No exception, no warning — just wrong data.

Risk & Impact

[Correctness] A refund is recorded against the wrong charge. Reconciliation fails, and the mistake only surfaces later when a customer disputes a charge they already paid.

Cost: Silent data corruption in the payments path — the hardest kind to notice and the most expensive to unwind.

Code Comparison

❌ Vulnerable (AI generated)

webhook.phpphp
1
2
3
4
5
6
7
8
9
<?php
// webhook.php
$payload = json_decode(file_get_contents('php://input'), true);
$chargeId = (int) $payload['data']['object']['id']; // provider sends IDs as strings
$stmt = $pdo->prepare('UPDATE charges SET refunded = 1 WHERE charge_id = ?');
$stmt->execute([$chargeId]); // updates the wrong row (or none)
?>

The Fix

Treat provider IDs as opaque strings — cast to (string), never (int) — and store and compare them as strings end-to-end.

Prevention

  • [linter] A rule banning (int) casts of payload/ID fields in payment, identity, and webhook code paths.
  • [ci] A fixture webhook with a 20-digit string ID must update exactly the intended fixture row — assert both the affected row count and the targeted ID.
  • [manual] Tell the agent 'IDs are strings, do not cast them' (rule 3), and test with a real provider ID (rule 9).

Why This Happened

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

Sources

Is your codebase vulnerable to this?

Run Diagnostic on My Project →