MultiversX安全审计技能Skill multiversx-security-audit

这个技能提供了对MultiversX区块链平台的智能合约进行安全审计的完整方法论,涵盖从上下文构建到自动化扫描的四个阶段。它用于执行全面的安全审计、代码审查、漏洞检测和自动扫描设置。关键词:MultiversX、智能合约、安全审计、漏洞挖掘、Semgrep扫描、区块链安全、智能合约审计、安全方法论。

安全审计 0 次安装 0 次浏览 更新于 3/21/2026

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 上下文构建输出

完成上下文构建后,记录:

  1. 系统概述:一段话总结合约功能
  2. 信任边界:谁信任谁,存在哪些假设
  3. 关键路径:最安全敏感的代码路径
  4. 初始关注点:需要深入审查的初步区域列表
  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 从发现创建规则

工作流

  1. 手动发现错误 在审计期间
  2. 抽象模式 - 什么使这成为错误?
  3. 编写Semgrep规则 捕获类似问题
  4. 在代码库上测试 - 查找所有变体
  5. 细化以减少误报

示例:从错误到规则

发现错误:

#[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 规则编写最佳实践

  1. 从具体开始,然后泛化:从精确模式开始,谨慎放宽约束
  2. 包含修复建议:当自动修复安全时使用 fix: 字段
  3. 记录“为什么”:消息应解释影响,而不仅仅是检测到什么
  4. 包含CWE引用:链接到标准漏洞分类
  5. 用真实代码库测试:针对实际MultiversX项目验证
  6. 版本化规则:规则随框架API变化而演变
  7. 按严重性分类:安全用ERROR,最佳实践用WARNING