name: multiversx-security-audit description: MultiversX智能合约的完整安全审计方法论。涵盖上下文构建、入口点分析、静态分析模式和自动Semgrep扫描。用于执行安全审计、代码审查或设置自动漏洞检测时。
MultiversX安全审计方法论
一个序列化的工作流程,用于审计MultiversX智能合约,从初始侦察到自动扫描。
使用时机
- 开始新的安全审计项目
- 进行安全代码审查
- 设置自动漏洞扫描
- 映射渗透测试的攻击面
- 培训新的安全审查员
第一阶段:上下文构建
在深入漏洞狩猎前,快速建立代码库的全面心智模型。
1.1 侦察清单
- [ ] 核心逻辑:
#[multiversx_sc::contract],#[payable], 价值移动函数,访问控制模式 - [ ] 外部性:跨合约调用,硬编码地址(
sc:字面量),oracle依赖,桥接合约 - [ ] 文档:
README.md,specs/,sc-config.toml,multiversx.yaml,snippets.sh,scenarios/测试
1.2 系统映射
角色和权限
| 角色 | 能力 | 如何分配 |
|---|---|---|
| 所有者 | 完全管理访问 | 部署时,可转移 |
| 管理员 | 有限管理功能 | 所有者授予 |
| 用户 | 公共端点 | 任何人 |
| 白名单 | 特殊访问 | 管理员授予 |
资产清单
| 资产类型 | 示例 | 风险级别 |
|---|---|---|
| EGLD | 原生货币 | 关键 |
| 可替代ESDT | 自定义代币 | 高 |
| NFT/SFT | 非同质化代币 | 中高 |
| Meta-ESDT | 带元数据的代币 | 中高 |
状态分析
记录所有存储映射器及其用途:
// 示例状态清单
#[storage_mapper("owner")] // SingleValueMapper - 访问控制
#[storage_mapper("balances")] // MapMapper - 用户资金(关键)
#[storage_mapper("whitelist")] // SetMapper - 特权用户
1.3 入口点枚举(初始)
列出所有 #[endpoint] 函数及其风险分类:
高风险:
- deposit() - #[payable("*")] - 接受价值
- withdraw() - 移出资金
- upgrade() - 可以更改合约逻辑
中风险:
- setConfig() - 仅所有者,更改行为
- addWhitelist() - 扩展权限
低风险:
- getBalance() - #[view] - 只读
1.4 威胁建模(初始)
资产风险分析
- 直接损失:如果合约失败,哪些资金可能被盗?
- 间接损失:哪些下游系统依赖此合约?
- 声誉损失:可能发生哪些非财务损害?
攻击者画像
| 攻击者 | 动机 | 能力 |
|---|---|---|
| 外部用户 | 利润 | 仅公共端点 |
| 恶意管理员 | 内部威胁 | 管理员功能 |
| 可重入合约 | 利用回调 | 跨合约调用 |
| 前跑者 | MEV提取 | 交易排序 |
1.4 环境检查
依赖审计
- 框架版本:检查
Cargo.toml中的multiversx-sc版本 - 已知漏洞:对照安全公告进行比较
- 已弃用API:查找弃用函数的使用
测试套件评估
- 覆盖率:
scenarios/是否存在全面的测试? - 边缘情况:是否测试了失败路径?
- 新鲜度:运行
sc-meta test-gen验证测试是否匹配当前代码
1.5 上下文构建输出
完成上下文构建后,记录:
- 系统概述:一段话总结合约功能
- 信任边界:谁信任谁,存在哪些假设
- 关键路径:最安全敏感的代码路径
- 初始关注点:需要深入审查的初步区域列表
- 团队问题:需要开发人员澄清的事项
上下文构建清单
- [ ] 所有入口点已识别和分类
- [ ] 存储布局已记录
- [ ] 外部依赖已映射
- [ ] 角色/权限模型已理解
- [ ] 测试覆盖率已评估
- [ ] 框架版本已记录
- [ ] 初始威胁模型已起草
第二阶段:入口点分析
通过枚举所有公共交互点并分类其风险级别,识别完整的攻击面。
2.1 入口点识别
暴露函数的多维扩展宏
| 宏 | 可见性 | 风险级别 | 描述 |
|---|---|---|---|
#[endpoint] |
公共写 | 高 | 状态更改的公共函数 |
#[view] |
公共读 | 低 | 只读公共函数 |
#[payable] |
接受任何代币 | 关键 | 处理价值转移 |
#[payable("EGLD")] |
仅接受EGLD | 关键 | 处理原生货币 |
#[init] |
仅部署 | 中 | 构造函数(运行一次) |
#[upgrade] |
仅升级 | 关键 | 迁移逻辑 |
#[callback] |
内部 | 高 | 异步调用响应处理器 |
#[only_owner] |
所有者限制 | 中 | 管理员函数 |
扫描命令
# 查找所有端点
grep -n "#\[endpoint" src/*.rs
# 查找所有可支付端点
grep -n "#\[payable" src/*.rs
# 查找所有视图
grep -n "#\[view" src/*.rs
# 查找回调
grep -n "#\[callback" src/*.rs
# 查找初始化和升级
grep -n "#\[init\]\|#\[upgrade\]" src/*.rs
2.2 风险分类
A类:可支付端点(关键风险)
接收价值的函数需要最多审查。
#[payable]
#[endpoint]
fn deposit(&self) {
// 必须检查:
// 1. 代币标识符验证
// 2. 金额 > 0 验证
// 3. 多代币转移的正确处理
// 4. 外部调用前的状态更新
let payment = self.call_value().single();
require!(
payment.token_identifier == self.accepted_token().get(),
"错误代币"
);
// 处理存款...
}
可支付端点清单:
- [ ] 代币ID与预期代币验证
- [ ] 金额检查最小/最大边界
- [ ] 如果使用
all(),多转移处理 - [ ] NFT/SFT的非ce验证
- [ ] 可重入保护(检查-效果-交互)
B类:非支付状态更改端点(高风险)
#[endpoint]
fn update_config(&self, new_value: BigUint) {
// 必须检查:
// 1. 谁可以调用此?(访问控制)
// 2. 输入验证
// 3. 状态转换有效性
self.require_caller_is_admin();
require!(new_value > 0, "无效值");
self.config().set(new_value);
}
状态更改端点清单:
- [ ] 访问控制实施且正确
- [ ] 所有参数的输入验证
- [ ] 状态转换有效
- [ ] 重要更改的事件发射
- [ ] 无DoS向量(无界循环等)
C类:视图函数(低风险)
#[view(getBalance)]
fn get_balance(&self, user: ManagedAddress) -> BigUint {
// 应检查:
// 1. 是否实际修改状态?(内部可变性)
// 2. 是否泄漏敏感信息?
// 3. 计算是否昂贵(通过gas的DoS)?
self.balances(&user).get()
}
视图函数清单:
- [ ] 无状态修改(验证无存储写入)
- [ ] 无敏感数据暴露
- [ ] 有界计算(无无界循环)
- [ ] 区块信息使用适当(
get_block_timestamp_millis()/get_block_timestamp_seconds()可能链下不同)
D类:初始化和升级(关键风险)
#[init]
fn init(&self, admin: ManagedAddress) {
// 必须检查:
// 1. 所有必需状态已初始化
// 2. 无法重新初始化
// 3. 管理员/所有者正确设置
self.admin().set(admin);
}
#[upgrade]
fn upgrade(&self) {
// 必须检查:
// 1. 新存储映射器已初始化
// 2. 存储布局兼容性
// 3. 迁移逻辑正确
}
E类:回调(高风险)
#[callback]
fn transfer_callback(
&self,
#[call_result] result: ManagedAsyncCallResult<()>
) {
// 必须检查:
// 1. 错误处理(不要假设成功)
// 2. 失败时的状态回滚
// 3. 原始调用的正确识别
match result {
ManagedAsyncCallResult::Ok(_) => {
// 成功路径
},
ManagedAsyncCallResult::Err(_) => {
// 关键:必须处理失败!
// 回滚原始调用的任何状态更改
}
}
}
2.3 分析工作流
步骤1:列出所有入口点
| 端点 | 类型 | 可支付 | 访问 | 存储触及 | 风险 |
|----------|------|---------|--------|-----------------|------|
| deposit | endpoint | * | 公共 | balances | 关键 |
| withdraw | endpoint | 否 | 公共 | balances | 关键 |
| setAdmin | endpoint | 否 | 所有者 | admin | 高 |
| getBalance | view | 否 | 公共 | balances(读) | 低 |
| init | init | 否 | 部署 | admin, config | 中 |
步骤2:标记访问控制
// 公共 - 任何人都可以调用
#[endpoint]
fn public_function(&self) { }
// 仅所有者 - 区块链所有者
#[only_owner]
#[endpoint]
fn owner_function(&self) { }
// 仅管理员 - 自定义访问控制
#[endpoint]
fn admin_function(&self) {
self.require_caller_is_admin();
}
// 白名单 - 地址在集合中
#[endpoint]
fn whitelist_function(&self) {
let caller = self.blockchain().get_caller();
require!(self.whitelist().contains(&caller), "未在白名单中");
}
步骤3:标记价值处理
| 标签 | 含义 | 示例 |
|---|---|---|
| 可拒绝 | 拒绝支付 | 默认(无 #[payable]) |
| 仅EGLD | 接受EGLD | #[payable("EGLD")] |
| 仅代币 | 特定ESDT | #[payable("TOKEN-abc123")] |
| 任何代币 | 任何支付 | #[payable] |
| 多代币 | 多支付 | 使用 all() |
步骤4:图形数据流
deposit() ──写入──▶ balances
──写入──▶ total_deposited
──读取───▶ accepted_token
withdraw() ──读取/写入──▶ balances
──读取────────▶ withdrawal_fee
getBalance() ──读取──▶ balances
2.4 特定攻击向量
权限升级
// 易受攻击:缺少访问控制
#[endpoint]
fn set_admin(&self, new_admin: ManagedAddress) {
self.admin().set(new_admin); // 任何人都可以成为管理员!
}
// 正确:受保护
#[only_owner]
#[endpoint]
fn set_admin(&self, new_admin: ManagedAddress) {
self.admin().set(new_admin);
}
通过无界增长的DoS
// 易受攻击:公共端点添加到无界集合
#[endpoint]
fn register(&self) {
let caller = self.blockchain().get_caller();
self.participants().insert(caller); // 永远增长!
}
缺少支付验证
坏:
// 不要:接受任何代币而不验证 — 攻击者发送无价值代币
#[payable]
#[endpoint]
fn stake(&self) {
let payment = self.call_value().single();
self.staked().update(|s| *s += payment.amount.as_big_uint()); // 接受假代币!
}
好:
// 做:验证代币身份并可选执行最小金额
#[payable]
#[endpoint]
fn stake(&self) {
let payment = self.call_value().single();
require!(
payment.token_identifier == self.staking_token().get(),
"错误代币"
);
self.staked().update(|s| *s += payment.amount.as_big_uint());
}
回调状态假设
// 易受攻击:假设成功
#[callback]
fn on_transfer_complete(&self) {
// 即使转移失败也运行!
self.transfer_count().update(|c| *c += 1);
}
2.5 入口点分析输出模板
# 入口点分析:[合约名称]
## 摘要
- 总端点:X
- 可支付端点:Y(关键)
- 状态更改:Z(高)
- 视图:W(低)
## 访问控制矩阵
| 端点 | 公共 | 所有者 | 管理员 | 白名单 |
|----------|--------|-------|-------|-----------|
| deposit | 是 | - | - | - |
| setAdmin | - | 是 | - | - |
## 推荐关注领域
1. [最高优先级端点和原因]
2. [第二优先级]
3. [第三优先级]
第三阶段:静态分析模式
用于在MultiversX Rust和Go代码中查找漏洞的手动和自动静态分析。
3.1 Rust智能合约(multiversx-sc)
关键Grep模式
不安全代码
# 不安全块 - 仅对FFI或特定优化有效
grep -rn "unsafe" src/
风险:内存损坏,未定义行为
行动:要求每个 unsafe 块的正当理由
恐慌诱导器
# 直接unwrap - 可能恐慌
grep -rn "\.unwrap()" src/
# Expect - 也恐慌
grep -rn "\.expect(" src/
# 索引访问 - 可能越界恐慌
grep -rn "\[.*\]" src/ | grep -v "storage_mapper"
风险:合约停止,潜在DoS
行动:用 unwrap_or_else(|| sc_panic!(...)) 或正确错误处理替换
浮点算术
grep -rn "f32" src/
grep -rn "f64" src/
grep -rn "as f32\|as f64" src/
风险:非确定性行为,共识失败
行动:所有计算使用 BigUint/BigInt
未检查算术
grep -rn "[^_a-zA-Z]\+ [^_a-zA-Z]" src/ # 加法
grep -rn "[^_a-zA-Z]\- [^_a-zA-Z]" src/ # 减法
grep -rn "[^_a-zA-Z]\* [^_a-zA-Z]" src/ # 乘法
风险:整数溢出/下溢
行动:所有财务计算使用 BigUint 或检查算术
映射迭代(DoS风险)
grep -rn "\.iter()" src/
grep -rn "for.*in.*\.iter()" src/
grep -rn "\.collect()" src/
风险:气体耗尽DoS 行动:添加分页或边界检查
逻辑模式分析(手动审查)
代币ID验证
grep -rn "call_value()" src/
grep -rn "\.single()" src/
grep -rn "\.all()" src/
grep -rn "\.array()" src/
grep -rn "\.single_optional()" src/
# 遗留模式(可能在旧代码中)
grep -rn "all_esdt_transfers" src/
grep -rn "single_esdt" src/
对于每个出现,验证:
- [ ] 代币ID对照预期值检查(使用
TokenId比较) - [ ] 代币nonce验证(对于NFT/SFT)
- [ ] 金额验证(在边界内 — 非零由
Payment中的NonZeroBigUint保证)
// 易受攻击
#[payable]
fn deposit(&self) {
let payment = self.call_value().single();
self.balances().update(|b| *b += payment.amount.as_big_uint());
// 无代币ID检查!接受任何代币
}
// 安全
#[payable]
fn deposit(&self) {
let payment = self.call_value().single();
require!(
payment.token_identifier == self.accepted_token().get(),
"错误代币"
);
// 注意:不再需要金额 > 0 检查 — Payment.amount 是 NonZeroBigUint
self.balances().update(|b| *b += payment.amount.as_big_uint());
}
回调状态假设
grep -rn "#\[callback\]" src/
对于每个回调,验证:
- [ ] 不假设异步调用成功
- [ ] 显式处理错误情况
- [ ] 失败时如果需要,回滚状态更改
访问控制
grep -rn "#\[endpoint\]" src/
grep -rn "#\[only_owner\]" src/
对于每个端点,验证:
- [ ] 应用了适当的访问控制
- [ ] 限制敏感操作
- [ ] 管理员函数已记录
可重入(CEI模式)
grep -rn "\.send()\." src/
grep -rn "\.tx()" src/
grep -rn "async_call" src/
验证检查-效果-交互模式:
- [ ] 所有检查(require!)在状态更改前
- [ ] 状态更改在外部调用前
- [ ] 同一函数中外部调用后无状态更改
3.2 Go协议代码(mx-chain-go)
并发问题
Goroutine循环变量捕获
grep -rn "go func" *.go
// 易受攻击
for _, item := range items {
go func() {
process(item) // item 可能已更改!
}()
}
// 安全
for _, item := range items {
item := item // 创建本地副本
go func() {
process(item)
}()
}
映射竞争条件
grep -rn "map\[" *.go | grep -v "sync.Map"
确定性问题
映射迭代顺序
grep -rn "for.*range.*map" *.go
Go中的映射迭代是随机的。切勿用于生成哈希、创建共识数据或任何确定性输出。
时间函数
grep -rn "time.Now()" *.go
块处理中禁止 time.Now() — 使用 block.Header.TimeStamp 替代。
3.3 分析清单
智能合约审查清单
访问控制
- [ ] 所有端点有适当的访问限制
- [ ] 所有者/管理员函数使用
#[only_owner]或显式检查 - [ ] 无权限升级路径
支付处理
- [ ] 所有
#[payable]端点验证代币ID - [ ] 金额验证(非零,边界)
- [ ] 适用时验证NFT nonce
算术
- [ ] 无对 u64/i64 的外部输入原始算术
- [ ] 财务计算使用 BigUint
- [ ] 无浮点
状态管理
- [ ] 遵循检查-效果-交互模式
- [ ] 回调处理失败情况
- [ ] 存储布局升级安全
气体和DoS
- [ ] 无无界迭代
- [ ] 存储增长有界
- [ ] 大数据集的分页
错误处理
- [ ] 无
unwrap()无正当理由 - [ ] 有意义的错误消息
- [ ] 一致错误处理模式
协议审查清单
并发
- [ ] 所有共享状态正确同步
- [ ] 无goroutine循环变量捕获错误
- [ ] 通道使用正确
确定性
- [ ] 无用于共识数据的映射迭代
- [ ] 块处理中无
time.Now() - [ ] 无随机数生成无确定性种子
内存安全
- [ ] 切片边界检查
- [ ] 无空指针解引用
- [ ] 适当错误处理
3.4 漏洞类别快速参考
| 类别 | Grep模式 | 严重性 |
|---|---|---|
| 不安全代码 | unsafe |
关键 |
| 浮点算术 | f32|f64 |
关键 |
| 恐慌诱导器 | unwrap()|expect( |
高 |
| 无界迭代 | \.iter() |
高 |
| 缺少访问控制 | #[endpoint] 无 #[only_owner] |
高 |
| 代币验证 | call_value().single() 无代币ID require |
高 |
| 回调假设 | #[callback] 无错误处理 |
中 |
| 原始算术 | + | - | * 在 u64 |
中 |
第四阶段:使用Semgrep的自动扫描
创建自定义Semgrep规则,自动检测MultiversX特定的安全模式和最佳实践违规。
4.1 Rust的Semgrep基础
规则结构
rules:
- id: rule-identifier
languages: [rust]
message: "问题描述及其重要性"
severity: ERROR # ERROR, WARNING, INFO
patterns:
- pattern: <要匹配的代码模式>
metadata:
category: security
technology:
- multiversx
模式语法
| 语法 | 含义 | 示例 |
|---|---|---|
$VAR |
任何表达式 | $X + $Y 匹配 a + b |
... |
零个或多个语句 | { ... } 匹配任何块 |
$...VAR |
零个或多个参数 | func($...ARGS) |
<... $X ...> |
包含表达式 | <... panic!(...) ...> |
4.2 常见MultiversX规则
不安全算术检测
rules:
- id: mvx-unsafe-addition
languages: [rust]
message: "潜在算术溢出。财务计算使用 BigUint 或检查算术。"
severity: ERROR
patterns:
- pattern: $X + $Y
- pattern-not: $X.checked_add($Y)
- pattern-not: BigUint::from($X) + BigUint::from($Y)
paths:
include:
- "*/src/*.rs"
metadata:
category: security
subcategory: arithmetic
cwe: "CWE-190: 整数溢出"
- id: mvx-unsafe-multiplication
languages: [rust]
message: "潜在乘法溢出。使用 BigUint 或 checked_mul。"
severity: ERROR
patterns:
- pattern: $X * $Y
- pattern-not: $X.checked_mul($Y)
- pattern-not: BigUint::from($X) * BigUint::from($Y)
浮点检测
rules:
- id: mvx-float-forbidden
languages: [rust]
message: "浮点算术是非确定性的,智能合约中禁止使用。"
severity: ERROR
pattern-either:
- pattern: "let $X: f32 = ..."
- pattern: "let $X: f64 = ..."
- pattern: "$X as f32"
- pattern: "$X as f64"
metadata:
category: security
subcategory: determinism
可支付端点无值检查
rules:
- id: mvx-payable-no-check
languages: [rust]
message: "可支付端点未检查支付值。验证代币ID和金额。"
severity: WARNING
patterns:
- pattern: |
#[payable]
#[endpoint]
fn $FUNC(&self, $...PARAMS) {
$...BODY
}
- pattern-not: |
#[payable]
#[endpoint]
fn $FUNC(&self, $...PARAMS) {
<... self.call_value() ...>
}
metadata:
category: security
subcategory: input-validation
不安全Unwrap使用
rules:
- id: mvx-unsafe-unwrap
languages: [rust]
message: "unwrap() 可能恐慌。使用 unwrap_or_else 与 sc_panic! 或正确错误处理。"
severity: ERROR
patterns:
- pattern: $EXPR.unwrap()
- pattern-not-inside: |
#[test]
fn $FUNC() { ... }
fix: "$EXPR.unwrap_or_else(|| sc_panic!(\"错误消息\"))"
metadata:
category: security
subcategory: error-handling
缺少所有者检查
rules:
- id: mvx-sensitive-no-owner-check
languages: [rust]
message: "敏感操作无所有者检查。添加 #[only_owner] 或显式验证。"
severity: ERROR
patterns:
- pattern: |
#[endpoint]
fn $FUNC(&self, $...PARAMS) {
<... self.$MAPPER().set(...) ...>
}
- pattern-not: |
#[only_owner]
#[endpoint]
fn $FUNC(&self, $...PARAMS) { ... }
- pattern-not: |
#[endpoint]
fn $FUNC(&self, $...PARAMS) {
<... self.blockchain().get_owner_address() ...>
}
- metavariable-regex:
metavariable: $MAPPER
regex: "(admin|owner|config|fee|rate)"
4.3 高级模式
回调无错误处理
rules:
- id: mvx-callback-no-error-handling
languages: [rust]
message: "回调未处理错误情况。异步调用失败将静默继续。"
severity: ERROR
patterns:
- pattern: |
#[callback]
fn $FUNC(&self, $...PARAMS) {
$...BODY
}
- pattern-not: |
#[callback]
fn $FUNC(&self, #[call_result] $RESULT: ManagedAsyncCallResult<$TYPE>) {
...
}
无界迭代
rules:
- id: mvx-unbounded-iteration
languages: [rust]
message: "无边界地迭代存储映射器。可能导致通过气体耗尽的DoS。"
severity: ERROR
pattern-either:
- pattern: self.$MAPPER().iter()
- pattern: |
for $ITEM in self.$MAPPER().iter() {
...
}
metadata:
category: security
subcategory: dos
cwe: "CWE-400: 不受控制的资源消耗"
存储键碰撞风险
rules:
- id: mvx-storage-key-short
languages: [rust]
message: "存储键非常短,增加碰撞风险。使用描述性键。"
severity: WARNING
patterns:
- pattern: '#[storage_mapper("$KEY")]'
- metavariable-regex:
metavariable: $KEY
regex: "^.{1,3}$"
可重入模式检测
rules:
- id: mvx-reentrancy-risk
languages: [rust]
message: "状态更新前的外部调用。遵循检查-效果-交互模式。"
severity: ERROR
patterns:
- pattern: |
fn $FUNC(&self, $...PARAMS) {
...
self.send().$SEND_METHOD(...);
...
self.$STORAGE().set(...);
...
}
- pattern: |
fn $FUNC(&self, $...PARAMS) {
...
self.tx().to(...).transfer();
...
self.$STORAGE().set(...);
...
}
4.4 从发现创建规则
工作流
- 手动发现错误 在审计期间
- 抽象模式 - 什么使这成为错误?
- 编写Semgrep规则 捕获类似问题
- 在代码库上测试 - 查找所有变体
- 细化以减少误报
示例:从错误到规则
发现错误:
#[endpoint]
fn withdraw(&self, amount: BigUint) {
let caller = self.blockchain().get_caller();
self.send().direct_egld(&caller, &amount); // 发送前余额检查!
self.balances(&caller).update(|b| *b -= &amount); // 可能下溢
}
创建规则:
rules:
- id: mvx-withdraw-pattern-unsafe
languages: [rust]
message: "提现发送资金在更新余额前。可重入和下溢风险。"
severity: ERROR
patterns:
- pattern: |
fn $FUNC(&self, $...PARAMS) {
...
self.send().$METHOD(...);
...
self.$BALANCE(...).update(|$B| *$B -= ...);
...
}
- pattern: |
fn $FUNC(&self, $...PARAMS) {
...
self.tx().to(...).transfer();
...
self.$BALANCE(...).update(|$B| *$B -= ...);
...
}
4.5 运行Semgrep
# 运行单个规则
semgrep --config rules/mvx-unsafe-arithmetic.yaml src/
# 运行目录中的所有规则
semgrep --config rules/ src/
# 输出JSON用于处理
semgrep --config rules/ --json -o results.json src/
# 忽略测试文件
semgrep --config rules/ --exclude="*_test.rs" --exclude="tests/" src/
CI/CD集成
# GitHub Actions示例
- name: 运行Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
rules/mvx-security.yaml
rules/mvx-best-practices.yaml
4.6 规则库组织
semgrep-rules/
├── security/
│ ├── mvx-arithmetic.yaml # 溢出/下溢
│ ├── mvx-access-control.yaml # 认证问题
│ ├── mvx-reentrancy.yaml # CEI违规
│ └── mvx-input-validation.yaml
├── best-practices/
│ ├── mvx-storage.yaml # 存储模式
│ ├── mvx-gas.yaml # 气体优化
│ └── mvx-error-handling.yaml
└── style/
├── mvx-naming.yaml # 命名约定
└── mvx-documentation.yaml # 文档要求
4.7 测试规则
测试文件格式
# test/mvx-unsafe-unwrap.test.yaml
rules:
- id: mvx-unsafe-unwrap
# ... 规则定义 ...
# 测试用例
test_cases:
- name: "应匹配unwrap"
code: |
fn test() {
let x = some_option.unwrap();
}
should_match: true
- name: "不应匹配unwrap_or_else"
code: |
fn test() {
let x = some_option.unwrap_or_else(|| sc_panic!("Error"));
}
should_match: false
运行测试
semgrep --test rules/
4.8 规则编写最佳实践
- 从具体开始,然后泛化:从精确模式开始,谨慎放宽约束
- 包含修复建议:当自动修复安全时使用
fix:字段 - 记录“为什么”:消息应解释影响,而不仅仅是检测到什么
- 包含CWE引用:链接到标准漏洞分类
- 用真实代码库测试:针对实际MultiversX项目验证
- 版本化规则:规则随框架API变化而演变
- 按严重性分类:安全用ERROR,最佳实践用WARNING