关于 cairo-vulnerability-scanner
此技能为 Cairo/StarkNet 合约审计提供扫描流程,针对算术、跨层消息和签名等 6 类漏洞检查代码并给出证据与修复建议。适用于审计人员和发布前的安全审查。
主要功能
- 01检查 6 类 Cairo/StarkNet 漏洞
- 02分析 L1-L2 消息与地址校验
- 03审查签名重放保护
- 04按模式输出覆盖表与证据
- 05提供漏洞修复建议
适用场景
- 01审计 StarkNet 智能合约
- 02检查 L1-L2 桥接实现
- 03发布前审查签名验证逻辑
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trailofbits/ skills --skill cairo-vulnerability-scanner安装后在 Claude Code 中输入/cairo-vulnerability-scanner确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlassets/trail-of-bits-mark.svgresources/VULNERABILITY_PATTERNS.md详细介绍
Cairo/StarkNet 漏洞扫描器
1. 用途
系统地扫描 StarkNet 上的 Cairo 智能合约,查找与算术、跨层消息传递和密码学操作相关的平台特定安全漏洞。此技能涵盖 Cairo/StarkNet 生态系统特有的 6 种关键漏洞模式。
2. 何时使用此技能
- 审计 StarkNet 智能合约(Cairo)
- 审查 L1-L2 桥接实现
- 在 StarkNet 应用上线前进行安全评估
- 验证跨层消息处理
- 审查签名验证逻辑
- 评估 L1 handler 函数
3. 平台检测
文件扩展名和标识
- Cairo 文件:
.cairo
语言/框架标记
// Cairo contract indicators
#[contract]
mod MyContract {
use starknet::ContractAddress;
#[storage]
struct Storage {
balance: LegacyMap<ContractAddress, felt252>,
}
#[external(v0)]
fn transfer(ref self: ContractState, to: ContractAddress, amount: felt252) {
// Contract logic
}
#[l1_handler]
fn handle_deposit(ref self: ContractState, from_address: felt252, amount: u256) {
// L1 message handler
}
}
// Common patterns
felt252, u128, u256
ContractAddress, EthAddress
#[external(v0)], #[l1_handler], #[constructor]
get_caller_address(), get_contract_address()
send_message_to_l1_syscall
项目结构
src/contract.cairo- 主合约实现src/lib.cairo- 库模块tests/- 合约测试Scarb.toml- Cairo 项目配置
工具支持
- Caracal:Trail of Bits 提供的 Cairo 静态分析器
- 安装:
cargo install --git https://github.com/crytic/caracal --profile release --force(这是 Rust 工具,不在 PyPI 上) - 用法:
caracal detect src/ - cairo-test:内置测试框架
- Starknet Foundry:测试和开发工具包
4. 此技能的工作方式
调用后,我将:
- 搜索你的代码库,查找 Cairo 文件
- 分析每个合约,检查 6 种漏洞模式
- 报告发现的问题,附上文件引用和严重程度,并在其上方提供一张涵盖所有模式判定结果的覆盖率表
- 为每个已识别的问题提供修复方案
- 检查 L1-L2 交互,查找消息传递漏洞
5. 输出示例
发现漏洞时,你会收到类似这样的报告:
=== CAIRO/STARKNET VULNERABILITY SCAN RESULTS ===
6. 漏洞模式(6 种模式)
我会检查 Cairo/StarkNet 特有的 6 种关键漏洞模式。有关详细的检测模式、代码示例、缓解措施和测试策略,请参阅 VULNERABILITY_PATTERNS.md。
模式摘要:
- Felt252 算术溢出/下溢 ⚠️ HIGH -
felt252会静默回绕;请使用u128/u256 - L1 到 L2 地址转换 ⚠️ HIGH - 未验证 L1 地址是否小于 STARKNET_FIELD_PRIME
- L1 到 L2 消息失败 ⚠️ HIGH - 消息无法消费时没有取消路径
- 过度约束的 L1 <-> L2 交互 ⚠️ MEDIUM - 可能导致资金被困或阻碍进展的耦合
- 签名重放保护 ⚠️ HIGH - 没有 nonce,或域分隔符缺少链 ID/合约地址
- 未检查 L1 Handler 中的 from_address ⚠️ CRITICAL - 任何 L1 合约都能驱动该 handler
有关完整漏洞模式及代码示例,请参阅 VULNERABILITY_PATTERNS.md。
7. 扫描工作流
步骤 1:平台识别
- 确认使用 Cairo 语言和 StarkNet 框架
- 检查 Cairo 版本(Cairo 1.0+ 或旧版 Cairo 0)
- 定位合约文件(
src/*.cairo) - 识别 L1-L2 桥接合约(如适用)
步骤 2:算术安全扫描
# Find felt252 usage in arithmetic
rg "felt252" src/ | rg "[-+*/]"
# Find balance/amount storage using felt252
rg "felt252" src/ | rg "balance|amount|total|supply"
# Should prefer u128, u256 instead
步骤 3:L1 Handler 分析
对每个 #[l1_handler] 函数:
- 验证
from_address参数 - 检查地址是否不等于零
- 是否具有适当的访问控制
- 是否为监控发出事件
步骤 4:签名验证审查
对于基于签名的函数:
- 包含 nonce 跟踪
- 使用后递增 nonce
- 域分隔符包含链 ID 和合约地址
- 无法重放签名
步骤 5:L1-L2 桥接审计
如果合约包含桥接功能:
- L1 验证地址 < STARKNET_FIELD_PRIME
- L1 实现消息取消机制
- L2 在 handler 中验证 from_address
- L1 ↔ L2 具有对称的访问控制
- 测试完整的往返流程
步骤 6:使用 Caracal 进行静态分析
# Run Caracal detectors
caracal detect src/
# Specific detectors
caracal detect src/ --detectors unchecked-felt252-arithmetic
caracal detect src/ --detectors unchecked-l1-handler-from
caracal detect src/ --detectors missing-nonce-validation
8. 报告格式
覆盖情况表
报告 §6 中的每种模式,无论是否发现问题。将此表放在发现项之前,并确保包含全部 6 行:
| # | 模式 | 判定 | 证据 |
|---|---|---|---|
| 1 | Felt252 算术溢出/下溢 | clear | 余额使用 u256;已搜索算术运算中的 felt252,未发现 |
| 2 | L1 到 L2 地址转换 | ||
| 3 | L1 到 L2 消息失败 | ||
| 4 | 约束过严的 L1 <-> L2 交互 | ||
| 5 | 签名重放保护 | ||
| 6 | L1 处理函数中未检查的 from_address |
每项判定必须是以下之一:
found—— 引用file:line,并在下方完整写出发现项。clear—— 此模式适用于该合约,且合约已处理此问题。注明你搜索的函数、trait 或检查项,方便读者复查。n/a—— 此模式不适用于此处。用一个分句说明原因(“此合约没有 L1 处理函数”)。未进行检查不能判为n/a。
少于 6 行的表格表示扫描不完整,必须报告为不完整。判定单元格为空的行也同样表示不完整:上方已填写第 1 行以展示格式,报告完成前必须以相同方式填写每一行。6 项均判定为 clear 也是读者可以据此采取行动的结果。只涵盖两种模式而对其余四种只字不提的报告,看起来和一份没有问题的合约完全一样,而这正是此表旨在防止的问题。
发现项模板
## [CRITICAL] Unchecked from_address in L1 Handler
**Location**: `src/bridge.cairo:145-155` (handle_deposit function)
**Description**:
The `handle_deposit` L1 handler function does not validate the `from_address` parameter. Any L1 contract can send messages to this function and mint tokens for arbitrary users, bypassing the intended L1 bridge access controls.
**Vulnerable Code**:
```rust
// bridge.cairo, line 145
#[l1_handler]
fn handle_deposit(
ref self: ContractState,
from_address: felt252, // Not validated!
user: ContractAddress,
amount: u256
) {
let current_balance = self.balances.read(user);
self.balances.write(user, current_balance + amount);
}
```
**Attack Scenario**:
1. Attacker deploys malicious L1 contract
2. Malicious contract calls `starknetCore.sendMessageToL2(l2Contract, selector, [attacker_address, 1000000])`
3. L2 handler processes message without checking sender
4. Attacker receives 1,000,000 tokens without depositing any funds
5. Protocol suffers infinite mint vulnerability
**Recommendation**:
Validate `from_address` against authorized L1 bridge:
```rust
#[l1_handler]
fn handle_deposit(
ref self: ContractState,
from_address: felt252,
user: ContractAddress,
amount: u256
) {
// Validate L1 sender
let authorized_l1_bridge = self.l1_bridge_address.read();
assert(from_address == authorized_l1_bridge, 'Unauthorized L1 sender');
let current_balance = self.balances.read(user);
self.balances.write(user, current_balance + amount);
}
```
**References**:
- building-secure-contracts/not-so-smart-contracts/cairo/unchecked_l1_handler_from
- Caracal detector: `unchecked-l1-handler-from`
9. 优先级指南
严重(需要立即修复)
- L1 处理函数中未检查的 from_address(无限铸币)
- L1-L2 地址转换问题(资金转入零地址)
高(部署前修复)
- Felt252 算术溢出/下溢(余额操纵)
- 缺少签名重放保护(重放攻击)
- L1-L2 消息失败且无法取消(资金被锁定)
中(审计时处理)
- 约束过严的 L1-L2 交互(资金被困)
10. 测试建议
单元测试
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_felt252_overflow() {
// Test arithmetic edge cases
}
#[test]
#[should_panic]
fn test_unauthorized_l1_handler() {
// Wrong from_address should fail
}
#[test]
fn test_signature_replay_protection() {
// Same signature twice should fail
}
}
集成测试(与 L1 配合)
// Test full L1-L2 flow
#[test]
fn test_deposit_withdraw_roundtrip() {
// 1. Deposit on L1
// 2. Wait for L2 processing
// 3. Verify L2 balance
// 4. Withdraw to L1
// 5. Verify L1 balance restored
}
Caracal CI 集成
# .github/workflows/security.yml
- name: Run Caracal
run: |
# Rebuilds from source each run; cache ~/.cargo or pin a release binary instead.
cargo install --git https://github.com/crytic/caracal --profile release --force
caracal detect src/ --fail-on high,critical
11. 其他资源
- 构建安全合约:
building-secure-contracts/not-so-smart-contracts/cairo/ - Caracal:https://github.com/crytic/caracal
- Cairo 文档:https://book.cairo-lang.org/
- StarkNet 文档:https://docs.starknet.io/
- OpenZeppelin Cairo 合约:https://github.com/OpenZeppelin/cairo-contracts
12. 快速参考清单
完成 Cairo/StarkNet 审计前:
算术安全(高):
- 不使用 felt252 表示余额/金额(使用 u128/u256)
- 或者 felt252 算术有明确的边界检查
- 已测试溢出/下溢场景
L1 处理函数安全(严重):
- 所有
#[l1_handler]函数都验证from_address - 将 from_address 与存储的 L1 合约地址进行比较
- 无法通过部署其他 L1 合约绕过检查
L1-L2 消息传递(高):
- L1 桥验证地址 < STARKNET_FIELD_PRIME
- L1 桥实现消息取消功能
- L2 处理函数检查 from_address
- L1 ↔ L2 验证规则对称
- 已测试完整往返流程
签名安全(高):
- 签名包含 nonce 跟踪
- 每次使用后递增 nonce
- 域分隔符包含链 ID 和合约地址
- 已测试并阻止签名重放
- 已阻止跨链重放
工具使用:
- 已完成 Caracal 扫描,且没有严重发现
- 单元测试涵盖所有漏洞场景
- 集成测试验证 L1-L2 流程
- 主网部署前已在测试网上测试
- 已输出覆盖情况表,包含全部 6 行,每行均标注
found、clear或n/a,并说明原因
13. 应拒绝的自我合理化
- “合约很小,所以大多数模式显然不适用。” 对谁来说显然?写一句
n/a就能记录判断,便于审查。沉默不会留下任何记录,读者也无法区分你是检查过,还是根本没检查。 - “Caracal 没有报告问题,所以合约没有问题。” Caracal 只覆盖这 6 种模式中的一部分,完全无法检查逻辑层面的模式。工具运行结果干净,只是一条证据,并不代表对它从未检查过的模式做出了结论。请说明它覆盖了哪些模式。
- “我检查了这个合约中重要的模式。” 判断哪些模式重要,本身就是扫描的一部分,而不是开始扫描的前提。应在完成表格后再按严重性排序,而不是漏掉某些行。
- “没有发现问题,所以没什么可报告的。” 即使扫描结果为零,也应输出完整的覆盖表。该表就是交付物:它能区分经过检查的合约和只被粗略看过的合约。
- “Cairo 1 原生提供溢出检查,所以算术运算是安全的。” 请明确类型。
felt252的行为与定宽整数类型不同,而两者之间的边界正是模式 4 涉及的地方。 - “L1 端会验证这一点。” 那就引用 L1 合约。你认为桥接两端存在的检查,在亲自阅读之前都只是假设;未经验证的
from_address是 StarkNet 桥接漏洞的典型例子。