The Check That Hadn't Cleared — JavaScript Bug Hunt

Modelled on the JPMorgan Chase "infinite money glitch" (August–September 2024): videos spread on social media showing people depositing checks — often bad…

  • Language: JavaScript
  • Layer: Backend
  • Difficulty: Medium
  • Concepts: Money, Validation
  • Modelled on: JPMorgan Chase · 2024
  • Visible tests: cleared money is available; a large uncleared check is mostly held
  • Reward: 50 XP for a complete fix

Briefing

Modelled on the JPMorgan Chase "infinite money glitch" (August–September 2024): videos spread on social media showing people depositing checks — often bad ones, or checks written to themselves — and withdrawing the money before the checks bounced. Chase called it check fraud, closed the gap, and later sued some customers to recover the funds.

This project is a reconstruction of the underlying mistake: availability.js counts a deposited check as spendable the moment it is deposited, whether or not it has cleared.

Fix availableBalance so uncleared deposits are available only up to the policy's immediate amount.

Bug report

BUG-JPM-0902 · Priority: Critical · Reported by: fraud operations

account.entries holds { type: "deposit", amount, status } (status "cleared", "pending" or "returned") and { type: "withdrawal", amount }.

availableBalance(account) = sum of CLEARED deposits

  • sum of withdrawals
  • min(policy.IMMEDIATE_LIMIT, sum of PENDING deposits)

Returned deposits count for nothing. The immediate amount is one allowance across all pending deposits, not one per check.

withdraw(account, amount) succeeds (records a withdrawal, returns true) only if 0 < amount <= availableBalance(account); otherwise returns false and records nothing.

Observed: a $10,000 check deposited a minute ago can be withdrawn in full.

Logs

[atm 0931] acct ****5521 deposit check 10000.00 status=pending
[atm 0931] acct ****5521 withdraw 9800.00 approved (available 10100.00)
[returns] check 0048 returned: NSF

The code as shipped

src/bank/availability.js (editable)

var policy = require("./policy");

exports.availableBalance = function (account) {
  var total = 0;
  for (var i = 0; i < account.entries.length; i++) {
    var e = account.entries[i];
    if (e.type === "deposit") {
      if (e.status !== "returned") total += e.amount;
    } else {
      total -= e.amount;
    }
  }
  return total;
};

exports.withdraw = function (account, amount) {
  if (!(amount > 0) || amount > exports.availableBalance(account)) return false;
  account.entries.push({ type: "withdrawal", amount: amount });
  return true;
};

Read-only context: src/bank/policy.js.

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.