AIOil Security Shield · Post-mortem scan
Published 16 August 2026. Both programs below were fixed years ago — this is a re-run on public history, not a disclosure.
We took the on-chain source of two Solana protocols as it stood the day before each was exploited, and ran it through the same automated pipeline we sell: three AI agents (engineer, hacker, reviewer), no human hints, no knowledge of what the answer was supposed to be.
We are publishing both results — the one it got and the one it missed. A page with only the win is the page nobody believes.
| Target | Real root cause | Time |
|---|---|---|
| Wormhole core bridge Feb 2022 · $325M |
✔ Found — reported Critical, right location, right fix | 7m 33s |
| Cashio brrr / bankman Mar 2022 · $52M |
✘ Missed — flagged a related symptom, not the exploit path | 9m 36s |
api/verify_signature.rs
“instruction_acc is a raw Info account and is never
checked against solana_program::sysvar::instructions::id(). The code parses its data
as the serialized instructions sysvar, so an attacker can supply a fake account with crafted bytes
that looks like a previous secp256k1 instruction.”
That is the bug. Not an adjacent one — the one. The attacker forges the instructions sysvar, every guardian signature is marked valid without a single ECDSA signature, and the resulting VAA mints wrapped ETH out of nothing.
Open the pre-patch source. The account is declared untyped:
/// Instruction reflection account (special sysvar) pub instruction_acc: Info<'b>, // no type, no owner check
Its data is then parsed twice as the instructions sysvar —
load_current_index and load_instruction_at — and in the whole
program those two calls are the only places the sysvar appears. There is no
comparison against sysvar::instructions::id() anywhere. Nothing establishes
that the account handed in is the real sysvar.
The fix the pipeline proposed is the fix that was actually shipped: use a typed
Sysvar<Instructions>, or compare the key explicitly.
The same scan returned five findings. Honesty requires saying which ones survive review:
initialize has no constraint tying the caller to the
deployer. But it requires the bridge accounts to be uninitialized, and the
bridge has been live since 2021. We verified the program is deployed and executable on
mainnet today. The scenario describes a deployment-window risk, not a live one, and
Critical is the wrong severity for a deployed bridge.
transfer_fees does not update bridge.last_lamports.
Verified: last_lamports is written in exactly two places, and the fee-withdrawal
path is not one of them, so the next post_message hits MathOverflow.
Real — but it needs a governance action to trigger and anyone can unbrick it by topping the
collector back up.
upgrade_guardian_set accepts up to 255 keys while initialize
caps at 19 and the signer array is fixed at 19. Verified.
BrrrCommon::validate() and SaberSwapAccounts::validate() perform
nine assert_keys_eq! checks. Every one of them compares two
caller-supplied accounts to each other. Not one is anchored to a canonical root —
a known address, a PDA with fixed seeds, an upgrade authority. An attacker who builds the
whole chain out of accounts they control passes all nine.
The codebase knows how to anchor: print_cash checks
issue_authority against a hardcoded ISSUE_AUTHORITY_ADDRESS.
That discipline was simply never applied to the bank/collateral chain.
It reported High on Saber reserve balances being usable as a manipulable
price source — a real anti-pattern, and a different vulnerability class from the one that
was exploited. And it reported Medium on new_bank being callable
by anyone, which is a genuine precondition — but it framed it as
front-running the deployer to squat the canonical bank, and closed with:
"If multiple banks are intentionally permissionless, document it and ignore this finding."
The real attacker never needed to front-run anyone. They created a parallel bank after deployment — allowed by design — and the flaw is that the minting path never checks the bank is the bank. Our pipeline saw the unlocked door and described the wrong way through it.
We are not going to call that a near-miss. It is a miss.
The two runs disagree with each other in a way that is more useful than either result alone. The same structural pattern — an unauthenticated initializer that lets an attacker own the root of a validation chain — was rated Critical on Wormhole and Medium on Cashio. The pipeline is not blind to the pattern. It weighs it inconsistently.
So we wrote the rule down and put it in the reviewer's rulebook:
The same rule applies to a High our scanner once found in our own fee router, three days before we shipped it to mainnet. That report is public too, and no badge was issued for it — the system withheld it from us.
AIOil Security Shield runs automated, multi-agent static analysis. It is not a professional human audit and does not guarantee the absence of vulnerabilities — it reduces risk, it does not eliminate it. Both programs discussed here were patched years ago; nothing on this page is an unreported vulnerability, and no source code belonging to a customer is ever published. We make no claim that this scan would have prevented either incident: the scans were run in 2026, on code whose outcome was already public.