AI News HubLIVE
サイト内リライト3 分で読了

ゼロから基本AIエージェントを構築:セキュリティ III

この記事では、AIエージェントのセキュリティを強化し続け、3層のポリシーゲート(パススコープ、シェル拒否リスト、SSRFガード)、破壊的なアクションに対する常時確認パターン、最小権限の原則(ツール許可リストによる)、および暴走ループを防ぐためのリソース/コスト制御を実装します。

ソースHacker News AI著者: ruxudev

前回のパートでは、ヒューマン・イン・ザ・ループによって残されたギャップを埋め始めました。暴走コマンドを封じ込めるためのDockerサンドボックス、モデルがツール出力を命令として信頼するのを防ぐプロンプトインジェクション対策、そして不正なツール呼び出しが実行されないようにするスキーマ検証です。このパートでは、作業を完了させます。パススコープ、シェル拒否リスト、SSRFガードでツールポリシーゲートを強化し、スタックしたループが永遠に実行されないようにリソースとコストの制限を実施し、コンテナ環境からシークレットを除去し、すべての決定が記録され、任意のセッションを途中で中断できるように監査ログとキルスイッチを追加します。

ツールポリシーの強化

「ヒューマン・イン・ザ・ループとセキュリティ」のパートでは、check_permissionは単一の関数でした。読み取りツールと計画ツールは常に許可され、書き込みツールはacceptEditsモードで作業ディレクトリ内でのみ許可され、それ以外はすべて確認が必要でした。これはモードベースの決定でした。

ここでは、モード決定の前に実行されるポリシーゲートを追加します。ゲートには3つの層があります。

パススコープ: すべてのパス引数は解決され(相対パス、シンボリックリンク、..トラバーサルを処理)、プロジェクトの作業ディレクトリから外れると拒否されます。これにより、エージェントが~/.ssh/id_rsa/etc/passwd、またはプロジェクトツリー外のものにアクセスするのを防ぎます。

シェルポリシー: すべてのrun_bashコマンドは、危険なパターン(フォーク爆弾、dd、mkfs、/etc/へのリダイレクトなど)の正規表現拒否リストと、エージェントが決して呼び出してはならないバイナリ(sudo、nc、curl、chmod、dockerなど)のトークンレベル拒否リストでスクリーニングされます。これにより、破壊的および外部送信コマンドをキャッチします。

SSRFガード: サーバーサイドリクエストフォージェリは、サーバーサイドプロセスを騙して本来アクセスすべきでない内部リソースにリクエストを送らせる攻撃です。エージェントのコンテキストでは、プロンプトインジェクションによりwebfetchが内部サービスやクラウドメタデータエンドポイントにアクセスする可能性があります。ガードはURLのホストを解決し、結果のIPをループバック、リンクローカル、マルチキャスト、およびRFC1918プライベート範囲に対してチェックし、ブロックします。

def check_tool_policy(
    tool_name: str,
    args: dict[str, Any],
    working_dir: Path,
) -> tuple[bool, str | None]:
    """すべてのポリシー層を実行。(allowed, reason) を返す。

    ``agent.check_permission`` からモードベースの決定の前に呼び出される。
    False はハードブロックであり、どのモードもオーバーライドできない。
    """
    # 層1: パススコープ。
    ok, reason = check_path_scope(tool_name, args, working_dir)
    if not ok:
        return False, reason

    # 層2: シェルポリシー。
    if tool_name == "run_bash":
        ok, reason = check_shell_policy(args.get("command", ""))
        if not ok:
            return False, reason

    # 層3: Web/SSRFポリシー。
    if tool_name == "webfetch":
        ok, reason = check_web_policy(args.get("url", ""))
        if not ok:
            return False, reason

    return True, None

パススコープ

古いチェックは書き込みツールでのみ実行されました。現在、チェックはパスを使用するすべてのツールに一般化されています:read_file、glob_files、grep、write_file、edit_file。各ツールのパス引数は解決され(相対パス、シンボリックリンク、..トラバーサルを処理)、作業ディレクトリから外れると拒否されます。

シェルポリシー

run_bashは最も危険なツールであるため、独自のスクリーニングがあります。各コマンドに対して2つのチェックが実行されます。最初に、危険なパターンの正規表現拒否リスト。次に、コマンドが字句解析され、各トークンがバイナリ拒否リストと照合されます。

Webポリシー(SSRFガード)

webfetchはサーバーサイドリクエストフォージェリに対してスクリーニングされます。クラウドメタデータエンドポイント、ローカルホスト、およびプライベートIP範囲はすべてブロックされます。

常時確認パターン

ハードポリシーゲートに加えて、一部のパターンは十分に危険であるため、権限モードに関係なく明示的な確認が必要です。これらは不可逆/外部送信クラスです。check_permission関数は現在、3層構造を持っています:ハードポリシーゲート、常時確認、モード決定。常時確認は(bool, reason)を返すため、この層は独自の特定の理由を持ちます。

最小権限の原則

もう1つの制御がここに適合します:ツール名のカンマ区切りの許可リストを受け入れる--tools CLIフラグです。ハーネスは、インプロセスレジストリとLLMに公開されるスキーマの両方をフィルタリングするため、モデルは呼び出せないツールをまったく見ません。例:python agent.py --tools read_file,glob_files,grep,run_bash エージェントがファイルを書き込む必要がないことがわかっている場合、許可されるツールを制限することで、危険の爆発半径を大幅に減らすことができます。

リソースとコストの制御

ハーネスにリソースとコストの制御を追加し、エージェントが見ていない間に暴走するのを防ぎます。

ハードイテレーション上限

IterationCapsは2つのカウンターを追跡します:turns(単一のユーザーターン内のLLM応答ごとにインクリメント、デフォルト40、--max-turnsでオーバーライド可能)とtool_calls(ツール呼び出しごとにインクリメント、デフォルト200、--max-tool-callsでオーバーライド可能)。いずれかのカウンターが上限を超えると、ループは終了し、ユーザーに続行するかどうかを尋ねるメッセージが返されます。この機能を常に有効にしたいユーザーは、--iterations boundlessフラグを使用できます。