Skip to content

feat(nightly-todo): agent の tool scope 限定・台帳の untrusted framing・公開面 screening (ADR-072 決定 12-14、順位 379/380/381) - #369

Merged
aloekun merged 3 commits into
masterfrom
feat/nightly-agent-trust-boundary
Aug 8, 2026
Merged

feat(nightly-todo): agent の tool scope 限定・台帳の untrusted framing・公開面 screening (ADR-072 決定 12-14、順位 379/380/381)#369
aloekun merged 3 commits into
masterfrom
feat/nightly-agent-trust-boundary

Conversation

@aloekun

@aloekun aloekun commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • 決定 12(順位 379): agent の file tools を Edit(work/**) へ限定し、Edit(master-ref/**) / Edit(publish/**) を deny。決定 7 の改ざん検知の予防側を入れる(検知は残す)
  • 決定 13(順位 380): 台帳フィールドを ===BEGIN/END_LEDGER_DATA=== で framing し、区切りの偽装(不可視文字分断を含む)を parse 側で exit 2
  • 決定 14(順位 381): 公開面(draft PR 本文)へ出る台帳由来テキストを screening。コードスパンで囲み、抜け出せる文字を無害化
  • #363 post-merge feedback が Tier 1 に挙げた 4 件のうち、378(別 PR で land 済み)に続く 3 件

Context

Why: 台帳の自由記述が無検証で無人 agent のプロンプトへ流入し、agent は workspace 全体に書け、その出力が公開面に出る — ADR-054 の信頼境界そのもの。定常運用(毎晩 03:00 JST)が既に始まっているため、期限「定常運用開始前」を過ぎている。

実装で判明した事実:

  • Write(path) 指定子は no-op。CLI 2.1.218 はファイル編集の scope を Edit(path) だけで判定し、Write を含む全編集ツールを Edit がカバーする(CLI 自身が「Use Edit(path) instead」と警告)。初版の Write(work/**) 等は「効いているように見えて何もしない飾り」だったため除去
  • Read(master-ref/**) の deny は hygiene にとどまるGrep が非スコープなので内容は迂回して読める。脅威モデルの本体は書き込み(ゲート改ざん)で、そちらは閉じている

Scope decision: probe(実測用の使い捨て step)は本 PR に含めない。pre-push security review が「build.rs の commit 混入 + env 名の広域露出」を正当に REJECT したため撤去済み。todo 系列も含めない(docs バッチへ)。

Validation

  • ローカル CLI で tool scope の両側を実測(2026-08-08):
    • deny: master-ref/PROBE.txt への Write は File is in a directory that is denied by your permission settings. で拒否、ファイル未作成・config 無傷
    • allow: 対照の work/ への Write は成功。加えて実 dispatch run で agent が順位 240 の対象 1 ファイルのみ編集し guard=success
  • cargo test -p cli-nightly-task-select: 41 passed(parse 層 good/bad 対 + screening 5 件 + 不可視文字/枠偽装の回帰)
  • cargo test -p lib-docs-policy: 12 passed
  • pnpm push pre-push review: verdict=APPROVE(simplicity / security 両 facet)
  • workflow YAML: js-yaml で 18 step 確認

Notes

pre-push review の fix step が is_control() の Cc 限定を突いて bidi/ゼロ幅(Cf)対応を追加(妥当と実測確認して採用)。その後、fix が公開面のみ塞ぎ parse 側の枠検査が素通りだった隣接穴を自分で塞いだ(LEDGER<ZWSP>_DATA で回避可能なことを実証)。

References

Summary by CodeRabbit

  • 改善
    • 夜間タスクの概要表示を安全に整形し、不要な制御文字や特殊な記号を除去。
    • 長すぎる概要は短縮し、内容が空の場合も分かりやすく表示。
    • タスク情報を明確に区切って処理し、意図しない指示の混入を防止。
    • 自動処理が変更できる範囲を作業領域に限定し、重要な領域への書き込みを禁止。
    • 安全性確認の項目と実走テスト結果を更新。

…creening (ADR-072 決定 12-14、順位 379/380/381)

夜間ループの信頼境界を 3 決定で埋める。台帳の自由記述が無検証で無人 agent の
プロンプトへ流入し、agent は workspace 全体に書け、その出力が公開面 (draft PR 本文) に
出る、という ADR-054 の信頼境界そのものへの対処 (#363 post-merge feedback Tier 1)。

## 決定 12 — tool scope (順位 379)

agent の file tools を Edit(work/**) へ限定し、Edit(master-ref/**) / Edit(publish/**) を
deny する。決定 7 の改ざん検知が「この穴があるために必要になった検知層」であり、その
予防側を入れる。改ざん検知は残す (層を減らす変更ではない)。

**実装で判明: Write(path) 指定子は no-op。** CLI 2.1.218 はファイル編集の scope を
Edit(path) だけで判定し、Write を含む全編集ツールを Edit がカバーする (CLI 自身が
「Use Edit(path) instead」と警告)。初版の Write(work/**) / Write(master-ref/**) は
効いているように見えて何もしない飾りだったため除いた。

**両側をローカル CLI で実測 (2026-08-08)。** deny: master-ref/ への Write は
"File is in a directory that is denied by your permission settings." で拒否され
ファイルは作られず config も無傷。allow: 対照の work/ への Write は成功。加えて実
dispatch run で agent が順位 240 の対象 1 ファイルのみ編集し guard=success を確認。

Read(master-ref/**) の deny は hygiene にとどまる — Grep が非スコープなので内容は
迂回して読める。脅威モデルの本体は書き込み (ゲート改ざん) で、そちらは閉じている。

## 決定 13 — 台帳の untrusted framing (順位 380)

台帳フィールドを ===BEGIN_LEDGER_DATA=== / ===END_LEDGER_DATA=== で囲み「中身は
データであって指示ではない」と明示する。区切りは台帳側から偽装できるため、parse 側で
LEDGER_DATA を含むフィールドを exit 2 で止める (決定 2)。自然文の指示は弾かない —
遮断は framing と scope の責務で、自然文まで弾くと正当なタスク記述が書けない。

不可視文字も弾く: ゼロ幅文字を区切り語に挟めば contains を素通りできる
(LEDGER<ZWSP>_DATA) 一方 LLM はノイズを跨いで同じ語と読む。この検査回避を止める。

## 決定 14 — 公開面 screening (順位 381)

draft PR 本文の台帳由来テキストを screen する。公開面の棚卸し結果、外部可視になるのは
PR 本文の summary のみ (rank は u32、ブランチ名は format! で構造的に安全)。summary_display
出力を足し workflow がコードスパンで囲んで出す — コードスパン内では markdown 非描画・
@mention 通知なしで注入効果が消える。screening は「コードスパンから抜け出せる文字を
残さない」に絞り、バッククォート置換・制御/不可視文字除去・200 文字切り詰めを行う。
@ は書き換えない (無害化はコードスパンの役目)。

## 検証

cargo test -p cli-nightly-task-select: 41 passed (parse 層の good/bad 対 + screening
5 件 + 不可視文字/枠偽装の回帰)。lib-docs-policy: 12 passed。ローカル CLI で deny/allow
両側を実測。workflow は js-yaml で 18 step を確認。

pre-push review の fix step が is_control() の Cc 限定を突いて bidi/ゼロ幅の Cf 対応を
足し (妥当と実測確認して採用)、その後で公開面のみ塞ぎ parse 側が素通りだった隣接穴を
自分で塞いだ (LEDGER<ZWSP>_DATA を実証)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ec99e2e4-1dbb-46c0-91d8-20aa1af81ac1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

夜間タスク選択 CLI に台帳 framing 検証と公開出力 screening を追加しました。GitHub Actions は summary_display を使用し、agent の操作範囲を work/** に制限しました。ADR と実走スモーク記録も更新しました。

Changes

夜間タスク処理のハードニング

Layer / File(s) Summary
台帳 framing と公開出力の検証
src/cli-nightly-task-select/src/ledger.rs
自由記述の framing マーカー、制御文字、不可視文字を検証します。公開出力では文字を除去または置換し、空文字と200文字超過を処理します。関連テストを追加しました。
選択結果への summary_display 追加
src/cli-nightly-task-select/src/main.rs
選択タスクの summary を screening し、summary_display として出力します。
agent prompt とツール範囲の更新
.github/workflows/nightly-todo.yml
台帳データを明示的な区切りで prompt に渡します。Claude Code のファイル操作を work/** に限定し、master-ref/**publish/** を拒否します。draft PR の本文には summary_display を使用します。
ADR と実走結果の更新
docs/adr/adr-072-nightly-todo-loop.md, docs/harness-improvement-plan.md
tool scope、framing、screening、整合性検知、実走スモーク結果、残課題の記録を更新します。

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LedgerSelector
  participant GitHubActions
  participant ClaudeCode
  participant DraftPR
  LedgerSelector->>GitHubActions: summary_display を出力
  GitHubActions->>ClaudeCode: framing 済み台帳データを prompt に渡す
  ClaudeCode->>GitHubActions: work/** の範囲で処理
  GitHubActions->>DraftPR: summary_display から本文を生成
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed タイトルは、tool scope の限定、台帳の framing、公開面の screening という主要変更を明確に示しています。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/nightly-agent-trust-boundary

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Monitor 分析 (GitHub Actions バックストップ)

  • トリガー: issue_comment (created) / 実行 run
  • CI: rust (ubuntu-latest) pending、rust (windows-latest) pending、CodeRabbit pending (review in progress)
  • レビュー状況: 人間レビューなし。CodeRabbit はレビュー未着 (処理中、conversation コメントは "Currently processing new changes..." のみ)。インライン指摘・会話コメントとも実質的な指摘は 0 件
  • Verdict: user_decision (CI 未完了・CodeRabbit レビュー未着のため、現時点では判定材料が揃っていない)

Applicable Findings (Critical / High / Major)

(該当なし — レビュー指摘なし)

Applicable Findings (Medium 以下)

(該当なし — レビュー指摘なし)

Filtered (not applicable)

(該当なし)

軽量サマリー (レビュー指摘が無いため diff 概要のみ)

ADR-072 (夜間 todo ループ) の tool scope 限定・台帳 untrusted framing・公開面 screening を実装する PR。5 ファイル変更 (+392/-44)。

ファイル 変更 概要
.github/workflows/nightly-todo.yml +46/-10 agent の --allowedTools/--disallowedToolswork/** へパススコープ限定、台帳データを ===BEGIN/END_LEDGER_DATA=== で囲み untrusted framing、公開面出力を summary_display (screening 済み) へ切替
docs/adr/adr-072-nightly-todo-loop.md +56/-8 決定 12-14 を追記、実走スモーク観測項目を 9→10 件に更新、既存の「検知のみで防止でない」記述を予防層追加に合わせて訂正
docs/harness-improvement-plan.md +5/-5 順位 379-381 を実装済みに更新
src/cli-nightly-task-select/src/ledger.rs +280/-20 screen_for_public_output (bidi/ゼロ幅/Tag block 等の不可視文字除去・バッククォート置換・200文字切詰め) と reject_prompt_frame_escape (枠区切り偽装の parse-time 拒否) を追加、テスト多数追加
src/cli-nightly-task-select/src/main.rs +5/-1 summary_display 出力を追加

CI (rust ubuntu/windows) と CodeRabbit レビューはいずれも進行中で結果未確定。

次のアクション

  • CI (rust ubuntu-latest / windows-latest) の完了と CodeRabbit レビューの到着を待ってから再評価する。
  • 現時点で人間側の追加アクションは不要。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/adr/adr-072-nightly-todo-loop.md (1)

371-390: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

実走スモークの分母と状態分類を統一してください。

ADR は観測項目を 10 件と定義しますが、Line 388 の 8 + 1 + 2 は 11 件です。計画書は 8 + 1 + 1 と記載します。coderabbitai[bot] allowlist の判定不能を母数へ含めるか、停止側の 'false' と未設定を別項目として数えるかを明示して、両文書の表と集計を一致させてください。

  • docs/adr/adr-072-nightly-todo-loop.md#L371-L390: 観測表、分母、充足・不成立・未確定の合計を一致させてください。
  • docs/harness-improvement-plan.md#L227-L227: ADR と同じ分母および状態分類へ更新してください。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/adr-072-nightly-todo-loop.md` around lines 371 - 390,
統計対象の定義と状態分類を両文書で統一する。docs/adr/adr-072-nightly-todo-loop.md
の観測表および集計を、`coderabbitai[bot]` allowlist の判定不能を含めるか、停止側の `'false'`
と未設定を分割するか明示したうえで、分母と充足・不成立・未確定の合計が一致するよう修正する。docs/harness-improvement-plan.md
の該当する実走スモーク記述も、同じ分母・項目数・状態分類へ更新し、両文書の集計を一致させる。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/adr/adr-072-nightly-todo-loop.md`:
- Around line 459-460:
残課題一覧から、完了済みの外部設定記録に関する箇条書きを削除または完了済みとして更新してください。ADR-072の§外部設定の実体にある記録と、harness-improvement-planの順位384の完了状態に合わせ、未記録という記述や対応要求を残さないでください。

In `@docs/harness-improvement-plan.md`:
- Around line 178-179: Update the test counts in the ledger framing and public
screening entries to match the implementation: use 7 framing regression tests
and 9 public screening tests, or replace the counts with the listed covered
cases. Preserve the existing implementation-status descriptions.

In `@src/cli-nightly-task-select/src/ledger.rs`:
- Around line 314-318: Update the truncation logic around the visible
character-counting function so the collected head length is MAX_CHARS minus
TRUNCATION_SUFFIX’s character count before appending the suffix. Preserve
unchanged strings at or below the limit, and update the tests to assert the
final output, including the suffix, never exceeds MAX_CHARS.
- Around line 337-355: Update is_bidi_or_invisible_format_char to reject LRM and
RLM (U+200E–U+200F) by adding the corresponding bidi-mark range to its checks.
Add regression coverage for both parser and screening paths, including a marker
containing these characters, and verify they are rejected and cannot remain in
published output.

---

Outside diff comments:
In `@docs/adr/adr-072-nightly-todo-loop.md`:
- Around line 371-390:
統計対象の定義と状態分類を両文書で統一する。docs/adr/adr-072-nightly-todo-loop.md
の観測表および集計を、`coderabbitai[bot]` allowlist の判定不能を含めるか、停止側の `'false'`
と未設定を分割するか明示したうえで、分母と充足・不成立・未確定の合計が一致するよう修正する。docs/harness-improvement-plan.md
の該当する実走スモーク記述も、同じ分母・項目数・状態分類へ更新し、両文書の集計を一致させる。
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ef4927fb-93c7-4ec1-9d03-3e7fab6bffe4

📥 Commits

Reviewing files that changed from the base of the PR and between e8f4b7a and 907628b.

📒 Files selected for processing (5)
  • .github/workflows/nightly-todo.yml
  • docs/adr/adr-072-nightly-todo-loop.md
  • docs/harness-improvement-plan.md
  • src/cli-nightly-task-select/src/ledger.rs
  • src/cli-nightly-task-select/src/main.rs

Comment on lines 459 to +460
- **外部設定 (GitHub App / repository variables・secrets) の実体が未記録**。決定 8 は「なぜ App token か」を厚く残す一方、App 名・インストール範囲・付与権限の実際・`NIGHTLY_APP_ID` (variable) / `NIGHTLY_APP_PRIVATE_KEY` (secret) / `AUTONOMY_ENABLED` (variable) の登録先と欠落時の倒れ方を 1 行も書いていない。[ADR-051](adr-051-cross-system-config-coupling.md) が内部設定と外部 SaaS 設定の論理結合に課す 3 点 (相互参照コメント / 期待値の組み合わせ表 / 両側同一 PR) が未実施の状態にあたる。実走スモークで GitHub UI を触る際に**設定メタデータ** (名前・登録先の別・付与権限のスコープ・所有者・ローテーション方針・欠落時の挙動) を確認し、§ 外部設定の実体 として本 ADR へ追記する。**秘密値そのもの (`NIGHTLY_APP_PRIVATE_KEY` の鍵本文や発行済み token) は ADR にも git 履歴にも残さない** — ADR-051 が記録を課すのは「結合の存在」と「期待値の組み合わせ」であって、秘密の実値ではない。
- **`master-ref/` を agent のファイルシステムから外す**。決定 7 は検知どまりで、防止には別 job + artifact 受け渡しへの構造変更が要る。実走スモークで agent が実際にワークスペース外へ手を伸ばすか観測してから判断する
- **`master-ref/` を agent のファイルシステムから外すか (順位 377 の判断材料)**。決定 12 の tool scope で**agent が直接書く経路は予防側で塞いだ**ため、当初の「検知どまり」状態は解消した。残るのは build script 経由の経路で、完全に外すには別 job + artifact 受け渡しへの構造変更が要る。**決定 12 のスコープが実走で効いていることを確認できるまでは、構造変更の要否を判断しない** — 効いていなければ前提が変わる

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

完了済みの外部設定記録を残課題から削除してください。

Line 459 は外部設定が未記録と記載します。Line 392 以降には実体の記録があります。docs/harness-improvement-plan.md Line 175 も順位 384 を完了と記載します。残課題一覧を現在の状態に合わせて更新してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/adr-072-nightly-todo-loop.md` around lines 459 - 460,
残課題一覧から、完了済みの外部設定記録に関する箇条書きを削除または完了済みとして更新してください。ADR-072の§外部設定の実体にある記録と、harness-improvement-planの順位384の完了状態に合わせ、未記録という記述や対応要求を残さないでください。

Comment thread docs/harness-improvement-plan.md Outdated
Comment on lines +178 to +179
| 380 | 台帳フィールドを agent prompt へ untrusted data として明示 framing | 🚀 1 | **実装済**(同 決定 13)。prompt の framing + parse 側での枠偽装拒否の 2 層。unit test 3 件 |
| 381 | 台帳由来 SUMMARY の draft PR 本文出力に screening を追加 | 🚀 1 | **実装済**(同 決定 14)。公開面の棚卸し済み(台帳由来で外部可視なのは PR 本文の `内容` のみ)。unit test 5 件 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

テスト件数を実装と一致させてください。

Line 178 の framing 関連テストは 3 件ではありません。ledger.rs には marker、自然文、ゼロ幅文字、Tag block、SOFT HYPHEN、bidi、制御文字の回帰テストがあります。
Line 179 の public screening テストも 5 件ではなく 9 件あります。件数を更新するか、件数を記載せず対象ケースを記載してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/harness-improvement-plan.md` around lines 178 - 179, Update the test
counts in the ledger framing and public screening entries to match the
implementation: use 7 framing regression tests and 9 public screening tests, or
replace the counts with the listed covered cases. Preserve the existing
implementation-status descriptions.

Comment on lines +314 to +318
if trimmed.chars().count() <= MAX_CHARS {
return trimmed.to_string();
}
let head: String = trimmed.chars().take(MAX_CHARS).collect();
format!("{head}{TRUNCATION_SUFFIX}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

接尾辞を含めて 200 文字以内にしてください。

Line 317 は 200 文字を取得し、その後で …(以下略) を追加します。結果は 200 文字を超えます。TRUNCATION_SUFFIX の文字数を上限から引いてください。テストは出力全体の文字数も検証してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli-nightly-task-select/src/ledger.rs` around lines 314 - 318, Update the
truncation logic around the visible character-counting function so the collected
head length is MAX_CHARS minus TRUNCATION_SUFFIX’s character count before
appending the suffix. Preserve unchanged strings at or below the limit, and
update the tests to assert the final output, including the suffix, never exceeds
MAX_CHARS.

Comment on lines +337 to +355
fn is_bidi_or_invisible_format_char(c: char) -> bool {
const BIDI_EMBEDDING_AND_OVERRIDE: RangeInclusive<char> = '\u{202A}'..='\u{202E}';
const BIDI_ISOLATE: RangeInclusive<char> = '\u{2066}'..='\u{2069}';
const ZERO_WIDTH_SPACE_AND_JOINERS: RangeInclusive<char> = '\u{200B}'..='\u{200D}';
const ZERO_WIDTH_NO_BREAK_SPACE: char = '\u{FEFF}';
const WORD_JOINER: char = '\u{2060}';
const SOFT_HYPHEN: char = '\u{00AD}';
const VARIATION_SELECTOR: RangeInclusive<char> = '\u{FE00}'..='\u{FE0F}';
const ARABIC_LETTER_MARK: char = '\u{061C}';
const TAG_BLOCK: RangeInclusive<char> = '\u{E0000}'..='\u{E007F}';
BIDI_EMBEDDING_AND_OVERRIDE.contains(&c)
|| BIDI_ISOLATE.contains(&c)
|| ZERO_WIDTH_SPACE_AND_JOINERS.contains(&c)
|| c == ZERO_WIDTH_NO_BREAK_SPACE
|| c == WORD_JOINER
|| c == SOFT_HYPHEN
|| VARIATION_SELECTOR.contains(&c)
|| c == ARABIC_LETTER_MARK
|| TAG_BLOCK.contains(&c)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

U+200E と U+200F も拒否してください。

is_bidi_or_invisible_format_char は LRM (U+200E) と RLM (U+200F) を検出しません。
そのため ===END_LEDGER\u{200E}_DATA=== は marker 検査と不可視文字検査を通過します。公開出力にも同じ文字が残ります。bidi mark の範囲を追加し、parser と screening の回帰テストを追加してください。

修正例
 fn is_bidi_or_invisible_format_char(c: char) -> bool {
+    const BIDI_MARK: RangeInclusive<char> = '\u{200E}'..='\u{200F}';
     const BIDI_EMBEDDING_AND_OVERRIDE: RangeInclusive<char> = '\u{202A}'..='\u{202E}';
@@
-    BIDI_EMBEDDING_AND_OVERRIDE.contains(&c)
+    BIDI_MARK.contains(&c)
+        || BIDI_EMBEDDING_AND_OVERRIDE.contains(&c)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn is_bidi_or_invisible_format_char(c: char) -> bool {
const BIDI_EMBEDDING_AND_OVERRIDE: RangeInclusive<char> = '\u{202A}'..='\u{202E}';
const BIDI_ISOLATE: RangeInclusive<char> = '\u{2066}'..='\u{2069}';
const ZERO_WIDTH_SPACE_AND_JOINERS: RangeInclusive<char> = '\u{200B}'..='\u{200D}';
const ZERO_WIDTH_NO_BREAK_SPACE: char = '\u{FEFF}';
const WORD_JOINER: char = '\u{2060}';
const SOFT_HYPHEN: char = '\u{00AD}';
const VARIATION_SELECTOR: RangeInclusive<char> = '\u{FE00}'..='\u{FE0F}';
const ARABIC_LETTER_MARK: char = '\u{061C}';
const TAG_BLOCK: RangeInclusive<char> = '\u{E0000}'..='\u{E007F}';
BIDI_EMBEDDING_AND_OVERRIDE.contains(&c)
|| BIDI_ISOLATE.contains(&c)
|| ZERO_WIDTH_SPACE_AND_JOINERS.contains(&c)
|| c == ZERO_WIDTH_NO_BREAK_SPACE
|| c == WORD_JOINER
|| c == SOFT_HYPHEN
|| VARIATION_SELECTOR.contains(&c)
|| c == ARABIC_LETTER_MARK
|| TAG_BLOCK.contains(&c)
fn is_bidi_or_invisible_format_char(c: char) -> bool {
const BIDI_MARK: RangeInclusive<char> = '\u{200E}'..='\u{200F}';
const BIDI_EMBEDDING_AND_OVERRIDE: RangeInclusive<char> = '\u{202A}'..='\u{202E}';
const BIDI_ISOLATE: RangeInclusive<char> = '\u{2066}'..='\u{2069}';
const ZERO_WIDTH_SPACE_AND_JOINERS: RangeInclusive<char> = '\u{200B}'..='\u{200D}';
const ZERO_WIDTH_NO_BREAK_SPACE: char = '\u{FEFF}';
const WORD_JOINER: char = '\u{2060}';
const SOFT_HYPHEN: char = '\u{00AD}';
const VARIATION_SELECTOR: RangeInclusive<char> = '\u{FE00}'..='\u{FE0F}';
const ARABIC_LETTER_MARK: char = '\u{061C}';
const TAG_BLOCK: RangeInclusive<char> = '\u{E0000}'..='\u{E007F}';
BIDI_MARK.contains(&c)
|| BIDI_EMBEDDING_AND_OVERRIDE.contains(&c)
|| BIDI_ISOLATE.contains(&c)
|| ZERO_WIDTH_SPACE_AND_JOINERS.contains(&c)
|| c == ZERO_WIDTH_NO_BREAK_SPACE
|| c == WORD_JOINER
|| c == SOFT_HYPHEN
|| VARIATION_SELECTOR.contains(&c)
|| c == ARABIC_LETTER_MARK
|| TAG_BLOCK.contains(&c)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli-nightly-task-select/src/ledger.rs` around lines 337 - 355, Update
is_bidi_or_invisible_format_char to reject LRM and RLM (U+200E–U+200F) by adding
the corresponding bidi-mark range to its checks. Add regression coverage for
both parser and screening paths, including a marker containing these characters,
and verify they are rejected and cannot remain in published output.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Monitor 分析 (GitHub Actions バックストップ)

  • トリガー: pull_request_review (submitted) / 実行 run
  • CI: rust (ubuntu-latest) pass (1m33s) / rust (windows-latest) pass (2m59s) / CodeRabbit pass (Review completed) / analyze (本 workflow自身) pending
  • レビュー状況: CodeRabbit が review #4888767791 を COMMENTED で提出 (2026-08-08T12:03:22Z、actionable comments 4件 + outside-diff comment 1件)。人間レビューなし
  • Verdict: needs_fix (Major の applicable finding が1件存在するため)

Applicable Findings (Critical / High / Major)

# File (Line) Reviewer Issue Recommended Action
1 src/cli-nightly-task-select/src/ledger.rs (337-355) CodeRabbit (Major, Security) is_bidi_or_invisible_format_char が LRM (U+200E) / RLM (U+200F) を検出しない。===END_LEDGER\u{200E}_DATA=== のように挟むと区切り検査・不可視文字検査の両方を回避でき、公開出力にも同文字が残る BIDI_MARK: RangeInclusive<char> = '\u{200E}'..='\u{200F}' を追加し is_bidi_or_invisible_format_char の判定に含める。parser 側 (reject_prompt_frame_escape) と screening 側 (screen_for_public_output) 双方の回帰テストを追加

Applicable Findings (Medium 以下)

# File (Line) Reviewer Issue Recommended Action
2 src/cli-nightly-task-select/src/ledger.rs (314-318) CodeRabbit (Minor, Functional Correctness) screen_for_public_outputMAX_CHARS (200) 文字を取得した後に TRUNCATION_SUFFIX (…(以下略)) を追加するため、切り詰め後の出力が200文字を超える head の取得文字数を MAX_CHARS - TRUNCATION_SUFFIX.chars().count() にしてから suffix を付与。テストは suffix 込みの最終文字数が200を超えないことをassertするよう更新
3 docs/harness-improvement-plan.md (178-179) CodeRabbit (Minor, Maintainability) 順位380/381 のテスト件数記載 (「3件」「5件」) が実装のテスト数と不一致 (実際は framing 7件・screening 9件、との指摘) 記載件数を実装の実測値に合わせて更新するか、件数を書かず対象ケースの列挙に置き換える

Filtered (not applicable)

# File (Line) Issue Filter Reason
4 docs/adr/adr-072-nightly-todo-loop.md (459-460) 残課題一覧の「外部設定の実体が未記録」項目が、同ADR内で既に記録済み・harness-improvement-plan.md 順位384も完了済みのため矛盾 Scope mismatch: docs/adr/ は read-only zone (analyze-coderabbit.md Step 2)
5 docs/adr/adr-072-nightly-todo-loop.md (371-390, outside diff) 実走スモークの観測項目「10件」の内訳集計が「8充足+1不成立+2未確定=11」となり分母と一致しない (harness-improvement-plan.md側の表記とも不一致の可能性) Scope mismatch: 主対象が docs/adr/ read-only zone。ただし docs/harness-improvement-plan.md 側の整合も要するため次アクションに記載

次のアクション

aloekun and others added 2 commits August 8, 2026 21:09
Resolved findings:
- [Minor] docs/adr/adr-072-nightly-todo-loop.md:460 完了済みの外部設定記録を残課題から削除してください。
- [Minor] docs/harness-improvement-plan.md:179 テスト件数を実装と一致させてください。
- [Minor] src/cli-nightly-task-select/src/ledger.rs:318 接尾辞を含めて 200 文字以内にしてください。
- [Major] src/cli-nightly-task-select/src/ledger.rs:355 U+200E と U+200F も拒否してください。
…t 指摘)

CodeRabbit 指摘 (docs/harness-improvement-plan.md L178-179): framing テストは
「3 件」ではなく実際は 9 件、public screening は「5 件」ではなく 10 件。自動 fix
(8aac859) が ledger.rs のコードとテストは直したが、計画書側の件数記載は stale の
まま残っていた。

件数は今後も変動する (この PR 内でも RLM/LRM 追加で 2 件増えた) ため、指摘の代替案
「件数を記載せず対象ケースを記載」を採り、固定値ではなく検証している観点を書く形に
した。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aloekun
aloekun merged commit 9f7c93e into master Aug 8, 2026
3 checks passed
@aloekun
aloekun deleted the feat/nightly-agent-trust-boundary branch August 8, 2026 12:47
aloekun added a commit that referenced this pull request Aug 8, 2026
…R-072 決定 14、#369/#370)

決定 14 は「公開面 = PR 本文」と狭く見ており step ログを見落としていた。Select task
step は exe 出力を tee で selected.txt と画面 (= Actions ログ) の両方へ出しており、
生の summary/target_files/caution が含まれていた。**public repo では step ログも
第三者に可視**なので、381 の screening を迂回する 2 つ目の公開面だった
(#369 post-merge feedback Tier 1 #1、High)。

## tee → リダイレクト

tee を > selected.txt に変え、生の出力はファイルに留めて $GITHUB_OUTPUT 経由でのみ
使う。ログへはマーカー行 (rank/branch/ledger のみ = 構造的に安全) と screening 済みの
summary_display だけを出す。

## 出力契約検証を別 step に分離 (#370 CodeRabbit Major 指摘)

初版は「マーカー欠落なら exit 1」を Select task step の中に置いていたが、同 step は
exit 2 (台帳破損) / exit 3 (該当タスク無し) を区別するため continue-on-error が必要で、
その非 success は下段 Stop step が「正常な no-op」として green で終える。検証を同 step に
置くと、**exe は成功したのに出力が壊れているケースの exit 1 も continue-on-error に
飲まれ、出力契約違反が「今夜は何も無かった」に化ける**。

検証を Validate and echo the task-selection output contract step に切り出し、
continue-on-error を付けないことで job の red として顕在化させる
(if: steps.select.outcome == 'success' なので exit 2/3 の no-op 経路とは分離)。

## ADR の保護方法を公開面ごとに明記 (#370 CodeRabbit Minor 指摘)

決定 14 が step ログと PR 本文の保護方法を混ぜて書いていた。実際は screening 処理は
共通だが囲み方が違う (PR 本文 = コードスパン、step ログ = 固定プレフィックス付き 1 行)。
公開面ごとに分けて記述した。

## 教訓

「公開面」は出力先を 1 つ塞ぐたびに次が見つかる (PR 本文 → step ログ)。棚卸しは
経路単位で行う、と決定 14 へ追記。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 8, 2026
…369 feedback) (#370)

* fix(nightly-todo): tee による生 summary の Actions ログ露出を塞ぐ (ADR-072 決定 14、#369 post-merge feedback)

決定 14 は「公開面 = PR 本文」と狭く見ており、step ログを見落としていた。
Select task step は exe 出力を tee で selected.txt と画面 (= Actions ログ) の両方へ
出しており、そこに生の summary / target_files / caution 行が含まれていた。
**public repo では step ログも第三者に可視**なので、これは 381 の screening を
迂回する 2 つ目の公開面だった (#369 post-merge feedback Tier 1 #1、High)。

## 修正

tee をリダイレクト (> selected.txt) に変え、ログへはマーカー行 (rank/branch/ledger
のみ = 構造的に安全) と screening 済みの summary_display だけを grep で出す。生の
出力はファイルに留まり $GITHUB_OUTPUT 経由でのみ使われる (後段の許可リスト grep は不変)。

## 実測

実データで確認: ログに出るのはマーカー行と summary_display (バッククォート置換済み)
のみ。grep exit 0 で set -e でも落ちない。生 summary はファイルに残り GITHUB_OUTPUT
用途は保たれる。

## 教訓

「公開面」は出力先を 1 つ塞ぐたびに次が見つかる (PR 本文 → step ログ)。棚卸しは
「PR 本文」で止めず経路単位で行う、と決定 14 へ追記した。narrow 修正が隣接エッジに
穴を作るパターン (memory dont-trust-takt-fix-output) の公開面版。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(nightly-todo): tee による生 summary の Actions ログ露出を塞ぐ + 出力契約検証を分離 (ADR-072 決定 14、#369/#370)

決定 14 は「公開面 = PR 本文」と狭く見ており step ログを見落としていた。Select task
step は exe 出力を tee で selected.txt と画面 (= Actions ログ) の両方へ出しており、
生の summary/target_files/caution が含まれていた。**public repo では step ログも
第三者に可視**なので、381 の screening を迂回する 2 つ目の公開面だった
(#369 post-merge feedback Tier 1 #1、High)。

## tee → リダイレクト

tee を > selected.txt に変え、生の出力はファイルに留めて $GITHUB_OUTPUT 経由でのみ
使う。ログへはマーカー行 (rank/branch/ledger のみ = 構造的に安全) と screening 済みの
summary_display だけを出す。

## 出力契約検証を別 step に分離 (#370 CodeRabbit Major 指摘)

初版は「マーカー欠落なら exit 1」を Select task step の中に置いていたが、同 step は
exit 2 (台帳破損) / exit 3 (該当タスク無し) を区別するため continue-on-error が必要で、
その非 success は下段 Stop step が「正常な no-op」として green で終える。検証を同 step に
置くと、**exe は成功したのに出力が壊れているケースの exit 1 も continue-on-error に
飲まれ、出力契約違反が「今夜は何も無かった」に化ける**。

検証を Validate and echo the task-selection output contract step に切り出し、
continue-on-error を付けないことで job の red として顕在化させる
(if: steps.select.outcome == 'success' なので exit 2/3 の no-op 経路とは分離)。

## ADR の保護方法を公開面ごとに明記 (#370 CodeRabbit Minor 指摘)

決定 14 が step ログと PR 本文の保護方法を混ぜて書いていた。実際は screening 処理は
共通だが囲み方が違う (PR 本文 = コードスパン、step ログ = 固定プレフィックス付き 1 行)。
公開面ごとに分けて記述した。

## 教訓

「公開面」は出力先を 1 つ塞ぐたびに次が見つかる (PR 本文 → step ログ)。棚卸しは
経路単位で行う、と決定 14 へ追記。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 8, 2026
* docs(todo): WP-18 セッションの観測を順位 385-388 へ登録し todo21.md を新設 (Phase 2)

WP-18 の PR 作業 (#364#370) で実測した自動化経路の運用問題を todo へ登録し、
384 完了削除・todo ローテーション・WP-11 記録を 1 バッチにまとめる。

## todo21.md 新設 (todo20.md が 56KB = 50KB 閾値超過)

新規追加先を todo20.md → todo21.md へ移行。breadcrumb を持つ 5 ファイル
(todo.md / todo8 / todo10 / todo13 / todo14) の「現在の追加先」ポインタと、
数詞「22つ/todo2-20」を持つ 8 ファイル (todo3-11) を 23つ/todo2-21 へ更新。

## 順位 385-388 (2026-08-08 実測、todo21.md)

- 385 (T3): cli-pr-monitor lock の liveness check 欠落 (復帰窓 30 分)
- 386 (T2): 監視・自動 fix 経路の空コミットで bookmark ずれ → merge/push 失敗。
  **本セッションで 7 回観測**、生成元確定、深さ非依存 revset が本命の対処
- 387 (T2): 自動 fix は push が BLOCK されてもローカル作業コピーを書き換える
- 388 (T3): post-merge-feedback の完了判定が書き込みと race し誤 failed marker

いずれも post-merge feedback には構造的に入らない (feedback の入力は PR diff と
レビュー指摘で、ツール自身の運用中の事象は拾わない)。

## 順位 384 完了・削除

外部設定の実体は ADR-072 § 外部設定の実体 に記録済み (#369/#370)。todo20.md の
full エントリと summary2 の行を削除し、完了記録の 1 行に置換。

## WP-11 記録 (harness-improvement-plan)

#366 で enforce 下の scope guard 誤検知を 1 件観測。anchor と remedy が別ファイルの
指摘は構造的に必ず BLOCK される。本採用判定の前に判定基準の再定義が要ることを記録。

## 検証

pnpm lint:docs OK (preamble + cross-ref + priority-inversion — 数詞 23 整合を含む) /
markdownlint 127 files 0 error。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: CodeRabbit 指摘 4 件に対応 — 実装確認のうえ断定を訂正 (#371)

いずれも「実コードを確認せず断定していた」箇所で、実装を verify して直した。

## #1 (harness-plan) WP-11 の「誤検知」→「設計どおりの保守的 deny」

evaluate_scope_guard の allowlist は allowlist_from_paths(findings.map(f.file)) =
finding の anchor 位置だけで、remedy が別ファイルなら含まない (ADR-054 も欠点として
明記)。#366 の BLOCK は誤検知ではなく設計どおりの保守的 deny。本採用の判定基準を
「この保守的 deny を誤検知に数えない」よう明確化する、と修正。

## #2 (todo.md/todo3-7) breadcrumb の todo20/todo2-20 残存

docs バッチで更新し漏れた参照を補完。todo.md 冒頭の使い分けを todo21 + summary2 まで、
todo3-7 の「todo2-20」を todo2-21 へ。全 docs で todo2-20 残存ゼロを確認。

## #3 (todo21:58) heads(::@ & bookmarks()) の複数返り

@ に複数 bookmark が付くと複数コミットを返し clone --head / PR 選択が多対象になる。
trunk 除外 + 単一 bookmark へ絞る (現行 is_trunk_bookmark 除外と同規律) 必要を追記。

## #4 (todo21:102) 388 の「race」断定を撤回

reconcile_takt_output → copy_feedback_report は find_latest_run_dir で run dir を
選ぶ (mod.rs:147 / takt.rs:84)。単純な write race と断定せず、latest 特定のずれ /
パス不一致 / 前後関係を「まず特定する」形へ。#367 では実体が run dir に存在した。

## 検証

pnpm lint:docs OK / markdownlint 0 error。scope guard・feedback reconcile の実装を
実際に読んで記述と一致させた。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 8, 2026
…Phase 2 C)

WP-18 の prompt injection 対策 PR (#369/#370) の post-merge feedback の採用候補の
うち、セッション中に未対応で価値の高い分をユーザー承認 (2026-08-09) のうえ登録する。

## 新規登録 (todo21.md、389-391)

- 389 (T1): Write(path) tool-scope 指定子の no-op を検出する settings validator。
  CLI 2.1.218 で Write() は no-op = deny の silent 無効化。順位 379 で実際に踏んだ。
  **検知は warning ではなく error (必須 CI 失敗) にする** — silent security failure は
  ADR-043 の fail-closed 対象 (#372 CodeRabbit 指摘)
- 390 (T2): 台帳 framing 区切りの定数 (LEDGER_DATA_FRAME_MARKER) と workflow
  リテラル (===BEGIN/END_LEDGER_DATA===) の cross-file 一致を CI 検証
- 391 (T3): jj の落とし穴 (squash 方向・空コミットでの bookmark ずれ) を
  dev-conventions へ。本セッションで複数回踏んだ。**操作例は再現可能な最小の初期状態
  つきで書く** (jj バージョン・リモート有無・コミットグラフ・bookmark 位置に依存する
  ため、断定形でなく前提つきで、#372 CodeRabbit 指摘)

## 順位 375 補強 (todo20.md)

narrow-fix が隣接エッジに穴を作る教訓を 5 項目目として追加 (#369/#370 で複数回
再演、memory dont-trust-takt-fix-output と同根)。新規 rank は立てず既存 375 を編集。

## 見送り (ユーザー非選択)

Cf カテゴリ網羅テスト (#369 T1#3) / GITHUB_TOKEN vs App token 挙動差テスト
(#364 T2#3) / pre-push warning 対応方針 (#370 T3#4) は今回登録しない。既に
セッション中に対応済みの候補も再登録しない。

## 検証

pnpm lint:docs OK / markdownlint 0 error。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 8, 2026
…Phase 2 C) (#372)

WP-18 の prompt injection 対策 PR (#369/#370) の post-merge feedback の採用候補の
うち、セッション中に未対応で価値の高い分をユーザー承認 (2026-08-09) のうえ登録する。

## 新規登録 (todo21.md、389-391)

- 389 (T1): Write(path) tool-scope 指定子の no-op を検出する settings validator。
  CLI 2.1.218 で Write() は no-op = deny の silent 無効化。順位 379 で実際に踏んだ。
  **検知は warning ではなく error (必須 CI 失敗) にする** — silent security failure は
  ADR-043 の fail-closed 対象 (#372 CodeRabbit 指摘)
- 390 (T2): 台帳 framing 区切りの定数 (LEDGER_DATA_FRAME_MARKER) と workflow
  リテラル (===BEGIN/END_LEDGER_DATA===) の cross-file 一致を CI 検証
- 391 (T3): jj の落とし穴 (squash 方向・空コミットでの bookmark ずれ) を
  dev-conventions へ。本セッションで複数回踏んだ。**操作例は再現可能な最小の初期状態
  つきで書く** (jj バージョン・リモート有無・コミットグラフ・bookmark 位置に依存する
  ため、断定形でなく前提つきで、#372 CodeRabbit 指摘)

## 順位 375 補強 (todo20.md)

narrow-fix が隣接エッジに穴を作る教訓を 5 項目目として追加 (#369/#370 で複数回
再演、memory dont-trust-takt-fix-output と同根)。新規 rank は立てず既存 375 を編集。

## 見送り (ユーザー非選択)

Cf カテゴリ網羅テスト (#369 T1#3) / GITHUB_TOKEN vs App token 挙動差テスト
(#364 T2#3) / pre-push warning 対応方針 (#370 T3#4) は今回登録しない。既に
セッション中に対応済みの候補も再登録しない。

## 検証

pnpm lint:docs OK / markdownlint 0 error。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant