Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
o1js-scan — o1js/Mina zkAppsおよびNoir回路におけるzk回路の健全性バグを検出する依存関係不要の静的解析ツール | Kitploit
ツール/GitHubGitHub/auditinfra-io/o1js-scan
防御ツール静的分析脆弱性スキャナー静的コード分析 (SAST)脆弱性分析コード分析暗号化DevSecOps
GitHubauditinfra-io/o1js-scan

o1js-scan

o1js/Mina zkAppsおよびNoir回路におけるzk回路の健全性バグを検出する依存関係不要の静的解析ツール

リポジトリを見る
2104日前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有
ウェブサイト

o1js-scan

CI Python License PyPI npm

コミュニティパッケージ: o1js-scan は公式の o1js Community Packages ディレクトリに掲載されています。

最新版: 0.20.0 — アナライザーが extends TokenContract するコントラクトを読み取れるようになりました。 このリリース以前は、コントラクトのゲートが SmartContract のみにマッチしていたため、 エコシステム内のすべての fungible token、NFT コレクション、AMM プールが 「no findings」としてスキャンされていました。0.20.0 より前にトークンコントラクトをスキャンした場合は、再度スキャンしてください。 CHANGELOG を参照してください。

以下における zk 回路の健全性バグ を検出する、高速で依存関係のない静的アナライザー:

  • o1js / Mina zkApps (TypeScript .ts / .js) — @method 本体から生成される Kimchi 回路
  • Noir (.nr) — Aztec の Rust ライクな ZK DSL (aztec-nr 形式のパターンを含む)

セキュリティ上重要なバグは通常、証明システムにあるのではなく、 アプリケーション自身の制約 にあります。つまり、prover が制御するものの回路が決して束縛しない witness です。o1js-scan は、Mina および Noir エコシステムにおける Circom のいとこたちのための、under-constrained-signal スキャナーです。```bash 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

root@kitploit:~
### 例

`withdraw` の金額が、オンチェーン状態に一切束縛されない、prover が制御する witness である vault が与えられた場合:```console
$ 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 で終了し、prover が選択した受取人に関する情報レベルの LOW のみを残します:```console $ 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

root@kitploit:~
脆弱/修正済みのペアについては、o1js と Noir の例を [`examples/`](https://github.com/auditinfra-io/o1js-scan/blob/main/examples) で参照してください。

## 目次

- [インストール](#install)
- [使用方法](#usage) · [検出結果の抑制](#suppressing-a-reviewed-finding)
- [GitHub Action](#github-action)
- [検出対象 — o1js](#what-it-detects-o1js) · [Noir](#what-it-detects-noir)
- [既知の制限事項](#known-limitations) · [このツールが対応しない範囲](#where-this-tool-stops)
- [プライバシーとプライベートコード](#privacy-and-private-code)
- [耐量子レビュー](#post-quantum-review)
- [互換性](#compatibility) · [仕組み](#how-it-works)
- [コントリビューション](#roadmap--contributing)

## インストール```bash
pip install o1js-scan

グローバルCLIへの分離インストールには、pipxを使用します:```bash pipx install o1js-scan

root@kitploit:~
Node/npmベースのNoir、Aztec、またはo1jsアプリのリポジトリでは、npmラッパーをインストールします:```bash
npm install -D o1js-scan
npx noir-scan . --lang noir --fail-on high

npm パッケージは同じ Python アナライザーの薄いラッパーであり、PATH 上に Python 3.8+(python3 または python)が必要です。特定のインタプリタを選択するには O1JS_SCAN_PYTHON を設定してください。

またはソースから:```bash git clone https://github.com/auditinfra-io/o1js-scan cd o1js-scan pip install -e .

root@kitploit:~
サードパーティ製の Python 依存関係はありません。Python 3.8 以上が必要です。`noir-scan` コンソールスクリプトは `o1js-scan` と一緒に(同じエントリポイントで)インストールされ、npm ラッパー経由でも利用できます。

## 使用方法```bash
# 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

o1js-scan --version

--fail-on レベル(デフォルトは high)以上の検出事項が存在する場合は終了コード 1、それ以外は 0 を返す — そのため CI にそのまま組み込める。デフォルトでは、low/medium の検出事項(以下に示す情報レベルの recipient ルールを含む)はビルドを 失敗させない。--fail-on none を使えばレポートのみ、--strict(--fail-on medium の省略形)を使えば low 重大度の検出事項を助言扱いのまま、より厳格にゲートできる。この 2 つのオプションは相互排他的であるため、CI 設定が曖昧になることはない。スキャンパスが存在しない場合は終了コード 2 とともに stderr にエラーを出力するため、タイポがクリーンな実行として CI を黙って通過することはない。すべての実行は 1 行のサマリー(重大度別の件数とゲート判定)を stderr に出力する。

テストコードはデフォルトで除外される — 両方のバックエンドで。 テストはアサーションがそれらを拒否することを証明するために、意図的に無効な値や不正なトランザクションを構築する。したがって、そこでの検出事項は回路のバグではなくテストの目的そのものである。以下の場合にファイルはテストコードとして扱われる:

  • ファイル名が *.test.ts / *.spec.ts(および .js/.jsx/.tsx/.mjs/.cjs の各バリアント)、または *_test.nr / test_*.nr に一致する;
  • test/、tests/、__tests__/、spec/、__mocks__/ ディレクトリ配下にある;
  • (Noir のみ、内容ベース)関数が #[test] / #[test(...)] 属性を持つ、または / ブロック内にある — ブロックスコープであるため、本番ファイルの末尾にあるテストモジュールがそのファイルの残りを無音にすることはない。

