AI-Scientistプロジェクトのセキュリティメカニズム
AI-Scientistは自動科学発見ツールとして設計されており、そのセキュリティ設計は実験信頼性とデータ安全性に直結します。本文ではコードサンドボックスとデータプライバシープロテクションの実装を解説し、開発者向けに安全なAI実験環境構築の指針を提供します。
セキュリティアーキテクチャの概要
AI-Scientistのセキュリティ設計は以下の要素から構成されています:
- 多層的なアクセス制御
- コンテナベースの実行隔離
- リソース制限とプロセス分離
- 実験ライフサイクル管理
- データ処理におけるプライバシーポリシー
コンテナ化された実行環境
プロジェクトのexperimentalディレクトリにあるDockerfileは最小権限原則に基づいて構成されています:
FROM python:3.11-bullseye
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
wget=1.21-1+deb11u1 \
git=1:2.30.2-1+deb11u2 \
build-essential=12.9 \
libssl-dev=1.1.1w-0+deb11u1 \
zlib1g-dev=1:1.2.11.dfsg-2+deb11u2 \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -m aiuser
USER aiuser
WORKDIR /app
RUN chown -R aiuser:aiuser /app
この構成により、以下のセキュリティメリットが実現されています:
- 固定バージョン依存によるサプライチェーン攻撃防止
- 不要なパッケージインストール回避
- APTキャッシュ削除によるイメージサイズ最適化
- 非rootユーザーでの実行によるリスク軽減
実験コードの実行制御
perform_experiments.pyにおいて実行制御が行われています:
def run_experiment(folder_name, run_num, timeout=7200):
cwd = osp.abspath(folder_name)
shutil.copy(osp.join(folder_name, "experiment.py"),
osp.join(folder_name, f"run_{run_num}.py"))
command = ["python", "experiment.py", f"--out_dir=run_{run_num}"]
try:
result = subprocess.run(command, cwd=cwd,
stderr=subprocess.PIPE, text=True, timeout=timeout)
if result.stderr:
print(result.stderr, file=sys.stderr)
if result.returncode != 0:
print(f"Run {run_num} failed with return code {result.returncode}")
if osp.exists(osp.join(cwd, f"run_{run_num}")):
shutil.rmtree(osp.join(cwd, f"run_{run_num}"))
stderr_output = result.stderr
if len(stderr_output) > MAX_STDERR_OUTPUT:
stderr_output = "..." + stderr_output[-MAX_STDERR_OUTPUT:]
next_prompt = f"Run failed with the following error {stderr_output}"
else:
with open(osp.join(cwd, f"run_{run_num}", "final_info.json"), "r") as f:
results = json.load(f)
results = {k: v["means"] for k, v in results.items()}
next_prompt = f"Run {run_num} completed. Results: {results}"
return result.returncode, next_prompt
except TimeoutExpired:
print(f"Run {run_num} timed out after {timeout} seconds")
if osp.exists(osp.join(cwd, f"run_{run_num}")):
shutil.rmtree(osp.join(cwd, f"run_{run_num}"))
next_prompt = f"Run timed out after {timeout} seconds"
return 1, next_prompt
この実行制御では以下のような安全機能が含まれています:
- 出力ディレクトリ指定制限
- タイムアウト制御
- エラーログのサイズ制限
- 失敗時の自動クリーンアップ
- 結果JSON検証
データプライバシープロテクション
data/enwik8/prepare.pyにおけるデータ処理:
n = len(data)
num_test_chars = 5000000
train_data = data[: -2 * num_test_chars]
val_data = data[-2 * num_test_chars: -num_test_chars]
test_data = data[-num_test_chars:]
train_ids = encode(train_data)
val_ids = encode(val_data)
test_ids = encode(test_data)
print(f"train has {len(train_ids):,} tokens")
print(f"val has {len(val_ids):,} tokens")
print(f"test has {len(test_ids):,} tokens")
train_ids = np.array(train_ids, dtype=np.uint16)
val_ids = np.array(val_ids, dtype=np.uint16)
test_ids = np.array(test_ids, dtype=np.uint16)
train_ids.tofile(os.path.join(os.path.dirname(__file__), 'train.bin'))
val_ids.tofile(os.path.join(os.path.dirname(__file__), 'val.bin'))
test_ids.tofile(os.path.join(os.path.dirname(__file__), 'test.bin'))
この処理では以下のような保護措置が採用されています:
- トレーニング/テストデータ分割
- 文字列エンコードによる明文防止
- バイナリ形式保存
- データ型最適化
- データ量記録
改善点と対策
| リスクタイプ | 現状 | 改善案 | 実装難易度 |
|---|---|---|---|
| データ明文通信 | 未暗号化 | SSL/TLS導入 | 中 |
| データ脱敏不足 | 未実施 | PII検出ツール導入 | 中 |
| アクセスログ欠如 | 未記録 | アクセスログ記録 | 低 |
| モデル重み保護 | 明文保存 | 暗号化保存 | 高 |
| データ廃棄戦略 | 未定義 | ライフサイクル管理 | 低 |
推奨改善点:
- データ暗号化通信の実装
- データ脱敏ツールの統合
- アクセスログシステムの構築
- モデルファイル暗号化
- データライフサイクル管理
セキュリティベストプラクティス
Dockerセキュリティ強化:
RUN useradd -m -d /home/aiuser -s /bin/bash aiuser
RUN chown -R aiuser:aiuser /app
USER aiuser
docker run --read-only --tmpfs /tmp --tmpfs /var/run ...
docker run --cap-drop=ALL --security-opt=no-new-privileges ...
実行制御の強化:
command = ["python", "experiment.py", f"--out_dir=run_{run_num}"]
result = subprocess.run(
command,
cwd=cwd,
stderr=subprocess.PIPE,
text=True,
timeout=timeout,
preexec_fn=lambda: os.setuid(os.getuid()),
)
データ保護の強化:
from cryptography.fernet import Fernet
key = Fernet.generate_key()
cipher_suite = Fernet(key)
encrypted_data = cipher_suite.encrypt(train_ids.tobytes())
with open('train.enc', 'wb') as f:
f.write(encrypted_data)