徹底解説:Dify Sandbox
こんにちは。今回はユーザが自由に定義したPythonやNode.jsのコードをセキュアに実行するサンドボックス環境である「Dify Sandbox」を紹介します。
Dify Sandboxは名前の通りDify(オープンソースのLLMアプリ開発プラットフォーム)で使用されている実行環境であり、画像のようなユーザが自由に定義したコードをセキュアに実行するものです。
https://docs.dify.ai/ja-jp/guides/workflow/node/code
はじめに
本記事ではDify Sandboxを開発環境に組み込むための解説とDify Sandbox自体の特徴を整理した2部構成に分けています。
また、以後のコードはすべてPythonでの実行を想定しています。
Dify Sandboxを実行する
それでは簡単にDify Sandboxを試してみましょう。
docker pull langgenius/dify-sandbox
まず、Dify SandboxのDocker ImageをローカルにPullします。
docker run -p 8194:8194 langgenius/dify-sandbox
その後、8194 portをlocalhostにバインドしてコンテナを起動すれば準備完了です。試しに以下のcurlコマンドを実行してみると確かにPythonコードが実行され、結果が返ってきました。
curl -X POST http://localhost:8194/v1/sandbox/run \
-H "Content-Type: application/json" \
-H "X-Api-Key: dify-sandbox" \
-d '{
"language": "python3",
"code": "def main() -> dict:\n return {\n \"hello\": \"hello\"\n }\n\nprint(main())",
"preload": "",
"enable_network": false
}'
提供されているAPI
それでは詳しく解説をしていきます。Dify Sandboxでコードを実行するためのエンドポイントは/v1/sandbox/run
です。
/v1/sandbox/run
POST Request Body
Name | Type | Required | Description |
---|---|---|---|
language |
string | true | 言語 (python3 , nodejs ) |
code |
string | true | 実行するコード |
preload |
string | false | プリロードするスクリプト (任意) |
enable_network |
boolean | false | サンドボックスでネットワークアクセスを有効にするか (デフォルト: false) |
Example Request
{
"language": "python3",
"code": "def main() -> dict:\n return {\n \"hello\": \"hello\"\n }\n\nprint(main())",
"preload": "import math",
"enable_network": false
}
Response Body
Field | Type | Description |
---|---|---|
code |
int | レスポンスのステータスコード。成功時は 0 。 |
message |
string | レスポンスメッセージ。成功時は "success" 。 |
data |
object | 実行結果のデータオブジェクト。 |
├─ stdout
|
string | 標準出力の内容。 |
└─ error
|
string | 標準エラーの内容 (あれば)。 |
Example Response
{
"code": 0,
"message": "success",
"data": {
"error": "標準エラーの内容",
"stdout": "標準出力の内容"
}
}
💁 実行時の注意点
実行結果は標準出力する必要がある
最初に注意すべき点は、Request Bodyのcodeに関数定義だけでなく、その関数を呼び出して標準出力する内容も含める必要がります。
def main() -> dict:
return {
"hello": "world"
}
# ----- このコードが必要 -----
print(main())
引数を含むコードの場合は事前に展開する必要がある
2つ目に、引数を含むコードの場合は事前に展開する必要があります。仮に引数を二つとる関数を実行したい場合、引数を展開した上で関数を実行し、結果を標準出力する必要があります。
def main(a, b):
return a + b
print(main("hello", "world"))
これらの使用法を知るためには、実際にDifyがどのようにDify SandboxのAPIを実行しているのか見るのが一番確実です。
すると、想像通りmain関数に対して引数を展開して実行していることがわかります。
output_obj = main(**inputs_obj)
そして実行された結果をjsonへ変換してprintしています。Difyではユーザが任意に定義した関数を実行する際には毎回このtransformer処理を挟んでいるようです。
output_json = json.dumps(output_obj, indent=4)
result = f'''<<RESULT>>{{output_json}}<<RESULT>>'''
print(result)
enable_networkをtrueにしないと使用できない標準パッケージがある
次の章で詳しく解説をしますがDify Sandboxは細かいシステムコールの制御によってセキュリティリスクを抑えています。その影響もあって、enable_networkがfalseの環境下では特定のPythonの標準ライブラリが使用できないケースがありました。
Dify SandboxはCPUアーキテクチャに合わせてそれぞれ必要なシステムコールを制限しており、許容しているシステムコールの一覧は上記のファイルのまとまっています。
その中でもALLOW_NETWORK_SYSCALLS
のシステムコール群はenable_networkがfalseの環境下では有効にならないため、これらのシステムコールを必要とするライブラリの実行が制限されてしまいます。
私の場合はPython標準のjson
ライブラリが使えずに調査していたところ、enable_networkの設定をtrueにすることで解決できました。ファイルシステムに関連するシステムコールがjsonライブラリの内部で使用されていたのだと考えています。
Dify Sandboxの特徴
これまでDify Sandboxを開発環境に組み込むための解説をしてきました。次にDify Sandbox自体の特徴を整理していきます。
Dify SandboxはGo(1.20.6)で書かれており、公式のドキュメントには以下の特徴が挙げられています。
- システムセキュリティ
- ファイルシステムの隔離
1. システムセキュリティ
Dify Sandboxは予期せぬセキュリティ侵害を防ぐため、特定のシステムコールのみを許可する許可リストポリシーを採用しています。許可リストポリシーとは、あらかじめ安全と判断されたシステムコールのみに限定して実行を許可するアプローチであり、これにより攻撃者が意図的に危険なシステムコールを呼び出してシステムを攻撃するリスクを最小限に抑えることができます。
https://dify.ai/blog/dify-ai-blog-introducing-difysandbox
システムコールとは
ユーザー空間は通常アプリケーションが動作する領域で直接ハードウェアにアクセスできません。そのため低レベルな操作が必要な場合は、カーネル空間にあるOSカーネルにシステムコールを介して依頼をします。
ユーザに自由に実行されては危ないシステムコールとは?
Dify Sandboxで実装されているテストコードを確認してみましょう。すると以下のようなコードを実行した際に"operation not permitted"
になることが分かります。
import os
os.execl("/bin/ls", "ls")
Pythonにおいてos.execl
を実行すると、現在のプロセスを置き換える形で新たなプログラムを実行します。このようなコードが実行されてはサンドボックス側がプロセスの挙動を制御できなくなくなりますし、システム情報の漏洩などのリスクに繋がります。
これはDify Sandbox側でexecve(59)
のシステムコールが制御されているため実行を防ぐことができます。このように適切にシステムコールを制御してくれているのがDify Sandboxの強みです。
2. ファイルシステムの隔離
次に特徴としてDify Sandboxはコード実行における安全性向上のためにファイルシステムの隔離を行なっています。
Dify Sandboxが実際にユーザのコードを扱う実装はこちらです。
Dify Sandboxがコードを実行する際はまず一時的なファイルが生成され、冒頭で動的リンクライブラリを呼び出し実行環境を初期化した後にユーザーコードが実行されます。
仮想化されたファイルシステム
サンドボックス内で実行されるコードを安全に隔離するためには、仮想化されたファイルシステムを使用する必要があります。これにより、サンドボックス内のプロセスがホストシステムのファイルシステムにアクセスすることを防ぎます。
具体的にDify Sandboxではプロセスのルートディレクトリを仮想のディレクトリに変更することで、サンドボックス内のプロセスはその仮想ディレクトリ内のファイルのみを閲覧できるようにします。
このInitSeccomp
はユーザコードが実行される前に動的リンクライブラリとして実行されるコードになります。同様にシステムコールを制御する実装などもここで行われています。
https://dify.ai/blog/dify-ai-blog-introducing-difysandbox
その他Dify Sandboxに関するセキュリティの考慮や実装に関する解説はこちらのブログが参考になりました。
まとめ
今回はユーザが自由に定義したPythonやNode.jsのコードをセキュアに実行するサンドボックス環境である「Dify Sandbox」を紹介しました。
Dify Sandboxは細かいシステムコールの制御やセキュリティに配慮した実行が特徴であり、開発環境への組み込みも大変容易に行うことができました。
当然ユーザが自由にコードを書いて実行できるサンドボックス環境では、致命的なシステムリスクを避けることが第一です。しかし、なるべく幅広いユーザの課題を解決できるよう、柔軟にコードを実行したいという観点もあります。
Dify Sandboxはこれらのバランスをうまく取り、セキュリティへ最大限配慮した上で利便性の高いサンドボックス環境を用意できると感じました。
最後に
AI Shiftではエンジニアの採用に力を入れています!
少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか?
(オンライン・19時以降の面談も可能です!)
【面談フォームはこちら】
Discussion