2026-08 レビューの残件トリアージ (#243) 中に、#248 (verify web の language が無検証で className に入る) の同型が verify-cli 側にもあることを確認したので分離して起票する。
現象
packages/verify-cli/src/output.ts:249 付近:
lines.push(`Language: ${result.language}`);
result.language は proof.json 由来の値を無加工で端末へ流している。改行と ANSI エスケープを含む language を仕込むと、Details セクションに任意の行を偽造できる。
formatResult を実際に呼んで確認済み: Hash Chain: PASS に見える行の注入に成功した。
影響
- exit code は 1 のままなので、CI の正規ゲートは守られる
- 壊れるのは stdout を grep する採点スクリプト。一斉採点でパイプして
PASS を数えるような運用は騙せる
- 端末の ANSI エスケープを通すので、カーソル移動で既存の出力行を上書きすることもできる
XSS ではないので緊急ではないが、#248 と同じ「proof 由来の文字列を検証せず表示層へ流す」構造。
対応案
language を allowlist 正規化する (shared 側に normalizer を置き web / CLI 双方が使う)。SyntaxHighlighter.isLanguageSupported に相当する集合が既にある
- CLI 側で表示前に制御文字 (改行・ANSI) をストリップする防御を足す
1 と 2 の両方が望ましい。1 だけだと将来別の proof 由来文字列が同じ経路を通る。
関連
2026-08 レビューの残件トリアージ (#243) 中に、#248 (verify web の
languageが無検証でclassNameに入る) の同型が verify-cli 側にもあることを確認したので分離して起票する。現象
packages/verify-cli/src/output.ts:249付近:result.languageは proof.json 由来の値を無加工で端末へ流している。改行と ANSI エスケープを含むlanguageを仕込むと、Details セクションに任意の行を偽造できる。formatResultを実際に呼んで確認済み:Hash Chain: PASSに見える行の注入に成功した。影響
PASSを数えるような運用は騙せるXSS ではないので緊急ではないが、#248 と同じ「proof 由来の文字列を検証せず表示層へ流す」構造。
対応案
languageを allowlist 正規化する (shared 側に normalizer を置き web / CLI 双方が使う)。SyntaxHighlighter.isLanguageSupportedに相当する集合が既にある1 と 2 の両方が望ましい。1 だけだと将来別の proof 由来文字列が同じ経路を通る。
関連
classNameに入る。.hiddenを混ぜてコードプレビューを消せることを確認済み)modeの XSS。当時innerHTML50 箇所を洗ったが、CLI の stdout は対象外だった