Access Control
Detects missing or improperly implemented access controls on privileged functions, allowing unauthorized callers to execute restricted operations.
Access Control
Overview
Remediation Guide: How to Fix Access Control Vulnerabilities
The access control detector identifies functions that modify sensitive state — ownership, admin roles, funds, or protocol parameters — without verifying the caller’s authorization. Sigvex analyzes the data-flow graph to determine whether msg.sender or tx.origin is compared against a stored privileged address before any sensitive operation executes.
This detector flags common patterns including: unprotected selfdestruct, unguarded transferOwnership, missing onlyOwner modifiers on withdrawal functions, and privileged state setters without a caller check. According to on-chain incident data, missing access controls account for a significant share of smart contract exploits annually, with the Ronin bridge hack ($625M, March 2022) being a prominent example of insufficient multi-signature authorization.
Why This Is an Issue
Access control vulnerabilities are among the highest-impact vulnerability class in smart contracts. An attacker who can call any function on a contract — including drain-ether, upgrade-proxy, or set-admin functions — can take complete control of the protocol’s assets and governance. Unlike reentrancy, access control bugs often require only a single transaction and leave no artifacts for defenders.
The pattern frequently appears in: newly deployed contracts that forget to port modifiers from prior versions, contracts copied from templates where the ownership model changed, and upgradeable proxies where the _authorizeUpgrade function is left unprotected.
How to Resolve
// Before: Vulnerable — no caller check
function withdrawAll() external {
payable(msg.sender).transfer(address(this).balance);
}
// After: Fixed — caller must be owner
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function withdrawAll() external onlyOwner {
payable(owner).transfer(address(this).balance);
}
For role-based access control, use OpenZeppelin’s AccessControl:
import "@openzeppelin/contracts/access/AccessControl.sol";
contract ProtocolAdmin is AccessControl {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(ADMIN_ROLE, msg.sender);
}
function emergencyPause() external onlyRole(ADMIN_ROLE) {
// safe: only ADMIN_ROLE holders can call
}
}
Examples
Vulnerable Code
contract VulnerableProxy {
address public implementation;
address public admin;
// Missing access check — anyone can upgrade the implementation
function upgradeImplementation(address newImpl) external {
implementation = newImpl;
}
// Missing access check — anyone can drain ETH
function emergencyWithdraw() external {
payable(msg.sender).transfer(address(this).balance);
}
}
Fixed Code
contract SecureProxy {
address public implementation;
address public admin;
modifier onlyAdmin() {
require(msg.sender == admin, "AccessControl: not admin");
_;
}
function upgradeImplementation(address newImpl) external onlyAdmin {
require(newImpl != address(0), "Invalid implementation");
implementation = newImpl;
}
function emergencyWithdraw() external onlyAdmin {
payable(admin).transfer(address(this).balance);
}
}
Sample Sigvex Output
{
"detector_id": "access-control",
"severity": "critical",
"confidence": 0.88,
"description": "Function upgradeImplementation() writes to storage slot 0x0 (implementation address) without verifying msg.sender against a privileged address.",
"location": { "function": "upgradeImplementation(address)", "offset": 64 }
}
Detection Methodology
- Identify sensitive operations: The detector looks for writes to storage slots associated with ownership/admin variables,
selfdestruct, ETH transfers, and proxy upgrade calls. - Trace caller checks: Using data-flow analysis, it checks whether
CALLER(opcode formsg.sender) orORIGINis compared (EQ,NEQ) against any storage value in the function’s execution path before the sensitive operation. - Evaluate sufficiency: It distinguishes between hardcoded address checks (high confidence), dynamic owner variable checks (medium-high confidence), and no checks at all (highest confidence of a true positive).
- Apply context: Functions named
initialize,constructor, or that are only reachable viadelegatecallare given lower confidence scores to reduce false positives.
Limitations
False positives:
- Functions protected by a custom modifier whose implementation is in a separate function call may not be fully resolved, resulting in false positives.
- Upgradeable proxy contracts may implement access control in the proxy layer, invisible when analyzing the implementation contract alone.
- Functions that use time-locks or multi-sig patterns rather than direct caller checks may be flagged.
False negatives:
- Access controls implemented via assembly (
sloadfollowed byeq) may not be recognized as owner checks without pattern matching. - Cross-contract access delegation (e.g., “only this sister contract can call”) is not analyzed without cross-contract analysis.
Related Detectors
- Delegatecall — detects dangerous delegatecall from unprotected functions
- Front-Running — detects ordering-sensitive patterns exploitable by unauthorized callers