Cairo External Without Validation Remediation
Overview
Related Detector: Cairo External Without Validation
An external, non-view function on a Starknet contract is callable by anyone. When such a function takes parameters, mutates state, and asserts nothing, it acts on unvalidated data from arbitrary callers. That covers two overlapping gaps: missing access control (a privileged state change with no caller check) and missing input validation (amounts, addresses, or indices used without bounds). The fix is to assert caller identity where the operation is privileged and assert input shape where the operation is value-bearing, before any state change.
Recommended Fix
Before (Vulnerable)
// Anyone can set the owner; no caller or input check.
#[external(v0)]
fn set_owner(ref self: ContractState, new_owner: ContractAddress) {
self.owner.write(new_owner);
}
After (Fixed)
#[external(v0)]
fn set_owner(ref self: ContractState, new_owner: ContractAddress) {
assert(get_caller_address() == self.owner.read(), 'not owner');
assert(new_owner.is_non_zero(), 'zero owner');
self.owner.write(new_owner);
}
This works because the caller assertion closes the privilege to the current owner, and the non-zero assertion rejects the degenerate address that would otherwise brick ownership. Both checks run before the storage write, so no invalid state is ever committed. An external function should never trust its inputs, and these guards make that distrust explicit.
Alternative Mitigations
- Use an access-control component. An
Ownableor role-based guard from a maintained Cairo contracts library centralises the caller check and keeps it consistent across every privileged entry point. - Validate every value-bearing argument. Beyond caller identity, bound amounts, reject zero recipients, and range-check indices so unprivileged functions cannot be driven into out-of-bounds or fund-burning states.
Common Mistakes
- Asserting an unrelated property. A function that asserts something incidental still leaves the privileged write unguarded; the assertion must actually constrain the caller or the input it protects.
- Checking the caller after the effect. Place the caller assertion before the storage write; a check that runs after the state change cannot prevent it.
- Relying on obscurity. An undocumented or oddly named setter is still public on Starknet; only an explicit caller check restricts it.