Skip to content

[verify-cli] 外部 analyzer が検証結果を書き換えられる / process.exit でパイプ出力が欠ける / audit help が実装と不一致 #283

Description

@shinyaoguri

#238 (2026-08 レビューのまとめ Issue) をサブシステム別に分割したうちの verify-cli 分。
項目番号 (c1〜c3) は #238 のトリアージコメントの採番。現存は 2026-08-24 / HEAD d61f126 で再確認済み。

トリアージ時点では「#218 / #266 の実装 PR に同乗が最も衝突が少ない」としていたが、
両方とも着地済み・closed (#218e87243d / #266d61f126) なので独立した PR が要る。

c3 — 外部 analyzer が検証結果を書き換えられる (最優先)

packages/verify-cli/src/verify.ts:177

const analysis = await runAnalysis({ proof, verification: result }, options.analyzers);

AnalysisInput.verification検証結果オブジェクトそのものの参照で、
result.valid / chainValid / metadataValid / poswSkippedrunAnalysis (:204-271) に読まれる。
つまり analyze() の中で input.verification.valid = true と書くだけで、
改ざん proof が valid: true (exit 0) になり、assurance.integrityfailed 以外になり、
pasteEvents の捏造まで通る (トリアージで一時 vitest により確認済み)。

脅威主体は --analyzer を渡す採点者自身なので実害の深刻度は低いが、
ADR-0009 / ADR-0023 の「advisory は valid / exit code に漏れない」不変条件が構造的に破れている

なお deriveAssurance 自体は analysis を process 層 (notableSignals / reviewPriority) にしか流さない
(packages/shared/src/assurance.ts:120-121)。破れの経路はこの共有参照だけ。

方針 (順序で保証し、凍結でトリップワイヤを張る):

  1. result / events / proof から導出される値をすべて runAnalysis より前に確定させ、
    runAnalysis 以降に読むのは analysis だけにする
  2. runAnalysis へは Object.freeze({ ...result }) の浅いコピーを渡す。ES module は strict mode なので
    書き込みは TypeError で throw し、orchestrator が握り潰す (Analyzer の契約どおり他の analyzer は止まらない)
  3. AnalysisInput の型 (shared) は変えない。深い凍結は event 数に比例するのでしない

~15-20 行 + 悪意 analyzer を注入して valid: false のままであることを assert するテスト。

c1 — process.exit() によるパイプ時の stdout 切り捨て

packages/verify-cli/src/cli.ts:42,51,59,87,94,109,143,154,226,229 (10 箇所)

stdout がパイプ かつ exit 時点で未 flush の出力が ≈64 KiB を超えると末尾が欠ける (POSIX のみ。
TTY / ファイル redirect では起きない)。1 proof 分は最小 700 B なので単一 proof では起きないが、
クラス単位の ZIP (数十 proof) を | tee / | less すると最後の === Summary: N/M proofs passed ===
から欠ける
(トリアージで再現済み)。

方針: 全 10 箇所を process.exitCode = n; return; に置換 (usage / flag error の早期 exit も揃える)。
main() は async でトップレベルから 1 回だけ呼ばれるので return でそのまま自然終了する。~10 行

副作用: --analyzer の外部モジュールが timer / socket を残すとプロセスが終わらなくなる。
README / CLAUDE.md の --analyzer の項に「ハンドルを残さないこと」を一言足す。

c2 — audit モードの help が実装と食い違う

packages/verify-cli/src/output.ts:519

audit - fast + deterministic PoSW sampling (placeholder)

は「fast 相当」と読めるが、実装は full と同等
(packages/verify/src/services/proofVerification.ts:72-79poswModeForaudit → 'full')。
spec §6.1 の記述「部分的 PoSW 検証 (未実装、現状 full と同等)」に揃える。help 1 行

対になる web 側の dead な PoswMode = 'sampled' の掃除は verify 側の Issue で扱う
(触るファイルが packages/verify/src/** に閉じるため、パッケージ単位で分けたほうが衝突しない)。

備考

  • 深刻度は c1 / c3 が medium 相当、c2 が low
  • 触るファイルが packages/verify-cli/src/** に閉じるので、他クラスタと並行して進められる

Refs #238 #243

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcode-review2026-07 多角レビュー由来pkg:verifypackages/verify + verify-cliseverity:medium中程度の問題

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions