0Pricing
AI Agents · レッスン

コード実行のセキュリティポリシー

機能制限、ネットワーク分離、ファイルシステムの制限、タイムアウトを設定します。

「コード実行のセキュリティポリシー」はCoddyKit上の無料AI Agentsレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Agents学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Agentsコースには全4レッスンが含まれています。

ポリシーだけでは不十分な理由

LLMに「ネットワークにアクセスしないでください」と指示しても、それはセキュリティ制御ではなく、単なるヒントです。LLMが指示を無視したり、脱獄されたりする可能性があり、生成されたコードが間接的にルールに違反することもあります。

実際のセキュリティポリシーは、プロンプトだけでなく実行レイヤーで強制する必要があります。

ケーパビリティ制限の概要

ケーパビリティ制限は、サンドボックス化されたプロセスがOSレベルで実行できる操作を制限します。Linux capabilitiesを使用すると、root権限を細かな権限に分割し、それぞれを個別に削除できます。

必要最小限のケーパビリティ以外をすべて削除することを、最小権限の原則と呼びます。

import docker
client = docker.from_env()

# Drop all Linux capabilities
output = client.containers.run(
    'python:3.12-slim',
    'python -c "print(\"safe run\")"',
    cap_drop=['ALL'],
    network_disabled=True,
    mem_limit='256m',
    auto_remove=True
)
print(output.decode())

/tmp以外へのディスク書き込みのブロック

ルートファイルシステムを読み取り専用にし、書き込み可能な状態でマウントするのは/tmpだけにします。これにより、コードがシステムファイルを変更したり、指定された一時領域の外部にデータを永続化したりすることを防止できます。

import tempfile, docker
client = docker.from_env()

with tempfile.TemporaryDirectory() as tmpdir:
    output = client.containers.run(
        'python:3.12-slim',
        'python -c "open(\"/tmp/ok.txt\",\"w\").write(\"ok\"); print(open(\"/tmp/ok.txt\").read())"',
        read_only=True,
        tmpfs={'/tmp': 'size=64m,mode=1777'},
        network_disabled=True,
        mem_limit='128m',
        auto_remove=True
    )
    print(output.decode())  # 'ok' — /tmp write allowed
    # Writing anywhere else raises ReadOnlyFileSystem

seccompプロファイル:システムコールのフィルタリング

seccomp(secure computing mode)を使用すると、許可するシステムコールの許可リストを指定できます。リストにないシステムコールが呼び出されると、プロセスはSIGSYSによって強制終了されます。

Dockerにはデフォルトのseccompプロファイルが付属しています。より厳しく制限するには、カスタムJSONプロファイルを指定できます。

import json, docker
client = docker.from_env()

# Minimal seccomp profile: only allow read, write, open, close, exit
seccomp_profile = json.dumps({
    'defaultAction': 'SCMP_ACT_ERRNO',
    'architectures': ['SCMP_ARCH_X86_64'],
    'syscalls': [
        {'names': ['read', 'write', 'open', 'openat', 'close',
                   'fstat', 'mmap', 'mprotect', 'munmap', 'brk',
                   'exit', 'exit_group', 'rt_sigaction',
                   'rt_sigprocmask', 'futex'],
         'action': 'SCMP_ACT_ALLOW'}
    ]
})

output = client.containers.run(
    'python:3.12-slim',
    'python -c "print(1+1)"',
    security_opt=[f'seccomp={seccomp_profile}'],
    auto_remove=True
)
print(output.decode())

リソース制限:CPU、メモリ、時間

すべてのサンドボックスで適用すべき3つのリソース制限は次のとおりです。

  • メモリ:mem_limit — OOM攻撃を防止します
  • CPU:nano_cpusまたはcpu_quota — CPUの枯渇を防止します
  • 時間:実行タイムアウト — 無限ループを防止します