--include-tests を渡すとそれらをレポートする。

サンプルコードは格下げされ、破棄されない。 examples/ または example/ ディレクトリ内、あるいは *.eg.ts(.nr および他の JS/TS 拡張子も同様)という名前のファイル内の検出事項は、注記付きで LOW に格下げされる — 依然としてレポートされるが、ビルドを失敗させることはできなくなる。サンプルコードは意図的に簡略化されており、フレームワーク自身のサンプルを脆弱性としてフラグ立てするのはノイズである; しかし、テストコードよりもはるかに頻繁に 本番環境にコピーされる ため、隠すのではなく格下げされるのである。--include-examples を渡すと元の重大度が維持される。

いずれかのポリシーが適用されるたびに、実行はその旨を stderr に 1 行出力する — 例: 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。

レビュー済みの検出事項を抑制する

ゲートを緩めることなくトリアージ済みの検出事項を無音にするには、フラグが立てられた行、またはその上の行にインラインコメントを付ける:```ts this.send({ to, amount }); // o1js-scan-disable-line O1JS_UNCONSTRAINED_WITNESS

// o1js-scan-disable-next-line this.send({ to, amount });

root@kitploit:~
## 検出

### 検出ルール

- ルールは `rules/` ディレクトリに YAML ファイルとして保存されます。
- 各ルールは、検出ロジックを定義する `detection` ブロックを含みます。
- ルールは、`severity`、`tags`、`metadata` などのメタデータを含むことができます。

### 検出の仕組み

1. ログは、設定された入力ソースから取り込まれます。
2. 各ログイベントは、有効なすべてのルールに対して評価されます。
3. ルールの条件が一致すると、アラートが生成されます。
4. アラートは、設定された出力先に送信されます。

### ルールの例

```yaml
title: 不審なプロセス実行
id: 12345678-1234-1234-1234-123456789012
status: experimental
description: 不審なコマンドライン引数を持つプロセスの実行を検出します
author: Security Team
date: 2024/01/01
logsource:
  category: process_creation
  product: windows
detection:
  selection:
    Image|endswith:
      - '\powershell.exe'
      - '\cmd.exe'
    CommandLine|contains:
      - 'Invoke-Expression'
      - 'DownloadString'
      - 'FromBase64String'
  condition: selection
falsepositives:
  - 正規の管理スクリプト
level: high
tags:
  - attack.execution
  - attack.t1059

ルールのテスト

ルールは、サンプルログデータを使用してテストできます。

root@kitploit:~
# 単一のルールをテストする
./tool test-rule --rule rules/suspicious_process.yml --log samples/process_creation.json

# すべてのルールをテストする
./tool test-rules --rules-dir rules/ --samples-dir samples/

ルールのデプロイ

ルールは、設定ファイルを更新するか、CLI を使用してデプロイできます。

root@kitploit:~
# ルールをリロードする
./tool reload-rules

# ルールのステータスを確認する
./tool rules-status
``````nr
let inv = unsafe { hint(x) };  // o1js-scan-disable-line NOIR_UNCONSTRAINED_WITNESS

1つ以上のルールIDを指定すると、それらのルールのみを抑制します。IDを指定しない裸のディレクティブは、対象行のすべてのルールを抑制します。

ライブラリとして:```python 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)

root@kitploit:~
## GitHub Action

数行でスキャナをCIに追加できます。検出結果はPRのdiff上のアノテーションとして、またリポジトリの**Security → Code scanning**タブのアラートとして表示されます。```yaml
# .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.20.0       # optional, pin the scanner version
          # fail-on: high         # optional, fail the job on high/critical

Noir専用CIレシピ

コードスキャンアラートと高重大度ゲートを求めるNoirプロジェクトに推奨:```yaml

  • uses: auditinfra-io/[email protected] with: path: . lang: noir fail-on: high
root@kitploit:~
またはActionなしで:```bash
pip install o1js-scan
noir-scan . --lang noir --fail-on high --sarif noir.sarif

