セキュリティ防御
稼働中の4つのガード、それぞれがどこで動くか、そしてどれもカバーしないもの。
経緯についての注記
Section titled “経緯についての注記”2026-09まで、このページは3段階のシェルスクリプト防御を説明していました:決定的ブラックリスト、難読化/流出スキャナー、Haiku AI判定。いずれも .claude/hooks/ に置かれ、GREEN/YELLOW/RED の脅威レベルステートマシンが制御するという内容でした。
それらのスクリプトは、.claude/ を公開リポジトリから外したコミット ba015a48 で削除済みです。現在 .claude/ は丸ごと gitignore されています。出荷バイナリのどこからも読まれていませんし、脅威レベルステートマシンも存在しません。
実際に製品にあるものは、より小さく、より推論しやすいものです:gateway が各エージェントディレクトリにインストールする 2つの PreToolUse フック(どちらも Rust のサブコマンド)、メッセージ経路上の 1つの入力スキャナー、そして「誰が誰に指示できるか」を決めるファイル群に対する 1つのフィールド単位の凍結。
ガード1 — agent-file-guard(PreToolUse、Rust)
Section titled “ガード1 — agent-file-guard(PreToolUse、Rust)”duduclaw hook agent-file-guard はシェルスクリプトではなく実際のサブコマンドなので、macOS/Linux/Windows で同一に動作します。gateway は matcher Write|Edit|MultiEdit|NotebookEdit|Bash で <agent_dir>/.claude/settings.json に登録し、起動のたびに再登録します(agent_hook_installer)。登録は運用者の既存設定を上書きせずマージする方式です。
インストールされるコマンドはエージェント id(--agent)を持ち、v1.68.1 より後のリリースでは DuDuClaw ホーム(--home "<パス>")も持ちます。gateway は社員の CLI を環境変数を絞った状態で起動し、許可リストに DUDUCLAW_HOME がないため、以前のフックは $HOME/.duduclaw にフォールバックしていました。ホームが既定の場所にない環境では、実際のホーム内のパスがすべて「ホームの外」と判定されていました。インストーラは <ホーム>/agents/<id>(または <ホーム>/agents/.ephemeral/<id>)形式のすべての社員ディレクトリに --home を書き込み、シェルの特殊文字を含むパスはシングルクォートで囲みます。既存のインストールは次回の社員起動または gateway 起動時にその場で書き換えられます。
フックは次の順でホームを決めます:--home(絶対パスのみ);社員身分のない呼び出し元(フックコマンドに --agent がなく、環境にも DUDUCLAW_AGENT_ID がない場合)は従来どおり既定のホーム;フックの環境で明示的に設定された絶対パスの DUDUCLAW_HOME。作業ディレクトリからホームを推定することはしません。どれからもホームが得られない場合、その社員の Write/Edit/MultiEdit/NotebookEdit/Bash 呼び出しはすべて拒否され、推測した場所で判定することはありません。同じ変更で NotebookEdit も matcher に加わり、その notebook_path は Write の対象と同じように判定されます。フックのサブコマンドは stderr でのみ応答し、ログファイルは書かなくなりました。
次の場合に exit 2(Claude Code は「このツール呼び出しをブロック」と解釈)を返します:
- エージェントがエージェント構造ファイル(
agent.toml、SOUL.md、CLAUDE.md、.mcp.json…)を正規の<home>/agents/<name>/ツリー外に書き込んだとき。新規エージェントの作成はcreate_agentMCP ツール経由に限られ、そちらには委譲認可ゲートがあります; - エージェントが自分自身の
SOUL.mdを書き込んだとき。場所が正しくてもブロックします。人格は運用者が管理します。このフックにオプトインはありません。agent.toml [permissions] can_modify_own_soul = trueで明示的にオプトインしたエージェントは、agent_update_soulMCP ツールを通じて自分のSOUL.mdを変更でき、ファイルを直接書くことはできません; - エージェントが自分自身の
CONTRACT.tomlを書き込んだとき。場所が正しくてもブロックします(判定BlockedOwnContractWrite)。契約は運用者がエージェントに課す境界なので、オプトインのフラグはありません。ブロック時のメッセージは運用者に依頼するよう伝え、運用者はダッシュボードから変更します(contract.update、管理者のみ。このフックは通りません); - エージェントが他のエージェントのファイルに触れたとき;
- エージェントが DuDuClaw ホーム内のそれ以外の場所に書き込んだとき。社員 id を持つ(または主張した id の検証に失敗した)呼び出し元にとって、ホーム配下で書き込めるのは自分のエージェントディレクトリと共有の
attachments/だけです。許可リスト方式なので、監査ログ(tool_calls.jsonl。根拠チェック、判定者向けダイジェスト、直近の行動フィードが読みます)、evals/(ホールドアウトセットと他の社員の評価セットを含む)、すべての SQLite ストア、ブレーカーの状態、ライセンスと組織ファイル、グローバルのskills/、共有 wiki が対象になり、今後追加されるストアも最初から保護されます。これらの正規の書き手は gateway とゲート付きの MCP ツールで、どちらもこのフックを通りません。影響を受けないのは社員身分のない呼び出し元、つまりフックコマンドに--agentがなく、環境にもDUDUCLAW_AGENT_IDがない場合だけです。インストールされるフックコマンドには必ず--agentが付き、フックはそれを優先するため、オペレーターが社員のディレクトリで手動でclaudeを実行すると、その社員として判定されます(身分トークンを必須にしている場合は、検証に失敗した呼び出し元として扱われます)。オペレーターがこれらのファイルを変更するときは、ダッシュボードか通常のエディタを使ってください; - 書き込み先が、上記で拒否される場所を指すシンボリックリンクのとき。Write/Edit のパスは、書かれたとおりと実際の書き込み先(リンクとリンクの後の
..をたどる。宙に浮いたリンクは常に拒否)の2回判定され、どちらかが拒否すれば拒否です。相対パスはフック入力の作業ディレクトリを基準に、入力に作業ディレクトリがない場合は社員自身のディレクトリを基準に解決します。実際の書き込み先を確定できないパスは「無法確認這次寫入實際會落在哪裡」(書き込みが実際にどこに落ちるか確認できない)というメッセージで拒否します; - エージェントが、削除された社員が保管される
agents/_trash/配下に何かを書き込み、移動、または削除したとき(Bash はヒューリスティック); - エージェントが Bash から
duduclaw agent create <name>を、削除された社員が使っていたために予約されている名前で実行したとき(委譲の隔離を参照)。この拒否はagent_name_reservedとして、path_kindをcli_bash_agent_createとして監査されます。
Bash では、自分の SOUL.md と自分の CONTRACT.toml のルールはヒューリスティックです。書き込み形のコマンドがファイルを agents/<自分>/… として、または CONTRACT.toml、./CONTRACT.toml のような相対表記で指定するとブロックされます。id の検証に失敗した呼び出し元は、agents/ 配下のどこかを指定する書き込み形のコマンドがすべて拒否されます(Write/Edit と同じ扱い)。
Bash とホームの許可リスト。 Bash 側は同じ許可リストでコマンド文字列を判定します。以下の「保護されたホーム対象」とは、ホーム内で自分の社員ディレクトリと attachments/ 以外の場所、他の社員のディレクトリ、削除された社員の保管場所を指します。身分の検証に失敗した呼び出し元では、自分のディレクトリも含みます。
- 判定の前に、bash と同じ読み方でコマンドを戻します。行継続、バックスラッシュのエスケープ、引用符はすべて先に元に戻します。出力やエラー出力を
/dev/nullに捨てる書き方(2>/dev/null、>/dev/null)と、ディスクリプタを別のディスクリプタに複製する書き方(2>&1)は書き込みとみなしません。出力とエラーをまとめてファイルへリダイレクトするのは書き込みです。 - 既知の読み取り専用コマンドの一覧に載っているコマンド(一覧、読み取り、検索、比較など)は引数を検査しません。ただし出力のリダイレクトは検査します。出力をファイルに書けるオプションや、他のコマンドを実行できるオプションを持つコマンドは一覧に入れないか、そのオプションを使ったときは読み取り専用とみなしません。社員が一覧にないコマンドでホーム配下のファイルを読もうとすると拒否されるので、一覧にあるコマンドか対応する MCP ツールを使ってください。
- コピー系のコマンド(コピー、インストール、ダウンロード、アーカイブ展開)はコピー先だけを見ます。ホーム内のファイルを自分のディレクトリへコピーするのは通ります。
- 引数が指すものを変更するコマンド(移動、削除、リンク作成、権限・所有者・タイムスタンプの変更、同期、データベースのコマンドラインツールなど)とインタプリタ・シェルは、引数のどれか(インラインのプログラム文字列を含む)が保護されたホーム対象なら拒否します。どのコマンドでも、保護されたホーム対象への出力リダイレクトは拒否します。
- それ以外のコマンド(解析できない前置オプションの場合を含む)は、引数のどれかが保護されたホーム対象なら、書き込みの有無にかかわらず拒否します。一覧にないコマンドが保護されたホームのパスに触れる唯一の出口は、既知の読み取り専用コマンドの一覧です。
- ホーム内で社員ディレクトリと
attachments/以外にあるデータベースファイル(*.dbとその-wal/-shm/journal ファイル、*.sqlite*)は、コマンドのどこに現れても読み取りを含めて拒否します。社員ディレクトリとattachments/内のデータベースはこのルールの対象外です(他の社員のディレクトリへの書き込みは引き続き拒否されます)。 - 相対パスはフック入力の作業ディレクトリ(なければ社員自身のディレクトリ)を基準に解決し、コマンド内の
cd/pushdにも追従します。作業ディレクトリを推定できず、コマンドのどこかで保護されたホーム上の場所を指している場合、検査対象の位置にある相対パスは拒否します。どこも指していなければ判定しません。 - 検査対象のパスにある既存のシンボリックリンクは解決し、実際の場所も判定します。解決できない検査対象のパスは、宙に浮いたリンクを含めて常に拒否します。
これは減速帯であって隔離ではありません。本当の隔離は、エージェントに Bash を与えないことです。制約は「これらのガードがカバーしないもの」に記載しています。
ライブフォーク(fork_run)は同じファイルを反対側から守ります。ブランチはエージェントの構造ファイルを読めますが、ブランチをエージェントのディレクトリに昇格するとき、SOUL.md、CONTRACT.toml、agent.toml、.mcp.json、.claude/ などのエージェント構造ファイルが親のものを上書きすることはありません。
ガード2 — data-file-guard(PreToolUse、Rust、RFC-23 §14.4)
Section titled “ガード2 — data-file-guard(PreToolUse、Rust、RFC-23 §14.4)”ガード1が守るのは DuDuClaw 自身の構造ファイル、こちらが守るのは顧客のデータです。
Read と Bash は Claude Code の組み込みツールなので、cat customers.csv は file_read/csv_read/xlsx_read が必ず通る MCP 匿名化チョークポイントを通りません。インストーラーは duduclaw hook data-file-guard を matcher Read|Bash に登録します。判定ロジックは duduclaw_core::data_file_guard にあり、CLI サブコマンドと gateway のインストーラーテストが同じ実装を共有します。契約はガード1と同じ:exit 0 で許可、exit 2 + stderr でブロック、stderr はモデルに提示されます。
gateway が spawn 時に DUDUCLAW_DATA_FILE_GUARD を設定しない限り何もしません。gateway がこれを設定するのは、そのエージェントで匿名化が実際に有効なときだけです。匿名化がオフの環境では、このガードが存在する前とバイト単位で同じ挙動になります。
H10(2026-09)以前は <agent_dir>/.claude/hooks/data-file-guard.sh に置かれた POSIX シェルスクリプトで、PATH に bash がない Windows ホストではまったく機能していませんでした——フックコマンドが失敗し、Claude Code は 2 以外の終了コード(“command not found” を含む)を許可として扱うため、誰も気づかない場所でガードが消えていたのです。インストーラーはアップグレード時に残存スクリプトを削除するので、古いコピーが現役のガードと取り違えられることはありません。
明示された制約:Bash 側の検査はファイル名を照合します。パスを動的に組み立てるコマンド(python -c "open(chr(99)+…)")はそのまま通り抜けます。本当の防御は MCP ツール面であり、このガードはモデルが無防備な経路を取る確率を下げるものです。ヒューリスティックであってサンドボックスではありません。
ガード3 — input_guard(プロンプトインジェクションスキャナー、Rust ライブラリ)
Section titled “ガード3 — input_guard(プロンプトインジェクションスキャナー、Rust ライブラリ)”duduclaw_security::input_guard::scan_input はテキストを11のルールカテゴリで 0–100 点に採点し、DEFAULT_BLOCK_THRESHOLD(60)以上でブロックします:
| ルール | 重み | 単独即ブロック |
|---|---|---|
instruction_override |
40 | はい |
role_hijack |
35 | はい |
tool_abuse |
30 | はい |
data_exfiltration |
25 | はい |
system_prompt_extraction |
30 | いいえ |
encoding_bypass |
25 | いいえ |
termination_manipulation |
30 | いいえ |
authority_escalation |
信号1種類につき 35、異なる2種類で加算 | いいえ |
memory_poisoning |
信号1種類につき 30、異なる2種類で加算 | いいえ |
role_provenance |
枠1種類につき 35、異なる2種類で加算 | いいえ |
action_binding |
30 | いいえ |
パターンは英語と中国語(繁体字と簡体字)をカバーします。テキストは先に NFKC 正規化され(unicode_normalizer)、ホモグラフや不可視文字の小細工はパターン照合をすり抜けられません。
中国語のカバー範囲(v1.67.1)。 リリース済みのバージョンは中国語の指示上書きを4つの完全一致文字列でしか照合しておらず、「所有」「之前」「的」などの語を1つ挟むだけで通り抜けました。実地テストでは、そうした4文が user_profile_record 経由で保存されました。v1.67.1 からは次のとおりです。
instruction_override:上書き動詞(忽略/無視/忘記/忘掉/不要理會/不用理會/別管、簡体字形を含む)の後、同じ節の中で 12 文字以内に指示系の名詞(指示/指令/規則/提示詞/系統提示)があり、その間に範囲語(先前/之前/以上/上面/上述/前面/所有/全部/一切/你的/原本/原來)がある場合に一致します。空白は数えず、。!?;と改行で節が終わります。重みと即時ブロックは英語のフレーズと同じです。system_prompt_extraction:抽出名詞(系統提示詞/系統提示語/你的系統提示/你的指示/你的設定)と出力動詞(輸出/顯示/告訴我/給我看/洩漏/列出/重複)が順不同で 12 文字以内にある場合。採点は英語のルールと同じで、重み 30、単独ではブロックしません。単独の「系統提示」は「システム通知」の意味もあるため抽出名詞に含めていません。role_hijack:「你現在是管理員模式」「開發者模式」「越獄模式」「你現在不受限制」「進入越獄模式」などの固定フレーズと、単独の「越獄模式」。英語と同じ扱いです。- しきい値と英語のリストは変わりません。
既知の誤検知。 形で照合するため、上書き動詞・範囲語・指示系の名詞が1つの短い節に並ぶ普通の文もブロックされます。たとえば「請忽略之前寄的指示,以新版為準」「請忽略以上規則中的第三條,已經取消」「忘記之前的規則了,可以再說一次嗎」です(テスト known_benign_shapes_that_do_match で固定)。「越獄模式」に触れるだけでもブロックされます。回避策は上書き動詞を使わずに言い換えることで、たとえば「之前的指示作廢,以新版為準」です。近い文「之前的報價單請作廢,以新的指示為準」は、一致してはならない文のテスト一覧に入っています。このスキャナーはフレーズによるヒューリスティックで分類器ではなく、実際の会話データでの計測もしていません。
termination_manipulation(LoopTrap、arXiv:2605.05846)は意図的に即ブロックにしていません。重み 30 は閾値より低く、単独一致では警告と監査のみでブロックしません。これにより通常の「続けてください」が誤検知されずに済みます。
文型ファミリー(v1.68.0)。 レッドチーム台帳で、AI 社員を狙う4つの手法がどれも0点だとわかりました。現在はそれぞれに英語と繁体字中国語のアンカー付き正規表現があります。
authority_escalation:ユーザーの権限ではなく社員自身の権限で実行させようとする文(“use your own service account / admin role”、“not my permissions”、“the system trusts you more than me”、用你自己的服務帳號/管理員身分、不要用我的權限、系統比較信任你)。memory_poisoning:恒久的な特権を記憶させようとする文(“remember this permanently”、“as a fact about me”、“I am always allowed to”、永久記住、記在你的記憶裡、以後每次對話都照辦、我一律有權限)。普通の「請記住…」、“remember to…”、“remember my name” は一致しません。role_provenance:ユーザーメッセージ内の偽のシステム・ツール・運営者の枠:[SYSTEM]、<tool_result>タグ、approval=granted、“policy update from the platform operator”、「本次對話已解除…限制」、平台營運方政策更新、引用文中の AI 宛てメモ(給 AI 助理的附註)とそれを隠す指示(不要提到這段附註)。枠はタグ全体でのみ一致するので “our system sends a tool result” は一致しません。開始タグと終了タグの組は1種類として数えます。action_binding:1つの承認を別の操作に流用する文(“use that same approval to…”、“use the approval from step 1”、用同一個核准、用第一步的核准去…)。「既然已經核准預算,請安排會議」は一致しません。
重みの方針:信号1つだけなら警告と監査のみ(30–35、60 未満)です。1つのメッセージに同じファミリーの異なる信号が2種類ある場合、または信号1つに既存ルール(instruction_override、system_prompt_extraction など)が加わる場合にブロックします。自身で加算されるのは3つのファミリーです:authority_escalation(「用你的服務帳號」と「系統比較信任你」で 70)、memory_poisoning(「永久記住」と「我一律有權限」で 60)、role_provenance([SYSTEM] と approval=granted で 70)。action_binding は自身では加算されず、他のルールと組み合わさったときだけブロックします。同じ信号が2回出ても1回と数えます。[SYSTEM] 1つ、または <tool_result>…</tool_result> の1組だけなら 35 のままです。既知の代償:たまたま信号を2種類含む普通の文もブロックされます。たとえば “please use your admin account, not my permissions, to fix the shared folder” です(テスト known_benign_shapes_blocked_by_stacking で固定)。信号が1つだけの言い方にするか、管理者に直接頼んでください。一致があれば本文を捨てる呼び出し側(蒸留、プロフィール書き込み)は、これらの文型を含む本文も捨てるようになりました。各ファミリーの陽性例と、似ているが一致してはならない文は input_guard.rs のテストで固定しています。
一致したときに影響が出る場所(確認済みの呼び出し箇所):
- チャットで受信したメッセージ(
channel_reply、scan_input_with_audit):ブロックされたメッセージには警告の返信が返り、AI には渡されません。 - MCP ツール呼び出し(
mcp_dispatch、シリアライズした引数にscan_input_with_audit):引数がブロック対象の文を引用していると、呼び出しは拒否され監査されます。 - 会話事実・プロフィール・知識振り分けの抽出(
wiki_ingest、profile_distill、knowledge_route):ブロックしない抽出ルールを含め、いずれかのルールに一致した内容は破棄されます。 user_profile_record:predicate と値をスキャンし、ブロック水準なら拒否します。duduclaw migrate fromのインポートはブロック対象の項目をスキップし、エキスパートパックのインストールはブロック対象のパックを拒否します。- Agent Mail:一致した受信メールも保存されますがフラグが付き、フラグ付きのメールが AI 従業員を起動することはありません。
- リマインダー:プロンプトがブロック対象のリマインダーは実行されません。
ガード4 — org_field_guard(組織権限の凍結)
Section titled “ガード4 — org_field_guard(組織権限の凍結)”A2A 委譲の述語(delegation_policy::can_delegate)は、agent.toml の [agent] reports_to/department/name と config.toml の [delegation]・[acp] を読んで「誰が誰に指示できるか」を決めます。どちらもただのファイルなので、Edit を持つエージェントは自分の reports_to を被害者に向けて書き換え、「部下 → 上位」ルールを主張できてしまいます。裁かれる側が証拠を所有している状態でした。
org_field_guard は同じ agent-file-guard フックの中で動き、再構成した書き込み後の内容をディスク上の現状とフィールド単位で比較します。保護対象のフィールドやセクションに変更があれば拒否します。[capabilities] はキー名リストではなくテーブル全体として凍結されるので、将来のリリースで追加される capability キーは、誰かがリストを拡張するのを思い出した日ではなく、着地した当日から保護されます。
社員自身のセキュリティ設定。 社員 id を持つ(または検証できない)呼び出し元に対して、自分の agent.toml の残りの部分は許可リスト方式で凍結されます。変更できるのは編集可能なセクションだけで、それ以外のセクションは、今後のリリースで追加されるものも含めて既定で保護されます。編集可能なセクションは [agent]、[model]、[prompt]、[heartbeat]、[proactive]、[research]、[goal_intent]、[memory]、[skills]、[sticker]、[cultural_context]、[preset]、[planner] です。その中でも [agent] role、[prompt] cli_bare_mode(Claude CLI にフックを飛ばさせる設定)、[model] account_pool と、組織フィールド [agent] reports_to / department / name は凍結されたままで、[capabilities] はテーブル全体が凍結されます。この許可リストは社員身分を持つすべての呼び出し元に適用され、社員のディレクトリで手動で claude を実行するオペレーターも含みます(Guard 1 参照)。オペレーターがこれらのセクションを変更するときは、ダッシュボードか通常のエディタを使ってください。以前からの組織フィールドと [capabilities] の行はこれまでどおり全員に適用されます。
社員自身の .mcp.json と CLI 設定。 社員の身元を持つ(または検証できない)呼び出し元は、自分のディレクトリ内のどの階層の .mcp.json にも一切書き込めません(Write、Edit、MultiEdit、NotebookEdit。最上位のものは Bash のルールが以前から拒否しています)。gateway はこのファイルを指定して Claude CLI を起動し、CLI はそこに並ぶすべてのサーバーを起動するため、コマンドがインタプリタの項目を 1 つ足すだけで、次の起動時に運用者の OS ユーザーとして任意のコマンドが実行できてしまいます。Bash の権限は要りません。出荷済みのすべてのバージョンが影響を受けます。以前は社員が「無関係な」サーバーを自分で追加できました。今後 MCP サーバーの追加は、ダッシュボード、ダッシュボードで出す MCP インストール申請(mcp.install_request、管理者の承認後に自動インストール)、expert pack、または運用者による直接編集で行います。いずれも hook を通りません。同じ呼び出し元は、自分のディレクトリ内の CLI 設定にも書き込めません。どの階層でも .claude/ 配下(設定、hook、スラッシュコマンド、サブエージェント、skill)と .claude.json という名前のファイル、そして最上位の Codex・Gemini・Grok・Antigravity の設定ディレクトリ(.codex/、.gemini/、.grok/、.agents/)です。名前の比較は大文字小文字を区別しません。動作の変更:社員ディレクトリ内に複製したプロジェクトも CLI が起動する場所なので、社員はそのプロジェクトの .claude/ を読めますが、書けません。運用者には従来のルール(.mcp.json は身元キーのみ凍結)が適用されます。gateway は社員の .mcp.json を Claude CLI に渡す前(チャネル返信、ディスパッチ、heartbeat の能動チェック、duduclaw eval の live モード、live fork がブランチを複製する前の親ディレクトリ)と起動時にも DuDuClaw の項目をまるごと作り直し(コマンド、引数、環境変数)、他の項目はそのまま残します。ファイルを確認できない場合(通常ファイルでない、読めない、JSON として不正、または duduclaw 実行ファイルのパスが絶対パスでない)はその起動を行わず、監査イベント(mcp_config_unverified)を記録します。この拒否はファイルの問題でありアカウントの問題ではないため、アカウントはクールダウンに入らず、別のアカウントでの再試行もしません。チャネル返信はローカルモデルや Direct API に切り替えて答えず、どの社員の設定が確認できなかったかを伝えるメッセージを表示します。.mcp.json を書くすべての処理が共有するロックは社員ディレクトリではなく <home>/locks/ にあるため、社員がそこに作ったファイルやディレクトリで妨げることはできません。アップグレード前に追加された項目は残ります。duduclaw doctor は DuDuClaw が書いていない項目をすべて一覧表示します(名前とコマンドのファイル名のみで、引数、環境変数、URL は表示しません)。運用者は一つずつ確認または削除してください。live fork のブランチを社員ディレクトリに採用するときは、.claude.json、.agents/、.codex/、.gemini/、.grok/、およびどの階層の .claude/、.claude.json、.mcp.json も持ち帰りません。制限:これは hook なので、制限のない Bash を持つ社員はファイルを変更できます。Codex、Gemini、Grok、Antigravity の各 runtime は hook を実行せず、その MCP 設定ファイル(上記 4 つのディレクトリ)も社員ディレクトリにあるため、これらの runtime では同種の問題はこの修正で扱われていません。実際の CLI で未検証:Claude Code が社員ディレクトリの .claude.json を読むかどうか(いずれにせよ凍結済み)。
構造上 fail-closed です:新しい内容がパースできない、既存の内容がパースできない、書き込み意図が再構成できない——いずれも拒否します。既存の agent.toml、config.toml、.mcp.json が読み取れない場合も拒否します(以前は新規ファイルとして許可していました)。ファイルがまだ存在しない場合は許可します。作成は create_agent 経由であり、そちらに独自のゲートがあるためです。
正当な変更の経路はすべて残っています:MCP agent_update ツールとダッシュボードの agents.update RPC。どちらもこのフックを通りません。
出典単位の忘却には管理者の承認が必要
Section titled “出典単位の忘却には管理者の承認が必要”duduclaw memory forget-source はメモリを完全に削除するため、手前に3つの関門があります。手順は会話・スケジュール実行・インポートファイルを忘れる、仕組みはメモリインテリジェンスにあります。
ダッシュボード承認。 plan は、プラン id とプランのハッシュに結び付いた承認リクエストを1件出します(action_kind は memory_forget_source)。このリクエストはダッシュボードでのみ、管理者のみが決定できます。チャネルのボタンや返信は拒否されます。apply --confirm は、そのリクエストが承認済みで、同じプランのハッシュを指し、プランが期限切れでない場合にだけ実行されます。リクエストはプランと一緒に期限切れになります(既定 30 分、最長 24 時間)。カードには件数と出典のラベルだけを載せ、メモリの内容は載せません。このリクエストがローカルのコマンドラインから作られたこと、誰が入力したかを確認する手段がないことを、固定の文言で伝えます。この承認を無効にする設定はありません。[memory] forget_source = false は、新しいプランの作成と適用を止めるだけです。
Bash ルール。 従業員の身元が付いた、または身元未検証の呼び出し元に対し、agent-file-guard の Bash レーンは、duduclaw と duduclaw-pro(パス付きや .exe も含む)の後に memory forget-source または memory migrate-namespace が続くコマンドを拒否します。サブコマンドは問わず、読み取り専用の list も同様です。判定は BlockedOperatorMemoryCommand で、メッセージは従業員にオペレーターへ依頼するよう伝えます。migrate-namespace は、これまで Bash レーンの対象外でした。これは減速帯であってサンドボックスではなく、次のものは防げません。
memoryとサブコマンドの間にグローバルオプションを挟む書き方。例:duduclaw memory --redact on forget-source …- コマンド置換で実行ファイルを組み立てる書き方。例:
"$(command -v duduclaw)" memory forget-source … - コマンド文字列をパイプで実行ファイルに渡す書き方。例:
echo memory forget-source … | xargs duduclaw - コマンド名の照合を避けるその他の書き方、および「これらのガードがカバーしないもの」に挙げた他の回避方法
AI セッション判定。 gateway が従業員のプロセスに設定する変数のどれか1つでも、空であっても存在すれば、コマンド自身が実行を拒否します。従業員の身元とトークン、ターンとセッションの id、そのターンのユーザーメッセージ番号と時刻、ディスパッチのセッションと run id、委任の送信者・起点・深さ、hop の深さ、返信チャネル、MCP API キー、データファイルガードのフラグ、goal ラウンドのタスク id です(唯一のリスト ai_session_guard::AI_SESSION_ENV_VARS。2026-10-06 以降、承認を提出するすべてのオペレーターコマンドが共有します)。オペレーター自身のターミナルにはこれらがありません。この判定は最初の一線にすぎません。従業員の Bash から直接実行したコマンドに対しては信頼できません。Bash を持つ従業員は変数を unset できるからです。本当の隔離は、Bash を与えないこと、または従業員をタスクサンドボックスで動かすことです。同じ OS ユーザーで動き、任意のコマンドを実行でき、ファイルガードを意図的に回避する従業員は、ローカルのデータベース(approvals.db、memory.db)を直接書き換えられます。
コマンドラインからの運用者操作は 1 つの承認ゲートを共有する
Section titled “コマンドラインからの運用者操作は 1 つの承認ゲートを共有する”ターミナルから状態を変えるコマンドは 4 つあります:duduclaw ops channel-ingress(LINE 受信箱)、duduclaw responsibility(継続タスク)、duduclaw memory forget-source apply、duduclaw ops computer-workspaces。ターミナルでは誰が入力したかを証明できないため、4 つとも、現在も Admin であるアカウントがダッシュボードで判断するリクエストを作るだけで、コマンドはその後 1 回だけ適用します。v1.70.0 の次のリリースから、4 つは 1 つの実装(gateway クレートの approval/operator_cli_gate.rs)と 1 つの種別表を共有します。ダッシュボードでの判断だけが有効で、同じリクエストかつ同じ状態(LINE の一括処理を除く)に限られます。待機中に対象の状態が変わるとカードは取り下げられ(state_changed)、新しいリクエストが作られます。その場で書き換えることはありません。承認は 1 回の条件付き更新で消費され、競合に負けた実行は何もせず、新しいリクエストも作りません。待機できるのは操作と対象の組ごとに内容違いで 3 件、種別ごとに 20 件までで、通知は対象ごとに 1 時間 2 回までです(忘却リクエストは AI 従業員ごと)。approvals.decide は 1 つのチェックで Admin 以外の判断を拒否します。4 つのコマンドは AI 従業員のセッション環境変数の一覧も共有します(CLI クレートの ai_session_guard.rs)。制限は変わりません。Bash を制限なく持つ従業員はローカルのデータベースを直接書き換えられます。
アップグレード時の注意。 このゲートは結び付け情報をリクエスト内容の gate オブジェクトに保存します。v1.70.0 以前に作られたリクエストにはこのオブジェクトがないため、新しいバージョンは照合も件数への算入もしません。アップグレード前に作られたリクエストは、承認済みで未適用のものでも、アップグレード後には適用されません。コマンドをもう一度実行すると新しいリクエストが作られ、改めて承認が必要です。古い待機中のカードには期限切れまで通常どおりリマインダーの通知が届きます(継続タスクのリクエストはもともと送りません)が、承認しても何も起きません。
補助レイヤー
Section titled “補助レイヤー”MCP 認可ゲート — すべての MCP ツールはスコープ表に列挙されており、表にないツールは既定で Admin スコープを要求します。スコープ、エージェント単位の capability 付与、denied_tools はそれぞれディスパッチのフロントドアで強制され、拒否はすべて error_class 付きで監査されます。
SOUL.md ドリフト検知 — soul_guard は起動時と各ハートビートティックで SOUL.md の SHA-256 フィンガープリントを取り、.soul_history/ に最大10世代のバックアップを保持し、Agent Stability Index とともにドリフトを報告します。
監査証跡 — tool_calls.jsonl はすべてのツール呼び出しを記録し、result_text/input_text はマスク済み(3パスのシークレットマスキング、切り詰めより先にマスク)、パーミッションは 0600、行はハッシュチェーンで連結され、16 MB でローテーションします。security_audit.jsonl はセキュリティイベントを別に保持します。このログはグラウンディング事前チェックと受け入れ判定者が読む証拠源でもあるため、弱めれば検証も弱まります。
エージェント単位の鍵分離 — MCP API キーとコネクタ認証情報はエージェント単位で、secret_ref 経由で解決されます。1つのエージェントの漏洩がプラットフォーム全体の漏洩にはなりません。チャネルの認証情報は2種類に分かれます。LINE、WhatsApp、Feishu、Google Chat、Teams、WeCom、DingTalk は config.toml [channels] にあるデプロイ全体共通の認証情報を使い、社員専用の bot トークンがあるのは Telegram、Discord、Slack だけです(自分のトークンがない社員は reports_to をたどって上位を探し、最後にグローバルのトークンを使います)。
チャネル上のチャットコマンド(v1.68.0) — !STOP、!STOP ALL、!RESUME、/model <名前> には管理者が必要です。WhatsApp、Feishu、Teams、WeCom、Google Chat、DingTalk では以前、すべての送信者に is_admin = true を渡していたため、bot にメッセージを送れる人なら誰でも停止や再開ができました。現在これらのチャネルは、送信者 id または会話 id をチャネルの admin_users 設定(グローバル範囲。Google Chat と Teams も設定可能になりました)と完全一致で照合し、一覧がなければ誰も管理者になりません。WebChat では、有効なダッシュボードアカウントで役割が管理者のものだけが該当し、Web サイト用ウィジェットの訪問者は該当しません。
キルスイッチのしきい値(v1.68.0) — KILLSWITCH.toml [triggers] の4つのしきい値には以前は読み取り側がありませんでした。現在はファイルに書かれていて範囲内のキーだけが有効になり、セキュリティ設定ページではしきい値ごとにチェックボックスがあります(チェックを外すと null を送り、キーを削除します)。ファイルが変わると読み直します。cost_limit_usd は全社員の24時間の支出と比べ、達するとグローバルの failsafe レベルを制限状態にし、failsafe が自然に回復するか誰かが !RESUME を送るまで続きます。max_replies_per_minute は会話ごとに数え、超過分は黙って破棄します。max_consecutive_errors と error_rate_threshold(直近20回、最低10回)はその会話の failsafe レベルを1段階上げます。発動ごとに killswitch_trigger として監査されます。KILLSWITCH.toml の [audit] セクションは読まれなくなりました。
秘匿化のデータソース保護(v1.68.0) — 「プライバシー / 秘匿化」タブの「資料來源保護」スイッチが機能するようになりました。user_input はチャネルのメッセージを AI に渡す前に、system_prompt は組み立て済みのプロンプトを(既定では apply_to_system_prompt が付いたルールだけ)、cron_context は条件スクリプトのトリガーメッセージを秘匿化します。エラー時は秘匿化されていない内容を送らず、そのターンを止めます。sub_agent(同じタブ、または config.toml [redaction.sources] で設定します)は、委任先のエージェントの返信をゲートウェイが委任元エージェントの会話履歴に書き込む経路を対象にします(send_to_agent・spawn_agent・spawn_ephemeral の返信、チェーンを始めたエージェントへ中継される分を含む)。on にすると、返信は受け取る側のエージェントのルールで秘匿化されてから保存され、そのエージェントがユーザーに答えるときにトークンが元に戻ります。既定の inherit は返信をそのまま残します。サブエージェントは同じ [redaction] ルールの下ですでに動いているためです。秘匿化に失敗した場合は、返信の代わりに固定の通知文を保存します。対象外:ユーザーのチャネルに送られる返信(ユーザー自身が見るもの)、エージェントが check_responses で自分から取得する返信(ツール結果なので tool_results に従います)、Team-as-Agent の役割間の受け渡し、Agent Mail。purge_after_expire_days は保管庫の掃除に使われるようになりました。
権限フラグ(v1.68.0) — agent.toml [permissions] の can_create_agents、can_send_cross_agent、can_modify_own_skills、can_schedule_tasks が false と書かれていると、MCP のディスパッチゲートで対応するツールが拒否されます(permission_denied として監査)。アップグレード後の最初の起動で古いテンプレートの false を true に移行します。ダッシュボード設定の対応表を参照してください。
これらのガードがカバーしないもの
Section titled “これらのガードがカバーしないもの”これを明言すること自体が防御の一部です。
- フックが見るのは Claude Code 自身のツール呼び出しであり、MCP ツール呼び出しではありません。 MCP には独自のゲート(スコープ、付与、
denied_tools)があります。フックは組み込みのWrite/Edit/Read/Bash面に対する2つ目の鍵です。 agent-file-guardの Bash 側はヒューリスティックです。 コマンド文字列を読むだけなので、次のものはすり抜けます:変数($DUDUCLAW_HOMEと、ホームが既定の場所にある場合の~/.duduclaw/$HOME/.duduclawを除く)、コマンド置換、その他の計算で組み立てたパス;エンコードしたコマンド;いったんスクリプトとして書いてから実行するもの;インタプリタに渡す here-document;エイリアスと関数;環境変数を使って後から起動するシェルにファイルを読み込ませるもの;一覧にある読み取り専用コマンドの、想定していなかった書き込みオプション;展開先や保存先を明示しない展開・ダウンロード系コマンド(現在の作業ディレクトリに書き込みますが、この経路は現在のディレクトリを判定しないため、先にホームへ移動してから実行すると止められません);同じコマンド内でリンクを作ってからそれを通じて書き込むもの;ハードリンク;そしてチェックと実際の実行の間の時間差。- Bash 側は無害なコマンドを誤って止めることもあります。 検査対象の位置にあるパスの解決に失敗すると、ホームの外のパスでも拒否します。宙に浮いたシンボリックリンクも、ホームの外を指していても拒否します。
- オペレーター専用のメモリコマンドは、減速帯と承認で守られており、サンドボックスではありません。
memory forget-sourceとmemory migrate-namespaceの Bash ルールは、サブコマンドの前のグローバルオプション、コマンド置換で組み立てた実行ファイル名、パイプで渡したコマンドを防げず、AI セッション判定も変数の unset で回避できます。関門はダッシュボード承認です。出典単位の忘却には管理者の承認が必要を参照してください。 agent-file-guardはReadを対象にしません。 ホールドアウトセットと監査ログは社員から読めたままで、フックが止めるのは書き込みだけです。- これらのフックを実行するのは Claude ランタイムだけです。 Codex、Gemini、Antigravity などのランタイムはそれぞれのサンドボックスフラグに依存します。
- 社員自身のディレクトリ内の状態ファイルは保護されません。 保護されるのは
SOUL.md、CONTRACT.toml、識別ファイル(.mcp.json、.claude/settings.json)とagent.tomlだけです。共有のattachments/はどの社員も書き込めます。 data-file-guardはヒューリスティックです。Bashコマンドライン中のファイル名を照合するため、動的に組み立てたパスは通り抜けます。(Windows で無効になる問題は H10 の Rust サブコマンド化で解消済みです。)- 脅威レベルステートマシンは存在しません。
~/.duduclaw/threat_levelは、computer use オーケストレーターがポーリングする運用者制御のキルスイッチとして残っています(REDで停止、YELLOWで一時停止)が、ワークスペース内にこれを書くものはもうありません。ファイルが無い場合はGREEN扱いです。ファイルはあるのに読めない場合、または中身がGREEN/YELLOW/REDのどれでもない場合は、50 ミリ秒間隔で 2 回読み直してもそのままならREDとして扱います(fail closed)。先頭の UTF-8 BOM と前後の空白は無視します。 - (2026-09に削除。) 本節はかつてPTYセッションプールが匿名化リライトの対象外であることを注記していました。そのプールはもう存在せず、Claudeのspawnはすべて呼び出しごとのspawn——まさにリライトがフックする形です。
他システムとの連携
Section titled “他システムとの連携”- CONTRACT.toml はエージェントが絶対にしてはならないことを定義し、
duduclaw testがそれをレッドチームします。ガードはツール呼び出しレベルで強制します。 - 進化エンジン —
SOUL.mdはエージェントにとって読み取り専用なので、進化する成果物は playbook です。38-aee-playbook-evolution.md を参照。 - 匿名化とデータソース —
data-file-guardが補完するパイプラインは 55-data-sources.md を参照。 - 委譲分離 —
org_field_guardが守る述語は 37-delegation-isolation.md を参照。
失効モードを明示した4つのガードは、もう裏にコードのない3層の物語に勝ります。防御を取り除いたらドキュメントも一緒に取り除かねばなりません。存在しないシェルスクリプトを説明するページは、ページが無いより悪いのです——運用者がそこで探すのをやめてしまうからです。
チャネル確認の保存と照合
Section titled “チャネル確認の保存と照合”高リスク Computer Use の確認は元のアカウントと会話・スレッドへ送信されます。確認 <完全 UUID> または 取消 <完全 UUID> で返信します。質問は 回答 <完全 UUID> <回答> で回答し、ツール実行を許可しません。単独の yes/A/B は要求を選択しません。実行前に画面、ウィンドウ、ポリシーとキャンセル状態を再確認します。再起動後は古い画面の承認を失効させ、レシートのない実行は uncertain として Admin が照合します。対応経路と制限を参照してください。