output = client.containers.run(
    'python:3.12-slim',
    'python -c "import time; time.sleep(100)"',
    mem_limit='128m',
    nano_cpus=500_000_000,   # 0.5 CPU
    network_disabled=True,
    auto_remove=True,
    timeout=10               # kill after 10 seconds
)
# Raises ReadTimeout after 10 seconds

Pythonインポートの許可リスト

コードを実行する前に、ASTをスキャンしてimport文を検出します。許可リストにないimportは拒否します。これは多層防御の一環であり、OSレベルの制御に代わるものではありませんが、明らかなポリシー違反を早期に検出するのに役立ちます。

import ast

ALLOWED_IMPORTS = {'math', 'statistics', 'json', 'csv', 'datetime', 'collections', 're'}

def check_imports(code: str) -> list[str]:
    tree = ast.parse(code)
    blocked = []
    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            for alias in node.names:
                if alias.name.split('.')[0] not in ALLOWED_IMPORTS:
                    blocked.append(alias.name)
        elif isinstance(node, ast.ImportFrom):
            if node.module and node.module.split('.')[0] not in ALLOWED_IMPORTS:
                blocked.append(node.module)
    return blocked

print(check_imports('import socket; print(1)'))   # ['socket']
print(check_imports('import math; print(math.pi)'))  # []

危険なコードパターンの検出

importだけでなく、importの制限を回避するパターンも確認します。__import__、exec()、eval()、compile()、/tmpの外部のパスを指定するopen()などが該当します。

import ast

DANGEROUS_CALLS = {'exec', 'eval', 'compile', '__import__', 'breakpoint'}

def check_dangerous_calls(code: str) -> list[str]:
    tree = ast.parse(code)
    found = []
    for node in ast.walk(tree):
        if isinstance(node, ast.Call):
            if isinstance(node.func, ast.Name):
                if node.func.id in DANGEROUS_CALLS:
                    found.append(node.func.id)
            elif isinstance(node.func, ast.Attribute):
                if node.func.attr in DANGEROUS_CALLS:
                    found.append(node.func.attr)
    return found

print(check_dangerous_calls('exec("import os")'))  # ['exec']

出力サイズの制限

暴走したprintループによって、ギガバイト単位の出力が生成される可能性があります。stdout/stderrを適切なサイズ(例:1MB)に制限し、超過した場合は切り詰めるかコンテナを停止してください。

MAX_OUTPUT_BYTES = 1 * 1024 * 1024  # 1 MB

def run_with_output_limit(client, code: str) -> dict:
    container = client.containers.run(
        'python:3.12-slim',
        f'python -c "{code}"',
        mem_limit='256m',
        network_disabled=True,
        detach=True
    )
    try:
        container.wait(timeout=30)
        logs = container.logs(stdout=True, stderr=True)
        if len(logs) > MAX_OUTPUT_BYTES:
            return {'success': False, 'error': 'Output too large'}
        return {'success': True, 'output': logs.decode(errors='replace')}
    finally:
        container.remove(force=True)

if __name__ == '__main__':
    class FakeContainer:
        def wait(self, timeout=30): pass
        def logs(self, stdout=True, stderr=True): return b'Analysis complete: 42 rows processed.'
        def remove(self, force=True): pass
    class FakeContainers:
        def run(self, *args, **kwargs): return FakeContainer()
    class FakeClient:
        containers = FakeContainers()

    result = run_with_output_limit(FakeClient(), 'print(42)')
    print('Success:', result['success'])
    print('Output :', result['output'])

No-New-Privilegesフラグ

no-new-privilegesセキュリティオプションは、setuidバイナリやsudoを介してプロセスが追加の権限を取得することを防ぎます。コードがシェルに移行しても、権限を昇格できません。

output = client.containers.run(
    'python:3.12-slim',
    'python -c "import os; print(os.getuid())"',
    user='nobody',                              # run as non-root
    security_opt=['no-new-privileges:true'],    # no privilege escalation
    cap_drop=['ALL'],                           # no capabilities
    network_disabled=True,
    mem_limit='128m',
    auto_remove=True
)
print(output.decode())  # numeric UID of 'nobody'

