
o1js/Mina zkApps 및 Noir 회로의 zk 회로 건전성 버그를 위한 종속성 없는 정적 분석기
커뮤니티 패키지:
o1js-scan은 공식 o1js Community Packages 디렉터리에 등록되어 있습니다.
최신: 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 형태의 패턴 포함)보안에 치명적인 버그는 대개 증명 시스템에 있는 것이 아니라
애플리케이션 자체의 제약 조건에 있습니다: 증명자가 제어하지만 회로가
결코 바인딩하지 않는 witness입니다. o1js-scan은 Mina 및 Noir 생태계에서
Circom의 사촌 격에 해당하는 under-constrained-signal 스캐너입니다.
참고: 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
withdraw 금액이 온체인 상태에 전혀 바인딩되지 않은, 증명자 제어 witness인 vault가 주어졌을 때:
$ 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
o1js 및 Noir의 취약/수정 쌍은 examples/를 참조하세요.
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 콘솔 스크립트는 npm 래퍼를 통한 경우를 포함하여 o1js-scan과 함께(동일한 진입점으로) 설치됩니다.
# 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
Exit code는 --fail-on 레벨(기본값 high) 이상의 발견 사항이 있으면 1, 그렇지 않으면 0이므로 CI에 바로 적용할 수 있습니다. 기본값에서는 low/medium 발견 사항(아래의 정보성 recipient 규칙 포함)이 빌드를 실패시키지 않습니다. --fail-on none을 사용하면 보고만 하고, --strict(--fail-on medium의 축약형)를 사용하면 low 심각도 발견 사항은 여전히 참고 사항으로 취급하면서 더 엄격하게 게이트를 적용할 수 있습니다. 두 옵션은 상호 배타적이므로 CI 구성이 모호해질 수 없습니다. 스캔 경로가 없으면 stderr에 오류를 출력하고 exit 2로 종료되므로, 오타가 CI를 조용히 통과하여 깨끗한 실행으로 처리될 수 없습니다. 경로가 존재하지만 분석기가 읽을 수 있는 o1js 또는 Noir 소스가 없는 경우(예: o1js 프로젝트에 --lang noir를 지정한 경우)도 마찬가지입니다. 이런 경우가 정당하다면 --allow-empty를 전달하세요. 모든 실행은 stderr에 한 줄 요약(심각도별 개수와 게이트 판정)을 출력합니다.
테스트 코드는 기본적으로 제외됩니다 — 두 백엔드 모두. 테스트는 assert가 이를 거부하는지 증명하기 위해 의도적으로 유효하지 않은 값과 잘못된 트랜잭션을 만들기 때문에, 그곳에서의 발견 사항은 회로 버그가 아니라 테스트의 목적 자체입니다. 파일이 테스트 코드로 간주되는 경우:
*.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 diff에 주석으로, 그리고 저장소의 Security → Code scanning 탭에 경고로 나타납니다.
# .github/workflows/o1js-scan.yml
name: o1js-scan
on: [push, pull_request]