canister-security

Installation
SKILL.md

Canister Security

What This Is

Security patterns for IC canisters in Motoko and Rust. The async messaging model creates TOCTOU (time-of-check-time-of-use) vulnerabilities where state changes between await calls. canister_inspect_message is NOT a reliable security boundary. Anyone on the internet can burn your cycles by sending update calls. This skill provides copy-paste correct patterns for access control, reentrancy prevention, async safety, and callback trap handling.

Prerequisites

  • For Motoko: mops package manager, core = "2.0.0" in mops.toml
  • For Rust: ic-cdk = "0.19", candid = "0.10"

Security Pitfalls

  1. Relying on canister_inspect_message for access control. This hook runs on a single replica without full consensus. If that replica is malicious, it can simply skip the check and execute the update call without any message inspection. It is also never called for inter-canister calls, query calls, or management canister calls. Always duplicate access checks inside every update method. Use inspect_message only as a cycle-saving optimization, never as a security boundary.

  2. Forgetting to reject the anonymous principal. Every endpoint that requires authentication must check that the caller is not the anonymous principal (2vxsx-fae). In Motoko use Principal.isAnonymous(caller), in Rust compare msg_caller() != Principal::anonymous(). Without this, unauthenticated callers can invoke protected methods — and if the canister uses the caller principal as an identity key (e.g., for balances), the anonymous principal becomes a shared identity anyone can use.

  3. Reading state before an async call and assuming it's unchanged after (TOCTOU). When your canister awaits an inter-canister call, other messages can interleave and mutate state. This is one of the most critical sources of DeFi exploits on IC. Use per-caller locking (CallerGuard pattern) to prevent concurrent operations. For financial operations, also consider the saga pattern (deduct before await, compensate on failure) — but implementing it correctly is complex due to edge cases like callback traps and call timeouts where the outcome is ambiguous.

Related skills

More from dfinity/icskills

Installs
119
GitHub Stars
16
First Seen
Mar 10, 2026