社区包:
o1js-scan已收录于官方 o1js 社区包 目录。
最新版本:0.22.0 — 一个本地 MCP 服务器 让 Claude Code、Cursor 及其他 AI 编程助手在你编写 zkApp 时运行扫描器:
pip install 'o1js-scan[mcp]'。它通过 stdio 在你的机器上运行, 不会上传任何内容。未分析任何文件的扫描是错误,而非干净的结果; 没有发现问题的结果也声明其并非安全保证。核心扫描器仍然没有依赖。 参见 CHANGELOG。
一个快速、无依赖的静态分析器,用于检测以下环境中的 zk 电路可靠性缺陷:
.ts / .js)— 来自 @method 函数体的 Kimchi 电路.nr)— Aztec 的类 Rust ZK DSL(包括 aztec-nr 形态的模式)安全关键缺陷通常不在证明系统中——而在应用自身的约束中:
证明者控制但电路从未绑定的见证。o1js-scan 是 Mina 和 Noir 生态中
Circom 表亲的欠约束信号扫描器。
另见:gnark-safety(gnark 电路)、vk-guard (验证密钥回归)。
pip install o1js-scan
# or: pipx install o1js-scan
# or: npm install -D o1js-scan
o1js-scan path/to/zkapp # o1js + Noir (auto)
noir-scan path/to/circuits # same binary — Noir-friendly alias
noir-scan . --lang noir --fail-on high --sarif noir.sarif
给定一个 vault,其 withdraw 金额是由 prover 控制的 witness,且从未绑定到链上状态:
$ o1js-scan examples/vulnerable_vault.ts --include-examples
LOW O1JS_UNCONSTRAINED_RECIPIENT vulnerable_vault.ts:23 fn=withdraw Recipient `to` is prover-chosen in `withdraw`
HIGH O1JS_UNCONSTRAINED_WITNESS vulnerable_vault.ts:23 fn=withdraw Unconstrained witness `amount` flows to send_amount in `withdraw`
o1js-scan: 2 finding(s) [1 high, 1 low] in 1 of 1 file(s) — fails (--fail-on high)
$ echo $?
1
--include-examples 在这里是必需的,仅仅是因为演示文件位于
examples/ 下,路径分类器默认会将其降级,这样仓库
自身的示例代码就不会导致构建失败。同样的合约放在你的 src/ 中
会报告 HIGH,无需任何标志。
HIGH 发现是可耗尽漏洞。修复后的合约
(examples/safe_vault.ts)将其消除并退出 0,仅保留关于证明者选择接收者的
信息性 LOW:
$ o1js-scan examples/safe_vault.ts --include-examples
LOW O1JS_UNCONSTRAINED_RECIPIENT safe_vault.ts:23 fn=withdraw Recipient `to` is prover-chosen in `withdraw`
o1js-scan: 1 finding(s) [1 low] in 1 of 1 file(s) — passes (--fail-on high)
$ echo $?
0
参见 examples/ 中的 o1js 和 Noir 漏洞/修复对。
pip install o1js-scan
对于隔离的全局 CLI 安装,请使用 pipx:
pipx install o1js-scan
对于基于 Node/npm 的 Noir、Aztec 或 o1js 应用仓库,请安装 npm 封装包:
npm install -D o1js-scan
npx noir-scan . --lang noir --fail-on high
npm 包是对同一个 Python 分析器的轻量封装,需要在 PATH 中提供 Python 3.8+(python3 或 python)。设置 O1JS_SCAN_PYTHON 以选择特定的解释器。
或者从源码安装:
git clone https://github.com/auditinfra-io/o1js-scan
cd o1js-scan
pip install -e .
无第三方 Python 依赖。Python 3.8+。noir-scan 控制台脚本与 o1js-scan 一同安装(同一入口点),包括通过 npm 包装器安装。
# scan a directory (recursively; skips node_modules, target/, .git, …)
o1js-scan path/to/project
# Noir-only / o1js-only
noir-scan circuits --lang noir
o1js-scan src --lang o1js
# scan a single file
o1js-scan src/MyContract.ts
noir-scan src/main.nr
# machine-readable output for CI
o1js-scan src --json
# SARIF 2.1.0 for GitHub code scanning (writes o1js-scan.sarif by default)
o1js-scan src --sarif
noir-scan . --lang noir --sarif noir.sarif
# choose which severity fails CI (critical|high|medium|low|none; default high)
o1js-scan src --fail-on medium
# progressive/power-user gate (equivalent to --fail-on medium)
o1js-scan src --strict
# test code is excluded by default (both backends); opt back in
o1js-scan src --include-tests
# example code is downgraded to LOW by default; keep original severity
o1js-scan src --include-examples
# a path with no o1js / Noir source exits 2; opt out where that is expected
o1js-scan packages/ --allow-empty
o1js-scan --version
当存在达到或超过 --fail-on 级别(默认 high)的发现时,退出码为 1,否则为 0 —— 因此你可以直接将其接入 CI。在默认设置下,低/中等级别的发现(包括下面的信息性接收者规则)不会导致构建失败;使用 --fail-on none 仅报告,或使用 --strict(--fail-on medium 的简写)进行更严格的把关,同时仍将低严重性发现视为建议性。这两个选项互斥,因此 CI 配置不会产生歧义。缺失扫描路径会以退出码 2 退出,并在 stderr 上输出错误,因此拼写错误不会静默地作为干净运行通过 CI。路径存在但不包含分析器可读取的 o1js 或 Noir 源代码时也是如此(例如将 --lang noir 指向一个 o1js 项目);在这种情况下,如果合理,请传入 --allow-empty。每次运行都会向 stderr 打印一行摘要(按严重性计数和门控判定)。
默认排除测试代码 —— 两个后端均如此。 测试会故意构造无效值和错误交易,以证明断言会拒绝它们,因此那里的发现正是测试的目的,而不是电路缺陷。符合以下条件时,文件被视为测试代码:
*.test.ts / *.spec.ts(以及 .js/.jsx/.tsx/.mjs/.cjs 变体),或 *_test.nr / test_*.nr;test/、tests/、__tests__/、spec/ 或 __mocks__/ 目录下;#[test] / #[test(...)] 属性,或位于 mod test { … } / mod tests { … } 块内 —— 块作用域,因此生产文件底部的测试模块不会使文件其余部分静默。传入 --include-tests 可报告它们。
示例代码会被降级,而不是丢弃。 位于 examples/ 或 example/ 目录中,或位于名为 *.eg.ts(以及 .nr 和其他 JS/TS 扩展名)的文件中的发现,会被降级为 LOW 并附带说明 —— 仍会报告,但不再能使构建失败。示例代码是故意简化的,将框架自身的示例标记为漏洞是噪音;但它被复制到生产环境的频率远高于测试代码,这就是它被降级而不是隐藏的原因。传入 --include-examples 可保留原始严重性。
只要任一策略适用,运行就会向 stderr 打印一行说明 —— 例如 6 file(s) skipped as test code, 1 finding(s) downgraded as examples —— 因此安静的扫描永远不会静默无声。计数也会出现在 SARIF 的 invocation.properties 下。注意权衡:检测仅基于路径(不解析 describe(/it(),因此存储在 tests/ 下的生产电路会被跳过 —— stderr 行就是让你注意到这一点的方式。
遍历目录树时跳过的目录: node_modules、target(nargo)、.git、dist、build、__pycache__、.venv、venv。
在不放宽门控的情况下,通过在被标记行上或其上一行添加内联注释,来静默你已分类的发现:
this.send({ to, amount }); // o1js-scan-disable-line O1JS_UNCONSTRAINED_WITNESS
// o1js-scan-disable-next-line
this.send({ to, amount });
let inv = unsafe { hint(x) }; // o1js-scan-disable-line NOIR_UNCONSTRAINED_WITNESS
列出一个或多个规则 ID 以仅抑制这些规则;一个裸指令(无 ID)会抑制目标行上的所有规则。
作为库使用:
from o1js_scan import analyze_file, analyze_project
for path, finding in analyze_project("src", lang="auto"):
print(path, finding.rule_id, finding.severity.value, finding.title)
只需几行即可将扫描器添加到 CI 中。发现的问题会以注释形式显示在 PR 差异中,并作为警报出现在仓库的 Security → Code scanning 标签页中。
# .github/workflows/o1js-scan.yml
name: o1js-scan
on: [push, pull_request]
permissions:
contents: read
security-events: write # required to upload SARIF to code scanning
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: auditinfra-io/[email protected]
with:
path: src # optional, defaults to the repo root
lang: auto # auto | o1js | noir
# version: 0.22.0 # optional, pin the scanner version
# fail-on: high # optional, fail the job on high/critical
推荐用于希望获得代码扫描告警和高严重性 门禁的 Noir 项目:
- uses: auditinfra-io/[email protected]
with:
path: .
lang: noir
fail-on: high
或者不使用 Action:
pip install o1js-scan
noir-scan . --lang noir --fail-on high --sarif noir.sarif