pre-commit(オプション)```yaml

.pre-commit-config.yaml

  • repo: local hooks:
    • id: noir-scan name: noir-scan entry: noir-scan language: system pass_filenames: false args: [".", "--lang", "noir", "--fail-on", "high"]
root@kitploit:~
入力: `path` (デフォルト `.`)、`lang` (`auto`|`o1js`|`noir`、デフォルト `auto`)、
`version` (インストールする PyPI バージョン、デフォルトは最新)、`upload-sarif` (デフォルト
`true`)、`fail-on` (`critical`|`high`|`medium`|`low`|`none`、デフォルト `none`)、
`fail-on-findings` (非推奨、デフォルト `false`)、`include-tests` (デフォルト
`false`)、`include-examples` (デフォルト `false`)。出力: `sarif-file`。SARIF
のアップロードには `security-events: write` とコードスキャンが有効になっている必要があります。

レポートとゲートは単一の引数配列から構築されるため、`include-tests`
と `include-examples` は両方に適用されます — あなたが読む SARIF と、ゲートの基準とする終了コードは
常に同じソースセットを記述します。レポートパスは
`--fail-on none` で実行されるため、検出結果が SARIF のアップロードをブロックすることはありませんが、運用上の
障害 (存在しないパス、CLI の使用エラー) は依然としてステップを失敗させ、
クリーンなスキャンとして報告されることはありません。

`fail-on-findings: true` は互換性のために維持されており、`fail-on` が `none` のままの場合に
`fail-on: high` にマッピングされます。非推奨の警告が出力されます。任意の
重大度でゲートできる `fail-on` を推奨します。

## 検出するもの (o1js)

### サポートされるルールの概要

<!-- BEGIN GENERATED RULE SUMMARY -->
| バックエンド | ルール | High 対応 | Medium 対応 | Low 対応 |
|---------|------:|-------------:|---------------:|------------:|
| o1js | 18 | 11 | 12 | 2 |
| Noir | 11 | 4 | 9 | 1 |
| **合計** | **29** | **15** | **21** | **3** |
<!-- END GENERATED RULE SUMMARY -->

カウントは各バックエンドがサポートする個別のルール ID です。コンテキストに応じて
重大度を割り当てるルール (たとえば、値の転送には high、状態の書き込みには
medium) は複数の重大度列に現れるため、重大度列の合計は
意図的にルール総数と一致しません。現在、critical または info の重大度のルールはありません。
完全な説明と誤検知ガードを以下に示します。

<!-- BEGIN GENERATED O1JS RULE TABLE -->
| ルール | 重大度 | 意味 |
|------|----------|---------------|
| `O1JS_MISSING_STATE_PRECONDITION` | high | 対応する `requireEquals(...)` / `getAndRequireEquals()` なしで `this.x.get()` を読み取っている。素の `get()` はアカウントの事前条件を**一切**追加しないため、証明は `x` をオンチェーンの値に束縛せず — 証明者は任意の値を代入できます。 |
| `O1JS_UNCONSTRAINED_WITNESS` | high / medium | `@method` 引数 (証明者が制御するプライベートウィットネス) が送信**量** (`this.send(...)` または同一メソッド内の `AccountUpdate.create*(...).send(...)`) や状態の `.set(...)` に流れ込み、**決して**アサートされない。制約不足の Circom シグナルの直接的な類似物。値の転送に到達する場合は high。 |
| `O1JS_UNCONSTRAINED_PROVABLE_WITNESS` | high / medium / low | `Provable.witness(...)` のローカル値が、**回路内での**アサーションなしに送信/状態効果に流れ込む。ウィットネスコールバックは*回路の外*で実行される (単なる証明者のヒントにすぎない) ため、結果は証明者が制御する新たな値になります — `@method` 引数以外のもう一つのウィットネス源です。再導出してアサートする (`x.assertEquals(<recomputed>)`) か、状態に束縛する必要があります。送信量 (`this.send(...)` または同一メソッド内の `AccountUpdate.create*`) では high。 |
| `O1JS_UNCONSTRAINED_RECIPIENT` | low | `@method` 引数が送信の `to:` 受信者として**のみ**使用されている。これは通常意図的なもの (ユーザーが自身の引き出し先を指定する) であり情報提供的です — 宛先が固定のトレジャリーや状態に記録されたアドレスであることが意図されている場合にのみ重要です。CI の終了コードゲートは**トリップしません**。 |
| `O1JS_WITNESS_NOT_BOUND_TO_STATE` | medium | ウィットネスが効果の前に*自明に*しか制約されていない (例: `> 0`、または定数との比較) — オンチェーン状態に結び付けられていない。オフチェーンのオーケストレーションがこれを安全にしていることを確認するか、残高がその定常値まで引き出し可能です。 |
| `O1JS_STALE_MERKLE_ROOT` | high | メソッドが証明者提供のウィットネスから Merkle ルートを再計算する (`computeRootAndKey` / `calculateRoot`) が、再計算されたルートの**いずれも**現在のオンチェーンルートに束縛していない。ライブルートに対する `this.root.requireEquals(...)` / `assertEquals` がない場合、証明者は捏造または古いツリーのウィットネスを渡すことができ — メンバーシップを偽造したり古い状態をリプレイしたりできます。束縛はデコレートされていない同一クラスのヘルパー (`this.verifyX(witness)`) に存在する場合があり、ヘルパーの伝播がそれをカバーします。 |
| `O1JS_UNVERIFIED_PROOF` | high | `Proof<...>` / `SelfProof<...>` / `DynamicProof<...>` として型付けられた `@method` パラメータが、そのパブリックフィールドが使用される前に `.verify()` されていない。Proof を渡すだけでは検証されません — 明示的な verify がない場合、証明者は任意の証明オブジェクトを提供でき、その `publicOutput` の使用は制約されません。また、`.verifyIf(flag)` が制約されていない `@method` 引数によってゲートされ、証明のパブリックフィールドが読み取られる場合にも発火します。証明者が条件を false にできるためです。 |
| `O1JS_UNASSERTED_BOOL` | high / medium | o1js の述語 (`equals` / `lessThanOrEqual` / …) は `Bool` を返し、結果がアサートまたは使用されない限り**制約を追加しません**。呼び出しが素の破棄された文である場合は HIGH、再度参照されないローカルに代入された場合は MEDIUM。 |
| `O1JS_UNCONSTRAINED_SENDER` | high / medium | `this.sender.getUnconstrained()` は証明せずにトランザクション送信者を返します。その値 (またはそこから派生したローカル) が assert / 状態の `.set` / `send` に流れ込む場合 (空虚なチェック) は HIGH、それ以外は MEDIUM。`this.sender.getAndRequireSignature()`、または展開されたイディオム `AccountUpdate.createSigned(sender)` を推奨します。**以下の場合は沈黙します**: (1) 同じ `@method` がどこかで `this.sender.getAndRequireSignature()` も呼び出している場合 (署名要件はメソッドスコープ)、または (2) ウィットネスされた送信者値が `AccountUpdate.createSigned(...)` / その同じキーに対する `AccountUpdate.create(...).requireSignature()` の引数である場合 (引数の同一性が必要 — 異なるキーに対する `createSigned` は抑制しません)。 |
| `MissingRangeCheck` | high | 生の `Field` (範囲チェックされた `UInt64`/`UInt32` ではない) が転送量として使用されている。`Field` は mod p の要素であり、範囲に制限されません。 |
| `O1JS_WEAK_PERMISSIONS` | high / medium | `editState` / `send` が `proofOrSignature()` または `none()` に設定されており、zkApp アカウントキーが署名することで回路をバイパスできる。また、`setVerificationKey` / `setPermissions` が `signature` / `proofOrSignature` / `none` のままになっている場合もフラグを立てます (Mina が文書化したアップグレード用の補助輪)。同じ `permissions.set` 内で弱い `editState`/`send` と組み合わさった場合は HIGH。 |
| `O1JS_LOGIC_OUTSIDE_PROOF` | high | `Provable.asProver(...)` または `Provable.witness*` コールバック内のセキュリティロジック (assert / approve / send / 状態の `.set`)。これらのコールバックは*回路の外*で実行されます — 悪意のある証明者はそれらを削除しても検証に通る証明を生成できます。 |
| `O1JS_APPROVE_WITHOUT_BINDING` | medium | `@method` が `balanceChange` / `publicKey` を読み取らず、`assertCanMint` / `assertCanBurn` / `forEachUpdate` の保存チェックもなしに `approve` / `approveAccountUpdate` / `approveBase` を呼び出している — Mina の FlawedTokenContract の典型例。 |
| `O1JS_VACUOUS_ASSERT` | high / medium | 構築上満たされる assert: `x.assertEquals(x)`、`x.equals(x).assertTrue()`、または `Bool(true).assertTrue()`。自己比較の場合は HIGH (ほぼ常にタイポ)、定数 Bool の assert の場合は MEDIUM。 |
| `O1JS_CONDITIONAL_ASSERT` | medium | `if <flag> { ... }` 内の assert で、`<flag>` が証明者制御の `@method` `Bool` (または `.toBoolean()` からのローカル) である場合。JS の条件分岐は `Provable.if` のように回路を制約しません。精度のためインライン比較は報告されません。 |
| `O1JS_GUARDED_INVERSE` | medium | `Provable.if` 分岐内の `.div()` / `.inv()` / `.sqrt()` で、それが失敗するまさにその値に対する条件でガードされている。両方の分岐が回路内で評価され、これらの呼び出しは逆元または根が存在することを無条件にアサートするため、ガードはアサーションをスキップしません — 回路はガードが処理するために書かれたまさにその入力に対して充足不可能となり、そのメソッドは決して証明できません。Veridise により `V-O1J-VUL-060` として報告されています。まず安全な除数を計算し (`Provable.if(isZero, Field(1), d)`)、その後で結果を選択してください。**以下の場合は沈黙します**: ガードが除数について何も述べておらず、安全な除算の周りの無関係な `Provable.if` にはフラグが立たない場合。 |
| `O1JS_PRECONDITION_OVERWRITTEN` | medium | 1 つのメソッド内で**同じ**プロパティに対する 2 つ以上の `requireEquals` / `requireBetween` / `requireNothing` 呼び出しがあり、引数が異なる。事前条件は累積されるのではなく AccountUpdate に*設定*されるため、各呼び出しは前のものを上書きし、最後のものだけが適用されます — 合成される回路内アサーションとは異なります。`a.requireEquals(b)` の後に `a.requireEquals(c)` は `a === c` を意味し、`a === b` ではありません。Veridise により `V-O1J-VUL-012` として報告されています。**以下の場合は沈黙します**: 引数が同一の場合 (冪等、何も失われない)、`getAndRequireEquals()` の場合 (別のメソッドなので状態の繰り返し読み取りは問題ない)、および呼び出しが相互に排他的な JS 分岐にある場合 (回路構築時に解決される)。この最後の除外は、無関係な `if`/`else` にまたがる実際の上書きを隠す可能性があります。 |
| `O1JS_STATE_READ_AFTER_WRITE` | medium | 同じメソッド内で、同じフィールドへの `set(...)` が完了した後に `@state` フィールドが読み取られる (`get()` / `getAndRequireEquals()`)。`set()` は変更を AccountUpdate に記録しますが、`get()` に書き戻さないため、読み取りは書き込み前の値を依然として観測し、それに基づいて構築された算術はその書き込み分だけ静かにずれます。Veridise により `V-O1J-VUL-030` として報告されています。状態を読み戻すのではなく、新しい値をローカルに保持してください。**以下の場合は沈黙します**: 読み取りが書き込み自身の引数内にネストされている場合 (読み取り-変更-書き込みイディオム `this.x.set(this.x.getAndRequireEquals().add(1))`、これは正しい)、および書き込みと読み取りが相互に排他的な JS 分岐にある場合。単一メソッドにスコープされます — Veridise が説明するクロスメソッドのキャッシュケースは、このルールが持たないコールグラフの知識を必要とします。 |
<!-- END GENERATED O1JS RULE TABLE -->

### 誤検知ガード (o1js)

アナライザーは正しいコードに対して沈黙するように設計されています:

- **署名でゲートされたメソッドはスキップされます。** `this.requireSignature()` (または `getAndRequireSignature`、`AccountUpdate.createSigned`、
  `Signature.verify`) を呼び出す `@method` はオーナー/管理者でゲートされており — その引数は任意の証明者ではなく
  キー保有者によって選択される — ため、そのウィットネスにはフラグが立ちません。これは
  o1js における `onlyOwner` の等価物です。
- **状態に束縛されたウィットネスはスキップされます。** `getAndRequireEquals()` から導出された
  値と等しいとアサートされた (または順序比較でその値によって制限された) 引数は
  健全であり、報告されません。これは直接形式 —
  `amount.assertLessThanOrEqual(bal)` — と連鎖形式
  `amount.lessThanOrEqual(bal).assertTrue()` の両方をカバーします。デコレートされていない
  同一クラスのヘルパー (`this.verifyX(arg)`) に存在する束縛も認識され、
  そのようなヘルパーの連鎖を通じても同様です。
- **検証済みの証明はスキップされます。** `.verify()` が呼び出された `Proof` / `SelfProof` / `DynamicProof` /
  `*Proof` 型の引数は、検証された回路によって制約されます — それに対するウィットネスの検出結果 (およびその `publicOutput` /
  `publicInput`) は抑制されます。`.verifyIf(flag)` は、条件が制約されていないメソッド引数でない場合、
  またはそれ自体がアサートされている場合にのみ認められます。同じことが正規の OffchainState ラッパー
  `this.offchainState.settle(proof)` にも当てはまります (フレームワークは `settle` 内で検証します)。
  手書きの `.settle(proof)` は
  検証すると**仮定されません**。逆のケース (Proof 型の引数が決して検証されず、OffchainState で settle もされない) は
  `O1JS_UNVERIFIED_PROOF` として報告されます。
- **アサート済み/使用済みの Bool はスキップされます。** `.assertTrue()` / `.assertFalse()` で連鎖された、
  `Provable.if(...)` にネストされた、または後で参照されるローカルに代入された述語は、
  `O1JS_UNASSERTED_BOOL` として報告されません。
- **認証済みの送信者はスキップされます。** `this.sender.getUnconstrained()` は、
  同じ `@method` が `this.sender.getAndRequireSignature()` も呼び出している場合、またはそのウィットネスされた値が
  `AccountUpdate.createSigned(...)` に渡されている / それから構築された AccountUpdate に対して
  `.requireSignature()` で認証されている場合 (引数の同一性が必要) には発火しません。
- コメントと文字列リテラルは解析前に除去されるため、文字列内の `assert` が
  誤った結果を生み出すことはありません。

## 検出するもの (Noir)

同じ健全性の考え方 — 制約不足のウィットネス — が
[Noir](https://noir-lang.org) (`.nr`) 回路にも当てはまります。スキャナーを `.nr`
ファイルに向ける (または `--lang noir` を使用する) と、Noir ルールセットでそれらを解析します。
同じ字句的で依存関係のないアプローチです。aztec-nr のオラクル /
`unsafe` イディオムに対して調整されています — [`docs/noir_calibration.md`](https://github.com/auditinfra-io/o1js-scan/blob/main/docs/noir_calibration.md) を参照してください。

<!-- BEGIN GENERATED NOIR RULE TABLE -->
| ルール | 重大度 | 意味 |
|------|----------|---------------|
| `NOIR_UNCONSTRAINED_WITNESS` | high | `unsafe { ... }` ブロックから束縛された値 — `unconstrained fn` (オラクル / Brillig ヒント) の結果 — で、`assert` / `assert_eq` (または確認ヘルパー / merkle チェック) によって再制約されないもの。ヒントは**回路の外**で実行されます。`O1JS_UNCONSTRAINED_PROVABLE_WITNESS` の類似物。 |
| `NOIR_UNCONSTRAINED_INPUT` | medium | `fn main` のプライベート (ウィットネス) 入力で、**どの** `assert` / `assert_eq` にも流れ込まず、パブリック出力の一部でも**ない**もの。`O1JS_UNCONSTRAINED_WITNESS` の類似物。 |
| `NOIR_UNCONSTRAINED_PUBLIC_INPUT` | medium | `fn main` の**パブリック**入力で、どの制約にも出力にも到達しない — 回路が決してそれを読まない。プライベートウィットネスルールの*双対*: 検証者が値を提供し、ステートメントがそれについてのものであると信じる一方で、回路はそれを無視します (例: 決してチェックされない `merkle_root: pub Field` で、メンバーシップが実際には証明されなかった)。MEDIUM である理由は、意図的に未使用のパブリック入力も、証明をコンテキスト (nonce / chain id / recipient) に束縛するための正当なイディオムであり、字句的に区別できないためです — したがってデフォルトの `--fail-on high` では CI をゲートしません。 |
| `NOIR_UNCHECKED_CAST` | medium | 証明者制御の値が範囲アサーション**なし**で狭い符号なし型 (`as u8`/`u16`/`u32`) にキャストされている。o1js の `MissingRangeCheck` の類似物。 |
| `NOIR_UNCONSTRAINED_ARRAY_INDEX` | medium | 証明者制御の値が配列インデックス (`arr[i]`) として使用され、それに対する**いかなる種類の**チェックも**ない**。Noir の暗黙の境界チェックは、インデックスが*範囲内*であることだけを確立し — *正しい*インデックスであることは確立しない — ため、証明者は任意の要素を選択しても検証に通る証明を生成できます。これは Merkle パスの位置、ノート選択、許可リストのメンバーシップの背後にあるセレクター自由度のバグです。インデックスが範囲制限されている、等価性で固定されている、キャスト前に制限されている (`index.assert_max_bit_size::<8>(); let i = index as u32;`)、または読み戻された値自体が `assert_eq` で固定されている場合は抑制されます。 |
| `NOIR_UNASSERTED_BOOL` | high / medium | `bool` の結果が**破棄される**比較。o1js の `O1JS_UNASSERTED_BOOL` の類似物。 |
| `NOIR_CONDITIONAL_ASSERT` | medium | `if <flag> { ... }` 内の `assert` で、`<flag>` が証明者制御の素の `bool` または証明者制御の値から派生したローカルである場合。条件分岐内の制約は条件が真の場合にのみ適用されるため、証明者が選択した分岐はチェックをスキップできます。精度のためインライン比較 (`if x != 0`) はそのままにされます。ガードをローカルに代入する場合 (`let gate = x != 0; if gate`) は、`gate` 自体がアサートされていない限り報告されます。 |
| `NOIR_CONDITIONAL_CONSTRAIN` | medium | 証明者制御の `if` の下でのみ `constrain_*` / `confirm_*` / `verify_*` 呼び出しがあり、一方で `unsafe` ヒントが依然として出力に到達する。 |
| `NOIR_UNUSED_CHECK_RESULT` | high / medium | `check_*` / `confirm_*` / `verify_*` / `constrain_*` の結果が破棄されている (素の呼び出し) か、代入されて決してアサートされていない — チェックが回路を束縛しない。 |
| `NOIR_VACUOUS_CONSTRAINT` | high / medium | 構築上満たされる制約: 自己比較 (`assert(x == x)`、`assert_eq(x, x)`、`x >= x`) または定数条件 (`assert(true)`)。制約を追加しませんが、その行はチェックとして*読める* — これは欠落した制約よりも危険です。レビューがそこで止まるためです。自己比較の場合は HIGH (ほぼ常に実際のチェックのタイポ: `assert(computed == expected)` を `assert(expected == expected)` と誤って入力); 定数の場合は MEDIUM (より頻繁にプレースホルダー)。`x != x` には**フラグが立ちません** — それは充足不可能であり、静かな健全性の穴ではなく活性のバグです。 |
| `NOIR_UNSAFE_MISSING_SAFETY` | low | 隣接する `// Safety:` コメントのない `unsafe { ... }` ブロック。情報提供的; デフォルトの `--fail-on high` では CI を失敗させません。 |
<!-- END GENERATED NOIR RULE TABLE -->

