Attack Lab #2: We Broke Our Own Firewall — A Thousand Small Payments
A per-transaction cap stops greed, not patience. We found our own reference implementation didn't satisfy our own standard (RAP Pillar 2), added a sixth gate, and caught an uglier float bug that killed legitimate payments. All real run data.
Attack Lab · Episode 2. This time the target was us — and we got through.
In Episode 1 we attacked our own compliance gateway six ways and every shot was blocked. The conclusion was "the line holds." This episode, we went looking for one it wouldn't hold.
We found it.
The hole: a thousand small payments
Our firewall has a per-transaction limit gate — any payment above the cap dies on the spot. So draining the wallet in one shot is impossible.
But a hijacked agent doesn't have to be greedy. It can spend 0.01 at a time, a thousand times.
Every single payment is under the cap. Every single one is perfectly "compliant." And the wallet is empty.
Our mandate at the time looked like this:
Per-transaction cap: 0.1 USDC ✅ had it
Daily cumulative cap: — ❌ didn't
RAP Pillar 2 says, in writing, "per-transaction / daily / cumulative limits." We had implemented only the first. Our own reference implementation did not satisfy our own standard. Not a good look — and true.
The patch: a sixth gate
We added a daily-cumulative gate: every agent's total spend for the day is accounted for, and once it exceeds the daily cap the payment dies — even if each individual payment is under the per-tx limit.
After patching, we ran a real test against the live gateway (per-tx cap 0.1, daily cap 0.3):
| Attempt | Amount | Result | |---|---|---| | #1 | 0.1 USDC | ✅ 200 allowed (cumulative 0.1) | | #2 | 0.1 USDC | ✅ 200 allowed (cumulative 0.2) | | #3 | 0.1 USDC | ✅ 200 allowed (cumulative 0.3, exactly at cap) | | #4 | 0.1 USDC | 🛑 403 blocked at the daily-cumulative gate |
Every payment was under the per-tx cap. #4 died anyway. The thousand-small-payments road is now closed.
Bonus: an uglier bug
On the first test after patching, payment #3 was blocked — but it should have passed (cumulative landing exactly on the cap).
The cause was this line:
if (spent + amt > dailyCap) // spent=0.2, amt=0.1, cap=0.3
In IEEE 754, 0.2 + 0.1 = 0.30000000000000004, which is greater than 0.3. A perfectly legitimate payment was killed.
Never compare money as floats. We moved to integer micro-units (USDC's 6 decimals):
const micro = (x) => Math.round(x * 1e6);
if (micro(spent) + micro(amt) > micro(dailyCap))
Re-tested: #3 correctly passes, #4 correctly dies.
A gate that fails to stop bad payments is dangerous. A gate that kills good payments is dangerous too — it makes people turn the firewall off.
Three things this episode taught us
1. Defenses must be built per attack surface, not per intuition. A per-tx cap stops greed; it does not stop patience. Velocity and cumulative caps stop patience. Real attackers use both.
2. Measure your own implementation against your own standard. We wrote that RAP Pillar 2 requires per-tx/daily/cumulative — and shipped only per-tx. A standard isn't something you publish; it's something you're held to — so we're publishing the gap rather than quietly patching it.
3. In financial code, floats are the enemy. This bug hurts you with no attacker present.
Next episode
We're going after attribution: can one agent impersonate another, already-attributed agent? Bring your attack ideas.
Attack it yourself: oceanalt.com/firewall · Test with your own agent: integration guide
(OceanAlt Attack Lab · research note)
Provenance & status
- Byline
- OceanAlt Editorial
- First published
- 2026-07-16
- Last updated
- 2026-08-01
- Source material
- Source not labeled

