Starknet Specialized Scanner
SkillCommunicationUse when the user wants to audit Starknet contracts for security vulnerabilities, scan Cairo contracts for Starknet-specific patterns including account abstraction, class replacement, or L1-L2 messaging, review Starknet DeFi protocols for component architecture flaws, or analyze cross-layer bridge security.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Starknet Specialized Scanner skill
What this skill tells your AI
The instructions your AI receives, as published by 0x-shashi/web3-audit-skills in skills/starknet-scanner/SKILL.md and read by ahel’s review.
Specialized security scanner for Starknet Cairo contracts. Extends the general Cairo Scanner with Starknet-specific patterns: account abstraction, contract upgrades via replace_class, component architecture, and the L1-L2 messaging bridge.
Why a Separate Starknet Scanner?
While the Cairo Scanner covers language-level patterns (felt arithmetic, Sierra safety), Starknet-specific features create unique attack surfaces:
| Feature | Security Impact |
|---|---|
| Account Abstraction | Custom validation logic = custom attack surface |
replace_class_syscall | Contract upgrade mechanism — must be protected |
| Components (like Solidity libraries) | Storage collision between components |
| L1-L2 Messaging | Cross-chain replay, message validation |
| Sequencer | Centralized sequencer = MEV, censorship risks |
| Fee market | STRK token fees, gas estimation |
Detection Capabilities
| Category | Detection | Severity |
|---|---|---|
| Account | __validate__ missing signature check | Critical |
| Account | __execute__ allows arbitrary call without validation | Critical |
| Account | Signature replay across chains (no chain_id in hash) | High |
| Upgrade | replace_class_syscall callable by unauthorized party | Critical |
| Upgrade | No upgrade delay/timelock | High |
| Upgrade | Storage layout incompatibility after upgrade | High |
| Components | Storage collision between components | High |
| Components | Component events shadowing contract events | Medium |
| L1-L2 | Message replay (consumed message not tracked) | Critical |
| L1-L2 | Missing sender validation on L1 handler | Critical |
| L1-L2 | Message not consumed (stuck funds) | High |
| Felt | Felt arithmetic wrapping (p = 2^251 + 17*2^192 + 1) | High |
| Storage | Storage address collision (Pedersen hash) | Medium |
| Access | Missing caller validation on external function | Critical |
| Access | Ownable component not initialized | High |
Starknet Account Abstraction
Every account on Starknet is a smart contract. This means custom validation logic:
#[starknet::contract(account)]
mod MyAccount {
// REQUIRED: Validates transaction signature
// If this returns successfully, the tx is considered valid
#[external(v0)]
fn __validate__(
ref self: ContractState,
calls: Array<Call>
) -> felt252 {
// CRITICAL: Must verify the transaction signature
// If this blindly returns VALIDATED, anyone can submit txs as this account
let tx_hash = get_tx_info().unbox().transaction_hash;
let signature = get_tx_info().unbox().signature;
// Verify signature against stored public key
assert(check_ecdsa_signature(tx_hash, self.public_key.read(), *signature.at(0), *signature.at(1)), 'invalid sig');
starknet::VALIDATED
}
// REQUIRED: Executes the validated transaction
#[external(v0)]
fn __execute__(
ref self: ContractState,
calls: Array<Call>
) -> Array<Span<felt252>> {
// Execute each call
// Typically a loop over calls with call_contract_syscall
}
}
Account Security Checklist
-
__validate__verifies transaction hash signature -
__validate__uses stored public key (not hardcoded) - Signature cannot be replayed (nonce handled by protocol)
-
chain_idincluded in signature verification (cross-chain replay) - Key rotation mechanism exists and is secure
- Multicall execution handles failures correctly (atomicity)
Contract Upgrade via replace_class_syscall
Starknet contracts can upgrade their logic using replace_class_syscall:
use starknet::replace_class_syscall;
use starknet::ClassHash;
#[external(v0)]
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
// CRITICAL: WHO can call this?
self.ownable.assert_only_owner();
// Replace the contract's class (logic) with new implementation
replace_class_syscall(new_class_hash).unwrap();
// Emit upgrade event
self.emit(Upgraded { new_class_hash });
}
Upgrade Security
| Risk | Description |
|---|---|
| Unauthorized upgrade | Anyone calling replace_class_syscall can change contract logic |
| No timelock | Instant upgrade = no time for users to exit |
| Storage incompatibility | New class may interpret storage differently |
| Proxy pattern | If using proxy, verify replace_class on implementation, not just proxy |
-
replace_class_syscallprotected by access control (owner, governance) - Upgrade delay (timelock) implemented for critical contracts
- Storage layout documented and verified compatible across versions
- Upgrade event emitted
Component Architecture
Starknet components are reusable modules (similar to Solidity libraries with storage):
// Using OpenZeppelin components
#[starknet::contract]
mod MyContract {
use openzeppelin::access::ownable::OwnableComponent;
use openzeppelin::token::erc20::ERC20Component;
component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
component!(path: ERC20Component, storage: erc20, event: ERC20Event);
#[storage]
struct Storage {
#[substorage(v0)]
ownable: OwnableComponent::Storage,
#[substorage(v0)]
erc20: ERC20Component::Storage,
// Custom storage
my_value: felt252,
}
}
Component Security
| Risk | Description |
|---|---|
| Storage collision | Two components writing to same storage address |
| Uninitialized component | Ownable without initializer() = no owner set |
| Event shadowing | Component events with same name as contract events |
| Version mismatch | Component version incompatible with contract |
- All components initialized in constructor
-
#[substorage(v0)]used correctly (automatic storage isolation) - No manual storage access that could collide with component storage
- Component versions compatible with each other
L1-L2 Messaging
Starknet communicates with Ethereum L1 via asynchronous messaging:
| Direction | Mechanism | Latency |
|---|---|---|
| L1 → L2 | send_message_to_l2() on Starknet Core contract | ~minutes (L2 block time) |
| L2 → L1 | send_message_to_l1_syscall() in Cairo | ~hours (proof verification) |
L1-L2 Security Checklist
- L2 handler validates L1 sender (
from_addressin L1Handler) - L1 handler validates L2 sender (message origin)
- Messages consumed exactly once (replay protection)
- Message format matches between L1 and L2 contracts
- Stuck message handling (cancellation mechanism exists)
- Fee handling on L1→L2 messages correct
Resources
Workflows
See Also
- Cairo Scanner for Cairo language patterns
- Chain Guide: Starknet for chain context
Error Code Reference
Starknet-specific error codes and system errors encountered during audits.
Starknet OS / Sequencer Errors
| Error Code | Name | Meaning |
|---|---|---|
TRANSACTION_FAILED | Transaction failure | Generic execution failure — check inner error |
ENTRYPOINT_NOT_FOUND | Missing entrypoint | Selector not found on contract — wrong function name/args |
UNINITIALIZED_CONTRACT | No contract | Address has no deployed contract class |
ENTRY_POINT_FAILED | Execution revert | Contract function reverted — check custom error |
FEE_TRANSFER_FAILURE | Fee payment | Insufficient balance to pay transaction fee |
VALIDATE_FAILURE | Account validation | Account __validate__ rejected transaction — signature/auth issue |
OUT_OF_RESOURCES | Resource limit | Transaction exceeded Cairo steps or builtins limit |
CLASS_ALREADY_DECLARED | Duplicate class | Contract class hash already declared on network |
Account Abstraction Errors
| Error Pattern | Source | Meaning |
|---|---|---|
'INVALID_SIGNATURE' | __validate__ | Signature verification failed in account contract |
'INVALID_CALLER' | Account guard | Caller is not the expected account or protocol |
'INVALID_TX_VERSION' | Version check | Transaction version not supported (v1 vs v3) |
'EXPIRED' | Time check | Transaction or session expired |
'UNDERSPENT_FEE' | Fee estimation | Actual fee lower than estimate — potential gas griefing |
'PAYMASTER_REJECTED' | Paymaster | Paymaster refused to sponsor transaction |
Starknet Contract Errors (OpenZeppelin Cairo)
| Error String | Component | Meaning |
|---|---|---|
'Caller is not the owner' | OwnableComponent | Missing owner role — access control |
'Caller is the zero address' | OwnableComponent | Invalid zero caller |
'New owner is the zero address' | OwnableComponent | Invalid ownership transfer |
'ERC20: insufficient balance' | ERC20Component | Token balance too low |
'ERC20: insufficient allowance' | ERC20Component | Approval not set |
'ERC721: invalid token ID' | ERC721Component | Token does not exist |
'ERC721: unauthorized caller' | ERC721Component | Not owner or approved |
'ReentrancyGuard: reentrant call' | ReentrancyGuardComponent | Reentrancy detected |
'Class hash cannot be zero' | UpgradeableComponent | Invalid upgrade target |
L1↔L2 Messaging Errors
| Error Pattern | Direction | Meaning |
|---|---|---|
'INVALID_MESSAGE_TO_CONSUME' | L1→L2 | Message not found in L2 pending messages |
'MESSAGE_NOT_SENT' | L2→L1 | L2 message not recorded by sequencer |
'INVALID_FROM_ADDRESS' | L1→L2 | L1 sender address does not match expected |
'INVALID_NONCE' | Both | Message nonce mismatch — replay or ordering issue |
Troubleshooting
| Issue | Likely Cause | Solution |
|---|---|---|
| Account abstraction vulnerabilities missed | Scanner uses EOA mental model | Audit __validate__ and __execute__ in all account contracts; check signature schemes |
replace_class upgrade risks not flagged | Scanner doesn't track class replacement | Map all replace_class_syscall calls; verify upgrade authority and timelock protections |
| Component storage collision missed | Scanner doesn't model Cairo component storage | Verify component storage isolation; check for #[storage] field name conflicts across components |
| L1↔L2 message handling gaps | Scanner audits L2 in isolation | Audit #[l1_handler] functions; trace message flow from L1 contract through Starknet OS |
| Fee estimation manipulation not caught | Scanner doesn't model Starknet fee mechanism | Check __validate__ and __execute__ for fee-related assumptions; test with v3 transactions |
| Missing event emission in state changes | Scanner focuses on logic, not observability | Verify all state-changing functions emit events; critical for off-chain indexing and monitoring |
Signals
- GitHub stars
- 60
- Forks
- 10
- Last commit
- Feb 2026
Advanced
- Catalog kind
- skill
- Gateway key
starknet-scanner- Source
- github.com/0x-shashi/web3-audit-skills