### 誤検知ガード (Noir)

- **Assert / let ホップ / 同一ファイル内の確認ヘルパー**が `unsafe` ヒントを束縛します。
- **呼び出しサイト名** `constrain_*` / `confirm_*` / `verify_*` /
  `check_(non_)membership*` / `public_data_storage_read` が引数を認めます (破棄されたチェックの未使用結果検出付き)。
- **文書化された意図的な unconstrained** (隣接する `// Safety:` が必要):
  `random()`、`avm::…`、および kernel/rollup/discovery の遅延文言。
- **タプル `let` + アサートされたフラグ**がメンバーシップチェックに渡された merkle ウィットネスを束縛します。

例:```console
$ noir-scan examples/noir_unconstrained.nr --include-examples
HIGH     NOIR_UNCONSTRAINED_WITNESS         noir_unconstrained.nr:16  fn=main  Unconstrained `unsafe` result `inv` in `main`
LOW      NOIR_UNSAFE_MISSING_SAFETY         noir_unconstrained.nr:16  fn=  `unsafe` block without a `// Safety:` comment
noir-scan: 2 finding(s) [1 high, 1 low] in 1 of 1 file(s) — fails (--fail-on high)

$ noir-scan examples/noir_constrained.nr --include-examples
noir-scan: no findings in 1 o1js or Noir file(s) — passes (--fail-on high)

