Use msg.sender for authorization and never tx.origin. The Solidity documentation says so directly. The reason is that tx.origin is the account that started the whole transaction, so a malicious contract that the owner is tricked into calling can pass the owner's address along and act as them.
What each value means
msg.sender is the address that called the current function. It changes as calls pass from contract to contract. tx.origin is the externally owned account that signed the original transaction. It stays the same through the whole chain of calls.
How the attack works
Suppose a wallet contract lets its owner withdraw, and it checks that tx.origin equals the owner. An attacker deploys a contract with an innocent looking function. They persuade the owner to call it, perhaps through a fake airdrop or a game.
Inside that function the attacker's contract calls the wallet. The wallet sees tx.origin equal to the owner and approves the withdrawal. The owner never meant to authorize anything, but the check passed.
The Solidity documentation describes this scenario: a malicious intermediary contract can supply the owner's address as tx.origin and drain the wallet.
The fix
require(msg.sender == owner);
With msg.sender, the wallet sees the attacker's contract as the caller, not the owner, and the check fails.
Is there ever a reason to use tx.origin
Rarely, and almost never for permission checks. Some people use it to block contracts from calling a function at all, but that is a fragile design choice, and it can break legitimate smart wallets. If you think you need it, write down the threat model first and have someone else review it.
How to catch it
- Search your code for tx.origin during review.
- Run a static analyzer. The Slither project lists tx.origin use among the issues its detectors look for.
- Add a test where a hostile intermediary contract calls your function as the owner would.
Quick check
Is tx.origin ever the same as msg.sender?
Yes, when a person calls your contract directly. They differ as soon as another contract sits in between, which is exactly the case an attacker creates.
Does a multisig fix this?
Only if the check inside your contract uses msg.sender. A bad check is bad whoever owns the account.
Sources
Last reviewed: 19 October 2026.