tx.origin refers to the original externally-owned account (EOA) that initiated a transaction chain. msg.sender refers to the immediate caller. When a user calls contract A, which calls contract B, tx.origin is the user and msg.sender in contract B is contract A. Using tx.origin for auth means any contract the user calls can impersonate them.
A victim's wallet contract uses tx.origin == owner as its auth check. An attacker deploys a phishing contract and tricks the owner into calling it (e.g., through a fake airdrop). The phishing contract then calls the victim's wallet — tx.origin is still the owner — and drains it.
// VULNERABLE — uses tx.origin for ownership check
contract Wallet {
address owner;
function transfer(address payable to, uint amount) public {
require(tx.origin == owner, "Not owner"); // WRONG!
to.transfer(amount);
}
}
// Attacker's phishing contract:
contract PhishingAttack {
Wallet target;
address attacker;
receive() external payable {
// When victim calls this contract, tx.origin == victim
target.transfer(payable(attacker), address(target).balance);
}
}
// SAFE — use msg.sender, not tx.origin
function transfer(address payable to, uint amount) public {
require(msg.sender == owner, "Not owner"); // CORRECT
to.transfer(amount);
}
// tx.origin is only acceptable for one use case:
// ensuring the caller is an EOA (not a contract)
require(msg.sender == tx.origin, "No contracts allowed");
The only safe use of tx.origin is checking that the immediate caller is an EOA (by requiring msg.sender == tx.origin). Never use it as an identity check for authorization.
msg.sender (not tx.origin) for authorization checks.tx.origin usage — enable the rule in your CI pipeline.tx.origin.