fix(verify-cli): analyzer 隔離 / exitCode 化 / audit help の是正 (#283) - #286
Merged
Conversation
`runAnalysis` へ渡す `AnalysisInput.verification` は検証結果オブジェクトそのものの 参照で、`valid` / `chainValid` / `metadataValid` / `poswSkipped` は `runAnalysis` の **後**に読まれていた。そのため `--analyzer` で渡した分析器が `input.verification.valid = true` と書くだけで、改ざん proof が exit 0 で通り、 三層保証の integrity や表示上の統計まで汚染できた。 脅威主体は `--analyzer` を渡す採点者自身なので深刻度は低いが、ADR-0009 / ADR-0023 の 「advisory は valid / exit code に漏れない」不変条件が外部 analyzer に対して構造的に 破れている状態だった。 守りを二重にする: 1. 検証結果を deep freeze してから分析層へ渡す。ES module は strict mode なので 書き込みは TypeError で throw し、orchestrator が握り潰す契約どおり他の分析器は 止まらない。凍結対象は event 数に比例しない小さなサマリのみ 2. 判定に効く値 (valid / exam / screenshots / language / processSummary、および deriveAssurance へ渡す result 由来のフィールド) を分析層より前にすべて確定させ、 分析層より後に読むのは `analysis` だけにする Assisted-by: Claude <noreply@anthropic.com>
stdout がパイプのとき Node の書き込みは非同期なので、`process.exit()` は未 flush の 出力を捨ててプロセスを落とす。60 proof の ZIP を遅い読み手へ流して実測したところ、 **ちょうど 65536 バイト (パイプバッファ 1 個分) で行の途中から切れ、 `=== Summary: 60/60 proofs passed ===` ごと消えた** (修正後は 79320 バイト全量)。 クラス単位の ZIP を `| tee report.txt` / `| less` で受ける採点運用がまさに壊れる形で、 TTY 実行とファイル redirect では再現しないので気付きにくい。 cli.ts の `process.exit()` 10 箇所を `process.exitCode` + `return` に置換した (usage / flag error の早期終了も揃えた)。`main()` は async でトップレベルから 1 回だけ呼ばれるので、return したあとイベントループが空になった時点で Node が flush してから終了する。 副作用として `--analyzer` の外部モジュールがハンドル (タイマー・ソケット) を残すと 自然終了できなくなるので、README に明記した。再導入は exitCode.test.ts が落とす。 Assisted-by: Claude <noreply@anthropic.com>
`--mode` の help は `audit - fast + deterministic PoSW sampling (placeholder)` と 書いており「fast 相当で軽い」と読めるが、実装は full と同等 (verify の poswModeFor が audit → 'full')。spec §6.1 も「部分的 PoSW 検証 (未実装、現状 full と同等)」なので、 help だけが実装と食い違っていた。採点者が「audit なら軽くて十分」と誤読すると、 実際にはフルコスト (10,000 iter/event の再計算) を払う。 spec の記述に揃え、printUsage の文言を固定するテストを足した。 対になる web 側の dead な PoswMode = 'sampled' の掃除は #281 で扱う (触るファイルが packages/verify/src に閉じるため)。 Assisted-by: Claude <noreply@anthropic.com>
🚀 Preview Deployment
Deployed from commit f762a3a |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#238 を分割した 5 本のうち、verify-cli 分 (#283) の 3 項目をまとめて直す。触るファイルは
packages/verify-cli/**に閉じているので、他の分割先 (#281 #282 #284 #285) と並行して進められる。目的
c3 — 外部 analyzer が検証結果を書き換えられる構造を塞ぐ (本命)
runAnalysisへ渡すAnalysisInput.verificationは検証結果オブジェクトそのものの参照で、valid/chainValid/metadataValid/poswSkippedはrunAnalysisの後に読まれていた。そのため
--analyzerで渡した分析器がinput.verification.valid = trueと書くだけで、改ざん proof が exit 0 で通り、三層保証の integrity も表示上の統計も汚染できた。
脅威主体は
--analyzerを渡す採点者自身なので実害の深刻度は低いが、ADR-0009 / ADR-0023 の「advisory は valid / exit code に漏れない」不変条件が
外部 analyzer に対して構造的に破れている状態だった。
c1 —
process.exit()によるパイプ時の stdout 切り捨てstdout がパイプのとき Node の書き込みは非同期なので、
process.exit()は未 flush の出力を捨てる。クラス単位の ZIP を
| tee report.txt/| lessで受ける採点運用がまさに壊れる形で欠ける。c2 —
auditモードの help が実装と食い違うaudit - fast + deterministic PoSW sampling (placeholder)は「fast 相当で軽い」と読めるが、実装は full と同等 (
poswModeForがaudit → 'full')。spec §6.1 も「未実装、現状 full と同等」。変更点
8c2ec53af1c29bcli.tsのprocess.exit()10 箇所をprocess.exitCode+returnへf554a08auditの help を spec §6.1 の記述に揃えるc3 の守りは二重にしてある。どちらか片方が将来崩れても事故にならないようにするため:
deepFreeze(result)してからrunAnalysisへ渡す。ES module は strict mode なので書き込みは TypeError で throw し、orchestrator が握り潰す契約どおり他の分析器は止まらない。
凍結対象は event 数に比例しない小さなサマリのみ (proof 本体は分析層が読む必要があるので触らない)
valid/exam/screenshots/language/processSummary、およびderiveAssuranceへ渡すresult由来のフィールドを分析層より前に確定させ、分析層より後に読むのは
analysisだけにする確認方法
自動
新規テスト (いずれも先に修正を戻して赤くなることを確認済み):
analyzerIsolation.test.ts— 検証結果の boolean を反転させにくる分析器を通しても、改ざん proof は
valid: false/integrity: 'failed'のまま、健全な proof はvalid: trueのまま(固定値ではなく反転にしたのは、「もともとその値だった」ケースで退行を見逃さないため)。
行儀の悪い分析器が throw しても他の分析器のシグナルが失われないことも見ている
exitCode.test.ts—cli.tsにprocess.exit(が再導入されたら落ちる。挙動そのものはプロセスを跨ぐので e2e の領分だが、再導入を止めるのはここ
(1 箇所足すだけで同じ穴が開き、しかも普段の TTY 実行では再現しない)
output.test.ts—printUsageのaudit行の文言を固定手動 (c1 の実測)
60 proof の ZIP を生成し、遅い読み手へパイプして比較した:
=== Summary: 60/60 proofs passed ===process.exit)process.exitCode)✓ file059_proof.json| catのような速い読み手では再現しない (パイプが常に空くため)。TTY 実行とファイル redirect でも再現しないので、気付かずに再導入しやすい。
終了コードが全経路で維持されていることも実プロセスで確認した
(正常 0 / 改ざん 1 / 不正フラグ 1 /
--help0 / 引数なし 1 / 存在しないファイル 1)。補足
PoswMode = 'sampled'の掃除は [verify] レビュー由来の表示不整合まとめ (v1-v5, v7 + dead な PoswMode sampled) #281 で扱う(触るファイルが
packages/verify/srcに閉じるため、パッケージ単位で分けたほうが衝突しない)--analyzerの外部モジュールがハンドル (タイマー・ソケット) を残すと自然終了できなくなるので、README に明記した。
process.exit()を使わない理由は verify-cli の CLAUDE.md 不変条件 2 に追記Closes #283
Refs #238 #243