The Bybit attack, and the path it used
On 21 February 2025, Bybit lost roughly $1.5 billion from a Safe multisig cold wallet. It is the largest theft in the history of the industry, and it is worth reading carefully, because almost everything about it was correct except one thing.
What happened
The short version, from the public post-mortems:
- An attacker compromised a
Safe{Wallet}developer’s machine and injected malicious JavaScript into the AWS S3 bucket serving theSafe{Wallet}front-end. The code went in on 19 February and was triggered on 21 February, targeted at Bybit’s specific Safe. - Bybit’s signers opened the interface and reviewed a transaction that looked ordinary.
- The payload actually sent to their hardware wallets was not that
transaction. It was a
delegatecallthat overwrote the Safe proxy’smasterCopy— slot 0 — replacing the entire account implementation with the attacker’s. - The signers approved. The signatures were valid. The contract did exactly what it was told.
Attribution was made publicly to North Korea-linked activity (the FBI named the TraderTraitor cluster).
What was not broken
- Not the Safe contracts. They executed a validly signed instruction. No bug in Safe was exploited.
- Not the cryptography. Every signature was genuine.
- Not the hardware wallets. Ledger devices were in the loop and signed
anyway — because a hardware wallet shows you what it is given, and what it
was given was the malicious payload. A device that cannot decode a
delegatecallinto something a human can evaluate protects the key, not the decision.
What was broken is the assumption underneath every wallet interface: that the screen describing a transaction and the bytes being signed are the same thing.
Why this is the general case, not a freak event
Most signatures produced in a web wallet rest on that assumption. The interface builds the payload, the interface renders the summary, and nothing independent checks that one matches the other. If the code serving that interface is replaced — by a compromised build pipeline, a hijacked CDN, a malicious dependency, a stolen deploy credential — the summary becomes whatever the attacker wants it to be, and your signature is real.
This is the risk Vela’s signing design is aimed at. Not phishing. Not a leaked key. A signing screen that is lying to you.
What Vela does about it
Clear signing, to the calldata. Every transaction is decoded into
human-readable intent before you approve — amount, recipient, what the call
actually does (ERC-7730). A call we cannot decode is flagged as undecodable, not quietly rendered as if it were fine. The Bybit
payload was a delegatecall that swapped an implementation address; that is
precisely the shape of thing that should stop a signer dead, and hiding it
behind a friendly summary is how it did not.
Two things to be exact about. A dApp cannot ask Vela for a delegatecall directly — the requests a page can make produce ordinary calls — so the Bybit
payload itself could not arrive that way. And a page that asks for a call from
your Safe to itself is refused, not merely decoded: enableModule, addOwnerWithThreshold, swapOwner, setFallbackHandler, setGuard and the
rest of that family are blocked when the target is your own wallet, inside a
batch and inside a MultiSend as well, as is any leg carrying a delegatecall whatever it targets, and a SafeTx typed-data signature. The wallet says which
call it refused and offers nothing to sign. Any one of those, signed once, would
hand over the account as completely as the Bybit payload did — an enabled module
can then run a delegatecall of its own. And if Vela’s own code were replaced,
as Safe{Wallet}’s was, the decoding would be the attacker’s too — which is what
the next point is for.
An independent path that can check the interface. Vela’s Trusted Signer is a single-file page at sign.getvela.app that decodes the request and performs the WebAuthn signature on its own — a page you can read end to end, rebuild byte for byte, or serve yourself. It is a second opinion that does not share the main app’s supply chain: the app only hands it the request, and the page computes what gets signed itself. Status: published; the desktop, iPhone and Android apps use it on a device where you chose it when creating the wallet or signing in. The desktop app compares the page with the versions it ships with but only logs a mismatch, and the phone apps don’t check yet.
No admin role for us to lose. Vela’s accounts are unmodified Safe v1.4.1, and Vela holds no privileged
role on them — no admin key and no upgrade path of ours that we could be coerced
or compromised into using. Be clear about what that does not remove: the
primitive Bybit’s attackers used — an owner-signed delegatecall that rewrites
the account’s implementation — still exists in every Safe, Vela’s included
(Vela’s own batched transactions use delegatecall into Safe’s MultiSend). It
takes a valid signature from one of your keys. The defences against being
talked into giving one are the decoding above and the independent check.
A fresh check for every signature. Every signature needs your key’s own confirmation — Face ID, a fingerprint, a PIN, or a touch and PIN on a security key. There is no long-lived session key, so there is no window in which something can sign on your behalf without you present.
Self-hosting as the backstop. The apps and the backend services are open source. If you do not want to trust our build pipeline at all, build the extension or an app yourself and run the services you need — the self-hosting guide walks through it. That takes our build pipeline out of the chain of trust; you still trust the code you build, so read it.
What Vela does not claim
Vela’s front-end could be compromised the same way Safe{Wallet}’s was. Our code
is not audited. Saying otherwise would be exactly the kind of assurance this
incident should have ended.
What the design tries to do is narrow the path: make the payload legible instead of opaque, hold no admin role that could be abused on your behalf, and give you a way to verify with something that is not us. The honest summary is that this class of attack is mitigated by design, not eliminated, and the parts that would harden it further are listed, unfinished, in audits & known issues.