- 公開日
Coding Agentの操作を制限するときは、実行してよいプログラムだけでなく、引数、作業場所、変更可能なファイル、通信先、渡す認証情報を一組で決めます。「この画面だけ直して」と依頼しても、テストが別のスクリプトを実行したり、共通設定の変更が他画面へ影響したりするためです。
指示文には作業の目的と境界を伝え、実際の制限はファイル操作の受付、プロセス起動、隔離環境の設定で強制します。終了後は、テストの成否と変更差分を別々に確認します。テストが通ったことだけでは、許可した範囲以外が変わっていないとは分かりません。
一画面の修正を、具体的な許可範囲へ変える
架空のWebアプリで、設定画面の保存ボタンを「保存」から「変更を保存」へ直す依頼を考えます。対象はLinuxの隔離環境で動かす作業用コピーで、依存パッケージは事前に管理者が準備済みとします。本番環境、実ユーザーデータ、秘密情報は配置しません。以下のコードと確認例は未実行です。
この仕事では、画面の実装ファイルだけを変更可能にし、既存テストと共通ボタン部品は必要な範囲で閲覧できるものとします。テストは、管理者が内容を確認した専用の起動プログラムから、対象画面の試験だけを実行します。依存関係の追加、共有設定の変更、外部への送信、コミットや公開は今回の範囲に含めません。
| 対象 | この例の条件 | 超えたときの扱い |
|---|---|---|
| 変更ファイル | src/pages/Settings.tsxのみ | パッチを適用せず相談する |
| 閲覧資料 | 対象画面、関連テスト、共通ボタンの必要部分 | 追加資料の必要性を確認する |
| 実行ジョブ | settings-testのみ | 任意のコマンド文字列を受け付けない |
| 実行場所 | 専用の作業コピー | 別のリポジトリやホーム領域へ移らない |
| ネットワーク | 外部通信を許可しない | 依存の取得が必要なら作業を止める |
| 秘密情報 | トークン、SSH鍵、本番設定を渡さない | 必要性と別の確認方法を相談する |
この表は組織全体の禁止規程ではなく、一回の修正に対する許可条件です。別の仕事でネットワークや複数ファイルの変更が必要なら、その仕事の目的と影響に合わせて改めて決めます。必要な機能を何でも禁止するのではなく、今回の依頼から説明できる操作だけを用意します。
コマンド名が同じでも、実行内容は変わる
「npmを許可」と書くだけでは、テスト、依存追加、任意スクリプトなどの区別がつきません。テストコマンドも、リポジトリ内の設定やプラグインを読み込むので、名前だけで副作用がないと判断できません。実行するプログラムの実体、引数、作業ディレクトリ、参照する設定を確認します。
特に、AIが変更できるファイルの中にテストの起動定義があると、同じコマンドでも中身を変えられます。今回の例では、テストの起動プログラムと設定を変更対象から外し、隔離環境に固定して配置します。固定したプログラムが対象コードを読み込むこと自体は避けられないため、そのコードが動く範囲も環境側で制限します。
シェルへ文字列を丸ごと渡す構成では、引数の意図とシェルの解釈を分ける必要があります。Pythonのsubprocessの文書は、引数をシーケンス(リスト)で渡す方法を一般に推奨し、shell=Trueでシェルを明示的に呼ぶ場合は、シェルインジェクションを避けるための引用符処理がアプリケーション側の責任になると注意しています。shell=Falseにすれば何でも安全になるのではなく、シェルの解釈を介さず起動するという性質を利用するものです。subprocess
許可するプログラム自体が別のプロセスを起動したり、ファイルへ書いたり、通信したりできる点は残ります。コマンド検査を通過したことと、その後の全動作が許可範囲に収まることは別なので、サンドボックスによる実行領域の制限を組み合わせます。
任意コマンドを、固定ジョブの選択へ置き換える
次はPython 3.10以降を想定して書いた、受付部分だけの説明用コードです。AIから実行ファイルや引数を自由に受け取る代わりに、既知のジョブIDだけを受け付け、管理側で固定した仕様へ対応させます。この関数はコマンドを実行しません。呼出し側が隔離環境を構成し、その環境内だけで仕様を実行する前提です。
from dataclasses import dataclass
@dataclass(frozen=True)
class JobSpec:
argv: tuple[str, ...]
cwd: str
sandbox_profile: str
SETTINGS_TEST = JobSpec(
argv=("/opt/trusted-tools/check-settings", "--no-update"),
cwd="/work/repo",
sandbox_profile="settings-offline-v1",
)
def select_job(request: object) -> JobSpec:
if not isinstance(request, dict):
raise ValueError("invalid request")
if set(request) != {"job"}:
raise ValueError("unexpected fields")
if request["job"] != "settings-test":
raise PermissionError("job not allowed")
return SETTINGS_TEST
/opt/trusted-tools/check-settingsとsettings-offline-v1は架空の管理用名称です。実在の標準コマンドや、定義済みの製品機能ではありません。このコードだけではファイル、通信、子プロセスの制限は実現しません。名前にofflineと付けたことを、ネットワークが遮断された証拠にもできません。
修正の要点は、入力から自由なargvやcwdを作らず、管理側に置いたジョブ仕様を選ぶことです。job以外のフィールドを拒否するため、別の作業場所や追加引数を要求へ付け足しても、その値を採用しない設計になっています。受け付ける文字列を増やすときは、対応する実行内容と環境を同時にレビューします。
実際のサービスには、呼出し主体の認証、依頼の有効性、要求サイズの制限、監査記録、実行時間と資源の上限、状態管理、例外処理が別に必要です。この短い関数を本番の権限制御全体として使わず、受付の型と固定ジョブ選択という一部分として扱います。
ファイルの変更は、コマンドの許可と別に強制する
AIへ任意のシェルと書き込み可能な作業コピーを渡してから、最後に変更ファイルを調べる方法は、事後検出です。範囲外の変更自体を止めたいなら、パッチの適用先を検査する受付を用意し、許可したファイルだけへ変更を反映する方法などを採ります。
この例では、AIが作る変更案をパッチとして受け取り、信頼する側で対象パスと元ファイルの状態を確認してから適用するとします。新規作成、削除、名前変更、ファイル種別や実行権限の変更も、単なる内容変更と区別して検査します。対象名が同じでも、通常ファイルから別の場所を指すリンクへ変わるなら、今回の修正条件には合いません。
パスは文字列の先頭一致だけで判断しません。相対表記、リンク、実際の保存先を確認し、許可対象の外へ解決される場合は拒否します。検査後に対象が入れ替わる問題もあるため、適用時の元ファイルが確認した版と一致するかを確かめます。これらを簡易な文字列検査で解決したと扱わず、利用するパッチ適用機構とOSの保護機能で検証します。
また、テストによるキャッシュや一時ファイルの書き込みと、納品するソース変更を分けます。テスト用の出力は専用の一時領域へ置き、成果物として許可したファイルへ紛れ込ませません。必要な一時書き込みまで禁止してテストが動かないなら、ソースの変更権限を広げるのではなく、出力先を分離します。
通信と秘密情報を、実行環境から外す
プロンプトに「外部へ送らない」と書いても、実行プロセスが通信でき、秘密情報も読めるなら、技術的な経路は残ります。この例では、テスト用の環境へ実際のトークンを渡さず、不要な環境変数、認証用ファイル、ホストのホームディレクトリを持ち込まないようにします。
Dockerのnoneネットワークは、コンテナのネットワークを隔離する方式として説明され、ループバックだけが残ります。外部通信を不要とする作業で利用を検討できますが、ほかの共有経路まで自動的に消えるわけではありません。None network driver
例えば、ホストの制御ソケットや、権限の広い共有ディレクトリを渡せば、コンテナ内の制限とは別の経路ができます。Dockerのセキュリティ資料も、デーモンを操作できるのは信頼できる利用者に限るべきこと、ホストのルートディレクトリ(/)をコンテナへ共有すれば、コンテナからホストのファイルシステムを制限なく変更できることを説明し、カーネルの機能やコンテナ設定の抜け穴を、隔離の安全性を左右する要素として挙げています。コンテナを使ったという事実だけで、ホストから安全に隔離されたと判断しません。Docker Engine security
作業用イメージや依存パッケージの準備は、今回のコード実行と分けて管理します。テスト中に依存が足りないと分かった場合は、任意サイトから自動で取得させず、必要なパッケージと版、取得元を確認する工程へ戻します。インストール時に実行される処理もあるため、単なるファイル取得と同じ扱いにはしません。
許可ファイルの変更でも、共有部品への影響を見る
今回の修正差分は、説明用には次のような小さな変更です。
- <Button>保存</Button>
+ <Button>変更を保存</Button>
しかし、見た目の文字列が対象ファイルにあるとは限りません。共通の辞書やボタン部品から読み込んでいるなら、その依存先を調べます。依存先を読めることと、変更してよいことは別です。共通辞書の値を変える必要があれば、勝手に対象ファイルを増やさず、どの画面へ影響するかを示して相談します。
逆に、許可ファイルだけで収めるために、不自然な例外処理を大量に追加するのも避けます。今回の境界では適切な修正ができないなら、「共有辞書の変更が必要」「対象画面だけの上書きで対応可能だが重複が増える」と選択肢を説明し、担当者に範囲の見直しを求めます。制限を守ることと、保守しやすい実装にすることを両方確認します。
共有部品を閲覧する際も、関係する使い方と依存を追える範囲に絞ります。巨大リポジトリ全体の探索や他サービスの網羅的な回帰試験へ、自動的に作業を拡大しません。今回の変更に必要な確認が終わったかを基準にし、範囲の不足が分かったときだけ追加を判断します。
正常・拒否・未確認を、証拠で分ける
確認は、外部通信が届かない試験環境と架空ファイルで行う前提です。次の表は期待結果であり、この記事ではコードやテストを実行していません。
| 確認する入力・操作 | 期待する結果 | 見る証拠と失敗時の対応 |
|---|---|---|
| jobがsettings-testだけの要求 | 固定ジョブを選ぶ | 選ばれた引数・場所・プロファイルを照合する |
| jobに未知の値を入れる | 起動前に拒否 | プロセスが開始していないことを確認する |
| 要求へ任意のcwdや追加引数を付ける | 入力を拒否 | 未知フィールドが実行へ渡らないか確認する |
| Settings.tsxの文言だけを変更 | 許可対象へ差分を適用 | 元の版と変更後の差分を照合する |
| 共有設定の変更を含むパッチ | 適用を保留 | 許可外ファイルが不変かを確認し相談する |
| 試験用の外向き通信を試みる | 環境側で遮断 | 試験用に用意した受信先に届いていないことと、実行ログの接続失敗を照合する |
| テストが時間内に終わらない | 上限で停止し未完了と記録 | 子プロセスを含めた停止状態と残存差分を調べる |
遮断の表示が出たことと、実際に外部へ届かなかったことは分けます。ログを取得できず結果が分からないなら、合格にせず未確認として残します。権限外要求を止めた後に、正常なテストまで動かなくなった場合も、制限の設定を見直す対象です。
テスト失敗は、コードの不具合、依存不足、実行環境の制限など原因を分けて調べます。失敗したからといって、自動で通信や書き込み範囲を広げません。許可内で確かめられることを進め、範囲を変えないと分からない部分は、その理由と必要な操作を担当者へ示します。
承認を求める場合は、対象ジョブ、変更案の版、実行環境、追加する権限を一緒に提示します。承認後にパッチや起動設定が変われば、同じ承認を流用できるかを再評価します。一般的な「続けて」という返答を、通信や秘密情報へのアクセスまで広げる根拠にしない設計が必要です。
最後に実行結果と差分を別々に渡す
作業の終了時には、変更したファイル、実際に実行したジョブ、正常終了か失敗か、実行できなかった確認をまとめます。終了コードがゼロでも、対象テストが本当に実行されたか、テストが省略されていないかを出力や実行記録で確認します。期待結果だけを「確認済み」と報告しません。
差分の確認では、追跡済みファイルだけでなく、新規ファイル、削除、名前変更、設定変更も対象にします。テストが生成したファイルと利用者が元から持っていた変更を区別するため、着手時の状態を記録しておきます。意図しない変更を見つけても、利用者の変更をまとめて元に戻さないようにします。
本番への反映や公開は、画面の修正と別の操作です。成果物をレビューへ渡し、未確認事項が残るなら、必要な環境や確認担当を明らかにします。許可コマンド、ファイル境界、隔離環境を用意するのは、変更の影響を限定しながら確認を進めるためであり、本番利用の承認を代わりに与えるものではありません。
基本から確認する:この記事の前提になる考え方は、「AIエージェントの安全設計(権限・外部資料・承認・検証の基本)」にまとめています。