すべてのポリシーの組み合わせ

本番運用に耐えるサンドボックスでは、すべての層を組み合わせます。OSのcapabilityの削除、seccompプロファイル、読み取り専用ファイルシステム、no-new-privileges、ネットワークの無効化、リソース制限、タイムアウトを適用します。多層防御により、ある層が回避されても別の層で阻止できます。

import tempfile, json, docker
client = docker.from_env()

def maximum_security_run(code: str) -> dict:
    with tempfile.TemporaryDirectory() as tmpdir:
        try:
            out = client.containers.run(
                'python:3.12-slim',
                f'python -c "{code}"',
                user='nobody',
                security_opt=['no-new-privileges:true'],
                cap_drop=['ALL'],
                network_disabled=True,
                read_only=True,
                tmpfs={'/tmp': 'size=32m,mode=1777'},
                mem_limit='128m',
                nano_cpus=500_000_000,
                auto_remove=True,
                timeout=15
            )
            return {'success': True, 'output': out.decode()}
        except Exception as e:
            return {'success': False, 'error': str(e)}

ポリシー違反のログ記録

ポリシー違反(ブロックされたimport、危険な呼び出し、タイムアウト)を検出したら、エージェントセッションID、コードスニペットのハッシュ、違反の種類とともにイベントを記録します。これによりセキュリティダッシュボードに情報が送られ、時間の経過に合わせたポリシーの調整に役立ちます。

import hashlib, logging, time

logging.basicConfig(level=logging.INFO)
security_logger = logging.getLogger('sandbox.security')

def policy_check_and_run(code: str, session_id: str) -> dict:
    violations = check_imports(code) + check_dangerous_calls(code)
    code_hash = hashlib.sha256(code.encode()).hexdigest()[:16]

    if violations:
        security_logger.warning(
            'POLICY_VIOLATION session=%s hash=%s violations=%s',
            session_id, code_hash, violations
        )
        return {'success': False, 'error': f'Blocked: {violations}'}

    result = maximum_security_run(code)
    security_logger.info(
        'EXEC session=%s hash=%s success=%s',
        session_id, code_hash, result['success']
    )
    return result

seccompプロファイルの「defaultAction: SCMP_ACT_ERRNO」設定は何をしますか

seccompプロファイルは、プロセスが実行できるシステムコールを制御します。defaultActionフィールドは、プロファイルに明示的に記載されていないシステムコールに対して何が起きるかを決定します。

セキュリティポリシーのまとめ

効果的なコード実行のセキュリティには、複数の層が必要です。Linux capabilitiesの削除、seccompによるシステムコールのフィルタリング、tmpfsの作業領域を備えた読み取り専用ファイルシステム、no-new-privileges、ネットワークの無効化、メモリ・CPU・時間の制限、ASTレベルのimportチェックを組み合わせます。

単独で十分な層はありません。多層防御では、各層が他の層の回避を補います。

よくある質問

「コード実行のセキュリティポリシー」レッスンは無料ですか?

はい。「コード実行のセキュリティポリシー」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Agentsコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Agentsコースには全4レッスンが含まれています。

「コード実行のセキュリティポリシー」で何を学びますか?

機能制限、ネットワーク分離、ファイルシステムの制限、タイムアウトを設定します。 ブラウザで直接実行するハンズオンコードでAI Agentsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

AI Agentsを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAI Agentsは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「コード実行のセキュリティポリシー」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAI Agentsレッスンでコードを書いて実行できますか?

はい。すべてのAI Agentsレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Docker ベースのエージェントサンドボックス
  2. 高セキュリティなコードエージェントのための VM 分離
  3. E2B とクラウドサンドボックスサービス
  4. コード実行のセキュリティポリシー
← AI Agentsに戻る