On May 4, 2026, about 3 billion DRB tokens left a wallet linked to the @grok account on X, worth roughly $150,000 to $200,000 depending on who you read. By the public accounts, no smart contract was broken. About 80% of the funds later came back.
Most coverage led with the Morse code, because Morse code is funny. I think it is the least important part of the story.
What was publicly reported
Bankr automatically provisions a wallet for X accounts that interact with its bot, and Grok had one. As reported by Giskard and others, the chain had three links:
- An attacker sent a Bankr Club Membership NFT to Grok's wallet, which reportedly unlocked transfer capabilities that were not available before.
- The attacker posted an encoded message and asked Grok to translate it. Grok did.
- Bankrbot read Grok's translated reply as a command and moved the tokens.
SlowMist classified it as permission-chain abuse, not as a simple model failure. Bankr's operator gave the detail that interests me most: an earlier version of the agent had a hardcoded block on replies from Grok, meant to stop model-to-model injection chains, and that block did not survive a later rewrite. They have since added a more robust block on that account.
The encoding was never the vulnerability
You can add Morse to a filter. Then someone uses Base64, or a language your filter does not read, or a story about a grandmother. A filter over model text is a list of the attacks you already thought of.
Look at the two decisions that actually moved the money:
- Authority came from something the account received. Anyone can send a token to an address. If holding it widens what an agent may do, then outsiders can write to your permission system.
-
Output from a model was accepted as an instruction. The reply was authenticated in the sense that it really came from
@grok. It said nothing about whether any person with authority over those funds wanted the transfer.
And then the rewrite. A guardrail that exists as a special case is a guardrail one refactor away from gone. I do not say that to single out Bankr's engineers: every team ships rewrites, and this kind of check is exactly the kind that gets lost, because nothing breaks when it disappears. In most codebases the tests would still pass and the product would keep working, right up to the moment it matters.
A block on one account protects one account. If that were the whole fix, the next model-operated account would just be a different string. Bankr's docs do describe broader controls, such as read-only API keys and trusted-recipient allowlists. From the outside, I can't tell whether they cover commands that arrive through X.
Default deny has a different failure mode
The alternative is not a better list. It is changing what happens when nobody wrote a rule.
I modeled the chain locally, using capability tokens from my project CleiMesh and a dispatch gate written from an open draft RFC. To be clear about what this is: a small simulation of the publicly described chain. It contains none of Bankr's code, touches no live service, and every "transfer" in it is a boolean. The code is about 250 lines, and you can run it yourself.
Model only: no real wallet, chain or service is involved.
A. Authority inferred from a gifted NFT, translated text as a command
transfer executed: true
B. The same gift, but authority must be signed by the owner
Err("tool transfer not granted")
C. Transfers granted, but only to a payee the owner named
Err("argument to out of bounds")
D. Capability is fine, but the call was not approved by a human
capability: Ok(())
dispatch: Refuse(MissingBinding)
E. Approved by a human, then the destination is swapped
as approved: Admit
swapped: Refuse(DigestMismatch)
Three things to notice:
- The gift grants nothing (B). Authority is a token the owner signed. No asset an outsider sends can produce that signature, so there is no rule to remember and none to lose.
- Grants are narrow (C). Even with transfers allowed, the owner named the payee.
- A valid capability is still not consent (D, E). With no human approval bound to the call, the host refuses. With one, the host recomputes what is about to run and refuses if the destination moved.
Where this stops working
This is not a magic fix, and I would rather you hear that from me.
- A rewrite can also delete a verification call. The structural protection only holds if the component that executes the transfer cannot act without a valid token, so that deleting the check breaks the feature instead of quietly opening it.
- In this model the gate stops the attack and also stops any autonomous transfer, since no human approved it. For agents that are supposed to act alone, you need consent that is delegated ahead of time, within limits. I am still working on that.
- The model assumes there is an owner to sign. In the real case the wallet was tied to the X account, and reportedly nobody at xAI was managing it. With no human owner there is nobody to grant anything, and the honest default is read-only.
- If the screen lied before the human approved, the binding faithfully records the lie. It gives integrity of the action about to run, not proof of what a person's eyes saw.
- Bankr's real architecture is more complicated than five steps. I am working from public reports, and details differ between them.
The question to ask about your own agent
Forget the encoding. Open your agent's permission path and ask: what can a stranger send me that changes what my agent is allowed to do? Then ask the second one: if someone deletes this check in a refactor, does the system fail closed or fail open?
If the honest answer to the second is "it would just keep working", you have the same bug. You just have not had your May 4 yet.
Open to consulting and collaboration on agent security: threat modeling, consent and trusted-path design, guardrail audits. I prefer async and written communication.
- GitHub: github.com/cleitonaugusto
- X: @cleitonaugustob
- Email: augusto.cleiton@gmail.com
— Cleiton Augusto Correa Bezerra



Top comments (2)
Putting special-case guardrails inside the agent orchestration layer is why rewrites drop them so quietly. Nothing in the build or happy-path test suite fails when a prompt filter or handle denylist disappears during a refactor. Moving capability token verification into the downstream signing service or execution wrapper changes that failure mode, because an unauthenticated call gets rejected at the boundary even if the orchestrator was rewritten.
Your point about screen lies before approval is the hardest piece to close cleanly. Even with digest binding on the destination address and amount, an upstream summary can misrepresent why a transfer is happening, so keeping the human review payload tied to raw transaction fields instead of model-generated descriptions matters as much as the signature itself.
Agreed on the first point, with one condition: moving the check downstream only helps if the signer has no unsigned path at all. If there is a "legacy" or "internal" route that skips verification, the rewrite just deletes the check one layer lower. The test I'd want is not "a valid token is accepted" but "the signer refuses when the verifier is removed".
On the second, you've named a gap the binding doesn't close: the reason for a transfer isn't part of the action, so nothing binds it. Raw fields help, but a raw address is also something no human actually reads. My working rule: everything on the approval screen is either a bound field of the action or clearly marked as unverified commentary. A model-written "why" belongs in the second bucket, visibly.