上記の o1js の例と同様に、--include-examples が必要なのは、これらのデモファイルが examples/ 配下にあるためだけです。

既知の制限

このアナライザーは、依存関係のない字句フロントエンドと軽量なセマンティックレイヤーであり、エイリアストラッキングと同一クラス内ヘルパーを介した手続き間伝播を行います。これは TypeScript コンパイラのフロントエンドでも、型チェッカーでも、プログラム全体のデータフローエンジンでもなく、このスキャナーには SMT や形式証明のレイヤーもありません。 トリアージの際は、これらの盲点を念頭に置いてください。これらはこの依存関係のない設計において既知かつ意図的なものであり、バグではありません:

  • 追跡されるのは単純なエイリアスのみです。 ウィットネストラッキングは const q = qty のような同一メソッド内の単純なエイリアスを追跡しますが、派生式や分割代入は追跡しません: ```ts const q = qty; this.send({ to: dest, amount: q }); // followed const q = qty.add(1); this.send({ to: dest, amount: q }); // not followed const slot = this.root; slot.get(); // missing precondition missed

    root@kitploit:~
  • クロスメソッドバインディングは同一クラスのヘルパーチェーンのみを対象とする。 装飾されていない同一クラスのヘルパーが this.verifyX(arg) として呼び出された場合、呼び出し元の引数を状態バインドでき、0.19.0 以降ではそれらのチェーン(@method → ヘルパー A → ヘルパー B)が不動点まで追跡される。ヘルパー→ヘルパーのステップは裸のパラメータ参照のみをマップするため、helperA(x.add(1)) は伝播しない。自由関数およびインポートされた関数は依然として追跡されず、ヘルパー引数のローカル変数エイリアシングは文書化された制限のままである。

  • 未アサート Bool 検出は文の形に依存する。 ティア A は、最外殻の呼び出しが Bool 述語であり、その後に何もチェーンされていない裸の式文のみをフラグする。Provable.if(...) 内にネストされた述語や、代入されて後で使用される述語はフラグされない。Bool ローカル変数の複雑な制御フローの使用は、名前が一度も参照されない場合に見逃される可能性がある(失敗モード:見逃しであり、偽陽性ではない)。

  • シグネチャゲーティングはメソッドレベルかつ部分文字列ベースである。 _method_is_signature_gated は、@method 全体がシグネチャイディオムを含む場合にオーナーゲート済みとして扱い、レシーバ名が文字通り signature を含む場合にのみ検証者を認識する — したがって sig.verify(admin, msg) はゲーティングとして、大きなメソッド内の無関係なシグネチャチェックが過剰抑制を引き起こす可能性がある。メソッドごとにオール・オア・ナッシングである。

これらが、検出結果が証明ではなく人間によるレビューの出発点である理由である。データフローを考慮した書き換えは、字句解析器の範囲外として意図的に除外されている。

このツールが止まる場所

o1js-scan は意図的に浅い、単一ファイルの字句パスである — パーサーなし、データフローなし、ソルバーなし。それが依存関係を不要にし、CI で即座に実行できる理由であり、同時に厳しい上限でもある。上記の制限はバックログではなく、設計の帰結である。

したがって、このツールが何を伝えられ、何を伝えられないかを明確にしておく価値がある:

  • クリーンな実行は監査ではない。 それはこのスキャナが認識する形が一致しなかったことを意味するだけであり、回路が健全であることを意味しない。データフロー、パス感度、または制約解決を必要とするバグクラスは、この形のツールではどの言語でも到達できない。
  • 検出結果は手がかりであり、評決ではない。 ここにあるすべてのルールは、文書化された偽陽性クラスを持つヒューリスティックである。

このトレードオフは、毎コミット実行するリンターとしては正しいものである。違いが重要となるもの — 実際の価値を保持するプロトコル、間違える余裕のない回路 — に取り組んでいるなら、これを最初のパスとして扱い、本格的なレビューの予算を確保すること。

より深い分析には、別の完全なスキャナが audit-engine-cli リポジトリで維持されている。o1js-scan は意図的に軽量なオープンスキャナであり、完全なスキャナの独自の検出知識と実装詳細はここには再現されていない。アクセスまたはより完全な回路レビューについては、連絡されたい:[email protected]。

プライバシーとプライベートコード

インストールされた CLI はファイルをローカルで分析する。テレメトリ、ネットワーククライアント、アカウント、アップロードステップはなく、その Python ランタイムにサードパーティの依存関係はない。o1js-scan path/to/private-repo を実行しても、ソースや検出結果をどこにも送信しない。

コンパイラログと同様に、スキャナの出力にはパス、識別子、ソースフラグメントが含まれる可能性がある。SARIF は正確なリポジトリの場所も識別し、GitHub Action はそれを GitHub コードスキャンにアップロードする。スキャン対象のソースに既に使用しているのと同じリポジトリおよび CI アクセス制御を使用すること。

アプリケーションを共有せずに有用な偽陽性または見逃し検出レポートを提供したい場合、発明した名前と定数で構文を再現し、ビジネスロジックを一度に一つの文ずつ削除し、投稿する前に合成スニペットが同じルールを依然としてトリガーすることを確認すること。プライバシーセーフなコントリビューションガイドには、具体的なチェックリストと、プライベート回路を開示せずに o1js コミュニティを支援するいくつかの方法がある。

この境界は、オープンスキャナが改善されることを妨げない。公開 o1js ドキュメントとリポジトリは新しいルールと互換性フィクスチャをサポートできる;合成例は偽陽性と見逃された制約をテストできる;そしてパーサーの回復力、診断、SARIF、パフォーマンス、パッケージング、キャリブレーションはすべて、プライベートな監査技術やクライアントコードを公開することなく改善できる。オープンスキャナは独立して説明可能な主張を行うべきであり、プライベートな研究は別の監査エンジンに留めることができる。

ポスト量子レビュー

量子リスクは回路セキュリティに関連するが、それは欠落制約ルールではない。o1js-scan は、シグネチャ、ハッシュ、コミットメント、Kimchi 証明システム、または Mina 自体がポスト量子セキュリティ目標を満たすかどうかを判断しない。それらの答えは、具体的なプリミティブとパラメータ、プラットフォームの前提、デプロイメントに必要な寿命、およびその移行計画に依存する — 字句スキャナが見ることのできる TypeScript 識別子だけではない。

O(1) Labs の Qubit or Not Qubit に触発されたポスト量子レビューガイドは、その境界を o1js 固有のインベントリと暗号アジリティチェックリストに変える。クリーンなスキャンをポスト量子評価と解釈するのではなく、このスキャナと併用すること。

互換性

o1js 1.x、2.x、3.x で動作し、o1js 3.0.0 がターゲットとする Mesa ハードフォークを含む。o1js-scan は TypeScript ソースをテキストとして分析し、o1js へのランタイム依存関係はない — 何もバージョン固定されていない。現代の require* 前提条件 API(getAndRequireEquals、requireEquals、requireSignature、getAndRequireSignature)、@method / @method() / @method.returns(...) デコレータ、注釈付き @state フィールド、this.send({...})、低レベル AccountUpdate.balance.subInPlace(...) 転送、および Permissions.* をキーとする。確立された形式は 1.x → 2.x → 3.x の境界を越えて互換性を保ち、スキャナは新しく文書化されたデコレータと低レベル転送のバリアントも受け入れる。2.x のオーナー認証イディオム this.sender.getAndRequireSignature() はシグネチャゲーティングとして認識される。(レガシーな 前提条件も依然として受け入れられるため、古いコードも壊れない。)

Mesa の破壊的変更はすべてランタイムおよびプロトコルレベルである — Transaction.setFeePerSnarkCost() と TransactionCost.* 定数の削除、新しい VerificationKey.toJSON() の形、再生成された検証キー、MAX_ZKAPP_STATE_FIELDS が 8 から 32 に引き上げ、および mina-signer v4 トランザクション形式。それらのいずれもこのスキャナがマッチする API の名前を変更しないため、Mesa 用にルールは変更されず、それは主張ではなく検証されている。scripts/o1js_release_matrix.sh はプロトコル境界を挟む 2 つの固定された o1js リリース — 2.15.0(9620ef08、最後の 2.x リリース)と 3.0.0(cc18a919、Mesa) — をスキャンし、すべての検出結果を tests/fixtures/o1js_release_matrix.json と比較する:

33 件の検出結果が境界を越えて同一であり、失われたものはなく、3 件の新しいものはすべて src/examples/zkapps/big-state-zkapp.ts にある — Mesa が MAX_ZKAPP_STATE_FIELDS を引き上げたことによってのみ存在する 32 状態フィールドの例である。その差分はテストによって固定されているため、静かにドリフトすることはない。マトリクスはすべての CI ビルドで実行され、週次の o1js-upstream-canary ジョブはさらに o1js を HEAD で追跡し、あらゆるリリースに先行する。

等価な制約の綴りは分析のために正規化される:インスタンス assertEquals(...)、静的 Provable.assertEqual(Type, ...)、および equals(...).assertTrue() 等価チェーンはすべて同じオペランドをバインドする。メソッド抽出は、長さを保持するコメントと文字列のマスキング後にブレースバランスされ、複数行デコレータ、ネストされたコールバック形状のパラメータ型、TypeScript アクセス修飾子、および複数行のアイデンティティエイリアス(括弧付きおよび as Type 形式を含む)を受け入れる。

Noir 分析は Aztec / nargo プロジェクトが使用する Noir 構文(.nr)を対象とする;nargo を呼び出したり回路をコンパイルしたりしない。

仕組み

これは完全な TypeScript または Noir パーサーではなく字句解析器である — o1js と Noir のソースはブレース区切りで正規表現で扱いやすく、出力は人間がトリアージすることを意図している。それが依存関係を不要にし、CI で即座に実行できるようにする。検出結果はレビューの出発点であり、証明ではない。

ロードマップ / コントリビューション

コントリビューション歓迎 — 新しいルールファミリー、より多くの FP ガード、実世界のキャリブレーションアーキタイプはすべて価値がある。CONTRIBUTING.md を参照。

Community Packages リストから o1js リポジトリのアドバイザリチェックへの提案された道筋については、すぐに送信できる o1js アップストリーム統合提案を参照。

テストとリンターを以下で実行する:```bash pip install -e ".[dev]" pytest # unit tests + Noir/o1js corpus ruff check . # lint npm run format:check # prettier, npm wrapper only

root@kitploit:~
## ライセンス

Apache-2.0。[`LICENSE`](https://github.com/auditinfra-io/o1js-scan/blob/main/LICENSE) を参照してください。
ツールをダウンロード
mod test { … }
mod tests { … }
認識されず
  • 送信者認証は名前ベースかつ同一メソッド内のみである。 O1JS_UNCONSTRAINED_SENDER は、this.sender.getAndRequireSignature() または AccountUpdate.createSigned(<その送信者>) が同じ @method 本体に現れる場合に抑制する。ヘルパー内にのみ存在するシグネチャ要件(this.requireSenderSig() → 内部で getAndRequireSignature)は追跡されない — 失敗モードは、イディオムをラップする正しいコードに対する偽陽性であり、実際のバグの見逃しではない。

  • Noir クロスクレートヘルパーは名前の慣習によってのみ認識される(Nargo.toml / インポート解決なし)。偽陽性よりも見逃しを優先する。

  • assertEquals
    リリース検出結果HIGHMEDIUMLOWファイル
    o1js 2.15.036826218
    o1js 3.0.0 (Mesa)39829219