
信頼できない Python、JavaScript、Bash を、5秒・128 MB・0.5コア・PID・出力制限付きの堅牢化された jail 内で実行します。
信頼できないコードをカーネルで強制執行する実行環境 — すべてのペイロードを cgroups v2、namespaces、seccomp-BPF、読み取り専用 rootfs で隔離し、すべての実行を PostgreSQL で監査します。
python · javascript · bash → 5秒、128 MB、0.5コア、32 PID、512 KB出力の隔離環境
信頼していないコードのためのセキュリティ強化された実行サービスです。呼び出し元は小さな REST API 経由でソースコードを送信し、デーモンは各ペイロードを一時的なカーネル隔離環境に起動します。そこではアプリケーションロジックではなく Linux カーネル自体が封じ込めを保証します:
pivot_root)、lo ダウン + seccomp が socket を強制終了)、PID · MOUNT · NET · IPC · UTS · USER)、厳格な seccomp-BPF 許可リスト、cgroups v2 リソース隔離、16 MB の tmpfs スクラッチ領域を備えた読み取り専用 pivot_root ファイルシステム。5 s ウォールクロックタイムアウト、128 MB のメモリ上限(スワップ無効、即時 OOM キル)、0.5 CPU コア、pids.max = 32 による fork 爆弾耐性、stdout/stderr の 512 KB 出力上限。execution_runs)に非同期で書き込まれます。flowchart LR
CLI["👤 API Client<br/><i>tenant / CI job</i>"]
subgraph CP["🔐 Trusted Control Plane — Go daemon"]
API["Ingestion Daemon<br/><i>net/http :8080</i>"]
POOL["Warm Runner Pool<br/><i>16 slots · 3 s cap</i>"]
RUN["Process Runner<br/><i>watchdog + pipes</i>"]
ISO["Kernel Isolation<br/><i>cgroups v2 · namespaces · seccomp-BPF</i>"]
end
subgraph JAIL["⛓️ Ephemeral Kernel Jail — untrusted"]
INIT["sandbox_init<br/><i>exec trampoline</i>"]
GUEST["💀 Untrusted Code<br/><i>python · node · bash</i>"]
end
PG[("🗄️ PostgreSQL 16<br/><i>execution_runs</i>")]
RD[("📮 Redis 7 Queue<br/><i>sandbox:jobs</i>")]
CLI -->|"POST /api/v1/execute"| API
API -->|"validate + acquire"| POOL
POOL -->|"spawn + limits"| RUN
RUN -.->|"apply jail"| ISO
ISO ==>|"seccomp · pivot_root"| GUEST
RUN -->|"fork / exec"| INIT
INIT -->|"syscall.Exec"| GUEST
RUN -.->|"stdout / stderr ≤ 512 KB"| POOL
API -.->|"async audit INSERT"| PG
RD -.->|"BRPop drain · worker module"| POOL
style JAIL fill:#2a1215,stroke:#f87171,stroke-width:2px
style CP fill:#0d1b2a,stroke:#38bdf8,stroke-width:1px
| コンポーネント | 責務 | ソース |
|---|---|---|
| Ingestion Daemon | REST サーフェス: POST /api/v1/execute + /health、リクエスト検証、非同期監査イベントの発行 | cmd/server/main.go |
| Warm Runner Pool | セマフォで制限された実行スロット(16)と 3 秒の取得タイムアウト → 503 相当の枯渇バックプレッシャー | internal/runner/pool.go |
| Process Runner | 言語コマンドの構築、cgroup のアタッチ、切り詰めライターを介したパイプのストリーミング、5 秒のウォッチドッグの適用、cgroup メトリクスの収集、結果の分類 | internal/runner/process.go |
| Kernel Isolation | cgroups v2 リミッター(memory.max、cpu.max、pids.max)、名前空間フラグ(CLONE_NEWPID/NET/NS/IPC/UTS/USER)、seccomp-BPF フィルターコンパイラ | internal/isolation/ |
| sandbox_init | 子プロセスのトランポリン: 読み取り専用の pivot_root + tmpfs の /dev//tmp、ネットワークの切断(lo ダウン)、seccomp の適用、続いてターゲットの syscall.Exec | cmd/sandbox_init/main.go |
| Untrusted Code | ゲストのペイロード。ホストデバイスは除去され、/proc は隠蔽され(hidepid=2)、許可リストにないすべてのシステムコールは強制終了される | runtimes/ rootfs イメージ |
| PostgreSQL 16 | セキュリティ違反を含む、すべての実行の永続的な監査ログ | migrations/001_init_sandbox_schema.sql |
| Redis 7 | sandbox:jobs キュー + プールへ排出する BRPop ワーカー (モジュールは実装済みだが、まだデーモンに接続されていない — 既知の制限を参照) | internal/queue/redis_consumer.go |
| 境界 | 何を分離するか | 適用方法 |
|---|---|---|
| クライアント ↔ デーモン | コントロールプレーンからの未認証のペイロード送信 | スキーマ検証(tenant_id、language、code) |
| コントロールプレーン ↔ ジェイル | 信頼された Go コードと敵対的ペイロード | プライベート名前空間への fork、pivot_root、exec 時の seccomp 継承 |
| ジェイル ↔ カーネル制限 | ゲストのリソース消費とホストの容量 | cgroups v2 memory.max=128MB、swap.max=0、cpu.max="50000 100000"、pids.max=32 |
| ジェイル ↔ ネットワーク | あらゆる外向き通信やホスト側への到達性 | プライベートな NET 名前空間、lo ダウン、socket システムコールの強制終了 |
🔍 この図のインタラクティブでテーマ対応のバージョン(PNG/SVG エクスポート付き)は、
docs/archify/内の証拠に基づく候補から作成されています。
sequenceDiagram
autonumber
participant C as Client
participant A as REST API
participant P as Warm Pool
participant R as Process Runner
participant I as sandbox_init
participant G as Untrusted Code
participant DB as PostgreSQL
C->>A: POST /api/v1/execute
A->>P: acquire slot · 16 max, 3 s timeout
P->>R: Run(req, limits)
R->>I: fork under CLONE_NEWPID·NET·NS·IPC·UTS·USER
R->>R: create cgroup scope → attach PID<br/>128 MB · 0.5 CPU · 32 PIDs
I->>I: pivot_root RO rootfs · lo down · seccomp-BPF
I->>G: syscall.Exec target interpreter
G-->>R: stdout / stderr via pipes (512 KB cap)
R->>R: watchdog: SIGKILL at 5 s → exit 137
R-->>P: result + cgroup metrics (peak mem, CPU)
P-->>A: ExecutionResult
A-->>C: 200 JSON
A-)DB: async audit INSERT (non-blocking)
ステップバイステップ:
tenant_id、language、code、任意の stdin/limits) を POST します。不正または未対応のペイロードは 400/422 で即座に失敗します。sandbox_init が読み取り専用の rootfs にピボットし、クォータ付き tmpfs をマウントし、lo を停止し、seccomp-BPF 許可リストをインストールしてから、インタプリタを exec します — ペイロードが親プロセスによって特権実行されることは決してありません。memory.max に引っかかります (クリーンな OOM kill)。fork 爆弾は pids.max = 32 で拒否されます。socket/ptrace/mount 呼び出しはカーネルによって kill されます (SIGSYS/SIGKILL → SECURITY_VIOLATION)。execution_runs に挿入されるため、永続化がレスポンスのレイテンシをブロックしたり漏洩させたりすることは決してありません。| レイヤー | 技術 | 役割 |
|---|---|---|
| 言語 | Go 1.27 (github.com/kavaryx/sandboxed-runner) | デーモン、ランナー、分離、テスト |
| カーネル分離 | cgroups v2 · namespaces · seccomp-BPF (libseccomp-golang) · pivot_root | 封じ込めの強制 |
| ランタイムイメージ | 最小限の Python / Node rootfs (Docker ビルド、runtimes/*.tar) | 読み取り専用のゲストファイルシステム |
| API | net/http 標準ライブラリ (REST、JSON) | 取り込み + ヘルスチェック |
| キュー | Redis 7 (go-redis/v9) — sandbox:jobs に対する LPUSH/BRPop | 非同期ジョブパイプライン (モジュール) |
| 永続化 | PostgreSQL 16 (lib/pq) | 永続的な実行監査 |
| ツール | Docker Compose · Makefile · go test | 開発、ビルド、セキュリティテスト |