yoi/tickets/permission-extension-point.md

5.5 KiB
Raw Blame History

パーミッション: パターンベースのツール実行制御

背景

現状の Scope はディレクトリ単位の書き込み制約で、静的な境界線。 実際のエージェント運用では、ツール単位・引数パターン単位の動的な権限制御が必要になる。

OpenCode はパターンベースのルールtool × pattern → allow/deny/askを持ち、 *.env への書き込み拒否や rm -rf の実行拒否を宣言的に設定できる。

方針

Permission の評価点は PreToolCall Hook とする。マニフェストにルールを宣言し、 insomnia 層が built-in の PreToolCall Hook として登録してツール呼び出し時に評価する。

deny はターン全体の Cancel/Abort ではない。対象 tool call を実行せず、 permission denied を表す is_error = true の synthetic tool result を履歴に追加してターンを継続する。 これにより provider が要求する tool_use / tool_result の対応を壊さず、LLM は拒否結果を見て別手段の検討やユーザーへの説明に進める。

askdeny の代替ではなく、ユーザー承認待ちを明示する action として扱う。承認されれば元の tool call を実行し、拒否されれば deny と同じ synthetic tool result に落とす。

[permissions]
default_action = "allow" # allow | deny | ask

[[permissions.rule]]
tool = "bash"
pattern = "rm *"
action = "deny"

[[permissions.rule]]
tool = "file_write"
pattern = "*.env"
action = "deny"

allowlist 型にしたい場合:

[permissions]
default_action = "deny"

[[permissions.rule]]
tool = "read"
pattern = "*"
action = "allow"

[[permissions.rule]]
tool = "grep"
pattern = "*"
action = "allow"

確認待ちを基本にしたい場合:

[permissions]
default_action = "ask"

[[permissions.rule]]
tool = "bash"
pattern = "rm *"
action = "deny"

評価順序:

  1. [permissions] が無い場合、Permission 層は無効。従来通り実行する
  2. [permissions] がある場合、default_action は必須
  3. [[permissions.rule]] は宣言順に評価し、最初に toolpattern が一致した rule の action を採用する
  4. 一致する rule が無ければ permissions.default_action を採用する

設計ポイント

  • 設計原則3: 新しい trait は作らない。PreToolCall Hook として実装
  • 設計原則2: マニフェストに宣言した以上、insomnia 層が解決する
  • Permission Hook は Pod が自動登録する built-in Hook とし、ユーザー追加 Hook より先に評価する
  • denyPreToolAction::Abort / 既存 Skip では表現しない。tool call 単位の拒否結果を履歴へ返すため、Worker 側に synthetic tool result を返せる action が必要
  • ask アクションは Pod Protocol の拡張が必要Event に PermissionRequest、Method に PermissionReply を追加)
  • ask を処理できない実行環境では暗黙に待機しない。設定時に validation error とするか、fail closed で deny 相当の synthetic tool result に落とす
  • Scope との関係: Scope は書き込みの物理的境界、Permission はツール実行のポリシー。補完関係
  • ルール評価はパターンマッチのみ。コンテキスト依存の判断はしない(シンプルに保つ)

段階的実装

  1. 拡張ポイントの記録(今): docs/pod.md の拡張ポイント表に追加
  2. deny/allow の実装(ツール実装時): default_action と rule 評価を manifest に追加し、built-in PreToolCall Hook でパターン評価
  3. 拒否 tool result の実装: deny が turn Abort ではなく synthetic error tool result として履歴に入るよう Worker の pre-tool action を拡張
  4. ask の実装Protocol 拡張時): Method/Event に Permission 関連メッセージを追加し、承認後に元 tool call を実行、拒否時は synthetic error tool result を返す

受け皿になる外部仕様

Agent Skills allowed-tools

tickets/agent-skills.md で ingest した SKILL.md の frontmatter には agent-skills 仕様の experimental field である allowed-tools (例: ["Read", "Bash"]) が含まれる場合がある。crates/memory/src/skill.rs::parse_skill_md 時点では tracing::warn! で受け流しているだけで、実効化していない。

本チケットの Permission 層が固まった時点で、Skill 由来 Workflow を実行中のみ当該 skill の allowed-tools リストに含まれるツールしか走れない形で反映する。スコープは「Workflow 実行中」相当 (Workflow の system message が context に乗っているターン) に限定する想定。skill 単位で local な permission 集合を持つので、グローバルな [[permissions.rule]] ルールとは独立に評価する。

実装上の足がかり:

  • WorkflowRecord の出所は WorkflowSource::Skill { dir } で識別済み (crates/memory/src/workflow.rs)。dir は manifest [skills] directories に書かれた skill ルートそのもの
  • 受け皿実装時に SkillFrontmatter::allowed_tools の保持先を WorkflowRecord に伸ばすか、別の SkillRecord registry を持つかは本チケット内で決める
  • 現状の tracing::warn! は受け皿実装と同時に消す

依存チケット

  • remove-hook-module.md — 完了。PreToolCall は Pod 層の hook::Hook<PreToolCall> として利用可能