diff --git a/README.md b/README.md index ba5eba9..e51461b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ A Rust-powered secure sandbox for multi-language code execution, leveraging WebA - 🔌 **Easy Integration**: Clean Python bindings for seamless integration with existing projects - 🎯 **AI-Optimized**: Runtime environment specifically optimized for AI applications +## 🔧 Requirements + +- Python >= 3.8 + ## 🚀 Quick Start ### Installation @@ -129,9 +133,10 @@ function message_handler(message_dict) { asyncio.run(main()) ``` -## 🔧 Requirements +## Examples -- Python >= 3.8 +- [Notebook-Qick Start](examples/notebook/lyric_quick_start.ipynb): A Jupyter notebook demonstrating how to use Lyric to execute Python and JavaScript code. +- [Notebook-Sandbox Execution](examples/notebook/lyric_sandbox_verification.ipynb): A Jupyter notebook demonstrating how to use Lyric to execute Python and JavaScript code in a sandboxed environment. ## 🤝 Contributing diff --git a/examples/notebook/lyric_quick_start.ipynb b/examples/notebook/lyric_quick_start.ipynb new file mode 100644 index 0000000..d538169 --- /dev/null +++ b/examples/notebook/lyric_quick_start.ipynb @@ -0,0 +1,465 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quick Start\n", + "\n", + "## 1. Install Dependencies\n", + "\n", + "First, we need to install the `lyric-py` package." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple\n", + "Collecting lyric-py>=0.1.3\n", + " Downloading https://pypi.tuna.tsinghua.edu.cn/packages/6d/0a/bd84e1c9316869ce790fe014155eac1e58f27a899f50692d84bbfe9a7fec/lyric_py-0.1.3-cp310-cp310-macosx_10_12_x86_64.whl (11.2 MB)\n", + "\u001b[2K \u001b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m11.2/11.2 MB\u001b[0m \u001b[31m7.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0mMB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m:01\u001b[0m\n", + "\u001b[?25hCollecting msgpack (from lyric-py>=0.1.3)\n", + " Downloading https://pypi.tuna.tsinghua.edu.cn/packages/df/7a/d174cc6a3b6bb85556e6a046d3193294a92f9a8e583cdbd46dc8a1d7e7f4/msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl (84 kB)\n", + "Collecting cloudpickle (from lyric-py>=0.1.3)\n", + " Downloading https://pypi.tuna.tsinghua.edu.cn/packages/48/41/e1d85ca3cab0b674e277c8c4f678cf66a91cd2cecf93df94353a606fe0db/cloudpickle-3.1.0-py3-none-any.whl (22 kB)\n", + "Collecting lyric-task (from lyric-py>=0.1.3)\n", + " Downloading https://pypi.tuna.tsinghua.edu.cn/packages/81/2a/ffdc51f17afcc19d422c1d7d3a77121a3da6ddb1b23feb4b1ad494405ec5/lyric_task-0.1.3-py3-none-any.whl (7.1 kB)\n", + "Installing collected packages: msgpack, lyric-task, cloudpickle, lyric-py\n", + "Successfully installed cloudpickle-3.1.0 lyric-py-0.1.3 lyric-task-0.1.3 msgpack-1.1.0\n" + ] + } + ], + "source": [ + "!pip install \"lyric-py>=0.1.3\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Install Language Worker\n", + "\n", + "Next, we need to install Python webassembly worker and JavaScript webassembly worker." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple\n", + "Collecting lyric-py-worker>=0.1.3\n", + " Downloading https://pypi.tuna.tsinghua.edu.cn/packages/16/a1/6ca985fd1cee109c3adb9c3a01a9c68916ad1274af2189af9b3e81ae75e2/lyric_py_worker-0.1.3-py3-none-any.whl (10.7 MB)\n", + "\u001b[2K \u001b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m10.7/10.7 MB\u001b[0m \u001b[31m4.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m02\u001b[0m\n", + "\u001b[?25hCollecting lyric-js-worker>=0.1.3\n", + " Downloading https://pypi.tuna.tsinghua.edu.cn/packages/fd/ff/ee475a3637c93850d4605ba8941d6cdecaa8b4038c3b9b5e7074be68c863/lyric_js_worker-0.1.3-py3-none-any.whl (3.5 MB)\n", + "\u001b[2K \u001b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m3.5/3.5 MB\u001b[0m \u001b[31m6.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m[31m6.7 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\n", + "\u001b[?25hRequirement already satisfied: lyric-task in /Users/staneyffer/projects/dl/llm/lyric/examples/.venv/lib/python3.10/site-packages (from lyric-py-worker>=0.1.3) (0.1.3)\n", + "Installing collected packages: lyric-py-worker, lyric-js-worker\n", + "Successfully installed lyric-js-worker-0.1.3 lyric-py-worker-0.1.3\n" + ] + } + ], + "source": [ + "!pip install \"lyric-py-worker>=0.1.3\" \"lyric-js-worker>=0.1.3\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Start Lyric Driver\n", + "\n", + "To submit your code to the lyric workers, you need to start the lyric driver." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from lyric import DefaultLyricDriver\n", + "\n", + "lcd = DefaultLyricDriver(host=\"127.0.0.1\", log_level=\"ERROR\")\n", + "lcd.start()\n", + "\n", + "# Load workers(default: Python, JavaScript)\n", + "await lcd.lyric.load_default_workers()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3.1 Execute Python Code\n", + "\n", + "You can submit a Python code by `exec` method. It will be executed by the Python webassembly worker, \n", + "then you can get the execution status and result." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\"local_778429183bca2759@b8837e05-c837-4078-a41b-99accbfc2b9c\"]: \"[Python-InterpreterTask] script: InterpreterRequest(protocol=1, lang='PYTHON', code='\\\\nprint(\\\"Hello, World!, I am a Python program.\\\")\\\\n\\\\ndef add(a, b):\\\\n return a + b\\\\n\\\\nresult = add(1, 2)\\\\nprint(result)\\\\n')\"\n", + "[\"local_778429183bca2759@b8837e05-c837-4078-a41b-99accbfc2b9c\"]: \"Hello, World!, I am a Python program.\"\n", + "[\"local_778429183bca2759@b8837e05-c837-4078-a41b-99accbfc2b9c\"]: \"3\"\n", + "[\"local_778429183bca2759@b8837e05-c837-4078-a41b-99accbfc2b9c\"]: \"[Python-InterpreterTask] test_result: b'{\\\"content\\\":\\\"Execute script successfully\\\",\\\"exit_code\\\":0,\\\"lang\\\":\\\"Python\\\",\\\"protocol\\\":1,\\\"stderr\\\":\\\"\\\",\\\"stdout\\\":\\\"Hello, World!, I am a Python program.\\\\\\\\n3\\\\\\\\n\\\",\\\"success\\\":true}'\"\n", + "CodeResult(exit_code=0, stdout='Hello, World!, I am a Python program.\\n3\\n', stderr='', output=None)\n" + ] + } + ], + "source": [ + "python_code = \"\"\"\n", + "print(\"Hello, World!, I am a Python program.\")\n", + "\n", + "def add(a, b):\n", + " return a + b\n", + "\n", + "result = add(1, 2)\n", + "print(result)\n", + "\"\"\"\n", + "\n", + "py_res = await lcd.exec(python_code, \"python\")\n", + "print(py_res)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "CodeResult(exit_code=0, stdout='Hello, World!, I am a Python program.\\n3\\n', stderr='', output=None)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Let's show the output of the Python code\n", + "py_res" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, the Python code is executed successfully, and the result like below.\n", + "\n", + "```python\n", + "CodeResult(exit_code=0, stdout='Hello, World!, I am a Python program.\\n3\\n', stderr='', output=None)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.2 Execute JavaScript Code\n", + "\n", + "You can submit a JavaScript code by `exec` method with `lang=\"javascript\"` parameter. It will be executed by the JavaScript webassembly worker, then you can get the execution status and result." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"Before run code in interpreter Task\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"Hello, World!, I am a JavaScript program.\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"3\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"After run code in interpreter Task\"\n", + "CodeResult(exit_code=0, stdout='Before run code in interpreter Task\\nHello, World!, I am a JavaScript program.\\n3\\nAfter run code in interpreter Task', stderr='', output=None)\n" + ] + } + ], + "source": [ + "js_code = \"\"\"\n", + "console.log(\"Hello, World!, I am a JavaScript program.\");\n", + "\n", + "function add(a, b) {\n", + " return a + b;\n", + "}\n", + "\n", + "let result = add(1, 2);\n", + "console.log(result);\n", + "\"\"\"\n", + "\n", + "js_res = await lcd.exec(js_code, \"javascript\")\n", + "print(js_res)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "CodeResult(exit_code=0, stdout='Before run code in interpreter Task\\nHello, World!, I am a JavaScript program.\\n3\\nAfter run code in interpreter Task', stderr='', output=None)" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Let's show the output of the JavaScript code\n", + "js_res" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, the JavaScript code is executed successfully, and the result like below.\n", + "\n", + "```python\n", + "CodeResult(exit_code=0, stdout='Before run code in interpreter Task\\nHello, World!, I am a JavaScript program.\\n3\\nAfter run code in interpreter Task', stderr='', output=None)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.3 Execute Python Function\n", + "\n", + "You can submit a Python function by `exec1` method. It will be executed by the Python webassembly worker, then you can get the execution status and result.\n", + "\n", + "Note: **You must pass the function name to be executed as a parameter.**" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\"local_d112c954598914a1@11571d8a-05c7-4166-8908-be3637362b99\"]: \"[Python-InterpreterTask] script: InterpreterRequest(protocol=1, lang='PYTHON', code='\\\\n# Define a function that take a dictionary as input and return a dictionary\\\\n\\\\ndef message_handler(message_dict):\\\\n user_message = message_dict.get(\\\"user_message\\\")\\\\n ai_message = message_dict.get(\\\"ai_message\\\")\\\\n return {\\\\n \\\"user\\\": user_message,\\\\n \\\"ai\\\": ai_message,\\\\n \\\"all\\\": [user_message, ai_message],\\\\n \\\"custom\\\": \\\"custom\\\",\\\\n \\\"handler_language\\\": \\\"python\\\",\\\\n }\\\\n')\"\n", + "[\"local_d112c954598914a1@11571d8a-05c7-4166-8908-be3637362b99\"]: \"[Python-InterpreterTask] call_name: message_handler\"\n", + "Python result: {'ai': 'Hello from AI', 'all': ['Hello from user', 'Hello from AI'], 'custom': 'custom', 'handler_language': 'python', 'user': 'Hello from user'}\n", + "Full output: CodeResult(exit_code=0, stdout='', stderr='', output={'ai': 'Hello from AI', 'all': ['Hello from user', 'Hello from AI'], 'custom': 'custom', 'handler_language': 'python', 'user': 'Hello from user'})\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "py_func = \"\"\"\n", + "# Define a function that take a dictionary as input and return a dictionary\n", + "\n", + "def message_handler(message_dict):\n", + " user_message = message_dict.get(\"user_message\")\n", + " ai_message = message_dict.get(\"ai_message\")\n", + " return {\n", + " \"user\": user_message,\n", + " \"ai\": ai_message,\n", + " \"all\": [user_message, ai_message],\n", + " \"custom\": \"custom\",\n", + " \"handler_language\": \"python\",\n", + " }\n", + "\"\"\"\n", + "\n", + "# Create a input dictionary\n", + "input_data = {\n", + " \"user_message\": \"Hello from user\",\n", + " \"ai_message\": \"Hello from AI\",\n", + "}\n", + "# Convert the input dictionary to json bytes, as the input to the function(It will be converted to dictionary inside the function)\n", + "input_bytes = json.dumps(input_data).encode(\"utf-8\")\n", + "\n", + "py_func_res = await lcd.exec1(py_func, input_bytes, \"message_handler\", lang=\"python\")\n", + "print(\"Python result:\", py_func_res.output)\n", + "print(\"Full output:\", py_func_res)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'ai': 'Hello from AI',\n", + " 'all': ['Hello from user', 'Hello from AI'],\n", + " 'custom': 'custom',\n", + " 'handler_language': 'python',\n", + " 'user': 'Hello from user'}" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Let's show the output of the Python function(It will be a dictionary)\n", + "py_func_res.output" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.4 Execute JavaScript Function\n", + "\n", + "You can submit a JavaScript function by `exec1` method with `lang=\"javascript\"` parameter. It will be executed by the JavaScript webassembly worker, then you can get the execution status and result." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"[JavaScript-InterpreterTask] script: {\\\"protocol\\\":1,\\\"lang\\\":\\\"JAVASCRIPT\\\",\\\"code\\\":\\\"\\\\n// Define a function that take a dictionary as input and return a dictionary\\\\n\\\\nfunction message_handler(message_dict) {\\\\n return {\\\\n user: message_dict.user_message,\\\\n ai: message_dict.ai_message,\\\\n all: [message_dict.user_message, message_dict.ai_message],\\\\n custom: \\\\\\\"custom\\\\\\\",\\\\n handler_language: \\\\\\\"javascript\\\\\\\",\\\\n };\\\\n}\\\\n\\\"}\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"[JavaScript-InterpreterTask] call_name: message_handler\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"The code is: \"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"// Define a function that take a dictionary as input and return a dictionary\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"function message_handler(message_dict) {\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" return {\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" user: message_dict.user_message,\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" ai: message_dict.ai_message,\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" all: [message_dict.user_message, message_dict.ai_message],\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" custom: \\\"custom\\\",\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" handler_language: \\\"javascript\\\",\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" };\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"}\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"[JavaScript-InterpreterTask] dynamicFunction: function message_handler(message_dict) {\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" return {\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" user: message_dict.user_message,\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" ai: message_dict.ai_message,\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" all: [message_dict.user_message, message_dict.ai_message],\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" custom: \\\"custom\\\",\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" handler_language: \\\"javascript\\\",\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \" };\"\n", + "[\"local_9499471fefe7edde@97033ada-8f45-41d3-8f88-c4ed80262261\"]: \"}\"\n", + "JavaScript result: {'ai': 'Hello from AI', 'all': ['Hello from user', 'Hello from AI'], 'custom': 'custom', 'handler_language': 'javascript', 'user': 'Hello from user'}\n", + "Full output: CodeResult(exit_code=0, stdout='The code is: \\n\\n// Define a function that take a dictionary as input and return a dictionary\\n\\nfunction message_handler(message_dict) {\\n return {\\n user: message_dict.user_message,\\n ai: message_dict.ai_message,\\n all: [message_dict.user_message, message_dict.ai_message],\\n custom: \"custom\",\\n handler_language: \"javascript\",\\n };\\n}\\n\\n[JavaScript-InterpreterTask] dynamicFunction: function message_handler(message_dict) {\\n return {\\n user: message_dict.user_message,\\n ai: message_dict.ai_message,\\n all: [message_dict.user_message, message_dict.ai_message],\\n custom: \"custom\",\\n handler_language: \"javascript\",\\n };\\n}', stderr='', output={'ai': 'Hello from AI', 'all': ['Hello from user', 'Hello from AI'], 'custom': 'custom', 'handler_language': 'javascript', 'user': 'Hello from user'})\n" + ] + } + ], + "source": [ + "js_func = \"\"\"\n", + "// Define a function that take a dictionary as input and return a dictionary\n", + "\n", + "function message_handler(message_dict) {\n", + " return {\n", + " user: message_dict.user_message,\n", + " ai: message_dict.ai_message,\n", + " all: [message_dict.user_message, message_dict.ai_message],\n", + " custom: \"custom\",\n", + " handler_language: \"javascript\",\n", + " };\n", + "}\n", + "\"\"\"\n", + "\n", + "js_func_res = await lcd.exec1(js_func, input_bytes, \"message_handler\", lang=\"javascript\")\n", + "print(\"JavaScript result:\", js_func_res.output)\n", + "print(\"Full output:\", js_func_res)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'ai': 'Hello from AI',\n", + " 'all': ['Hello from user', 'Hello from AI'],\n", + " 'custom': 'custom',\n", + " 'handler_language': 'javascript',\n", + " 'user': 'Hello from user'}" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Let's show the output of the JavaScript function(It will be a dictionary)\n", + "js_func_res.output" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Stop Lyric Driver\n", + "\n", + "Finally, you can stop the lyric driver by calling the `stop` method." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "lcd.stop()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/notebook/lyric_sandbox_verification.ipynb b/examples/notebook/lyric_sandbox_verification.ipynb new file mode 100644 index 0000000..6fa02b7 --- /dev/null +++ b/examples/notebook/lyric_sandbox_verification.ipynb @@ -0,0 +1,368 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Lyric Sandbox verification\n", + "\n", + "This notebook will be showing the capabilities of the Lyric Sandbox environment. \n", + "It will run Python code and JavaScript code in the sandbox environment which is isolated from the host environment.\n", + "\n", + "If you not familiar with the lyric, please read this notebook first: [Quick Start](lyric_quick_start.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. File System Isolation\n", + "\n", + "The sandbox environment runs in webassembly environment, we can control the file system access of the sandbox environment.\n", + "\n", + "It can't access any file in the host environment by default.\n", + "\n", + "Let's try to access a file in the host environment." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# First start lyric driver\n", + "\n", + "from lyric import DefaultLyricDriver\n", + "\n", + "lcd = DefaultLyricDriver(host=\"127.0.0.1\", log_level=\"ERROR\")\n", + "lcd.start()\n", + "\n", + "# Load workers(default: Python, JavaScript)\n", + "await lcd.lyric.load_default_workers()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] script: InterpreterRequest(protocol=1, lang='PYTHON', code='\\\\nimport os\\\\n\\\\n# List the files in the root directory\\\\nroot = os.listdir(\\\\'/\\\\')\\\\nprint(\\\"Files in the root directory:\\\", root)\\\\n')\"\n", + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] Exception: [Errno 44] No such file or directory: '/'\"\n", + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] test_result: b'{\\\"content\\\":\\\"Execute script successfully\\\",\\\"exit_code\\\":1,\\\"lang\\\":\\\"Python\\\",\\\"protocol\\\":1,\\\"stderr\\\":\\\"\\\",\\\"stdout\\\":\\\"[Python-InterpreterTask] Exception: [Errno 44] No such file or directory: \\\\'/\\\\'\\\\\\\\n\\\",\\\"success\\\":false}'\"\n", + "CodeResult(exit_code=1, stdout=\"[Python-InterpreterTask] Exception: [Errno 44] No such file or directory: '/'\\n\", stderr='', output=None)\n", + "logs:\n", + "[Python-InterpreterTask] Exception: [Errno 44] No such file or directory: '/'\n", + "\n" + ] + } + ], + "source": [ + "# List the files in the root directory\n", + "\n", + "python_code = \"\"\"\n", + "import os\n", + "\n", + "# List the files in the root directory\n", + "root = os.listdir('/')\n", + "print(\"Files in the root directory:\", root)\n", + "\"\"\"\n", + "\n", + "py_res = await lcd.exec(python_code, \"python\")\n", + "assert py_res.exit_code !=0, \"Python code should fail\"\n", + "\n", + "print(py_res)\n", + "print(f\"logs:\\n{py_res.logs}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, the exit code is 1, and the output is \n", + "```\n", + "[Python-InterpreterTask] Exception: [Errno 44] No such file or directory: '/'\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] script: InterpreterRequest(protocol=1, lang='PYTHON', code=\\\"\\\\nimport os\\\\n\\\\n# Create a directory\\\\nos.mkdir('/test')\\\\n\\\")\"\n", + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] Exception: [Errno 44] No such file or directory: '/test'\"\n", + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] test_result: b'{\\\"content\\\":\\\"Execute script successfully\\\",\\\"exit_code\\\":1,\\\"lang\\\":\\\"Python\\\",\\\"protocol\\\":1,\\\"stderr\\\":\\\"\\\",\\\"stdout\\\":\\\"[Python-InterpreterTask] Exception: [Errno 44] No such file or directory: \\\\'/test\\\\'\\\\\\\\n\\\",\\\"success\\\":false}'\"\n", + "CodeResult(exit_code=1, stdout=\"[Python-InterpreterTask] Exception: [Errno 44] No such file or directory: '/test'\\n\", stderr='', output=None)\n", + "logs:\n", + "[Python-InterpreterTask] Exception: [Errno 44] No such file or directory: '/test'\n", + "\n" + ] + } + ], + "source": [ + "# Create directory\n", + "\n", + "python_code = \"\"\"\n", + "import os\n", + "\n", + "# Create a directory\n", + "os.mkdir('/test')\n", + "\"\"\"\n", + "\n", + "py_res = await lcd.exec(python_code, \"python\")\n", + "assert py_res.exit_code !=0, \"Python code should fail\"\n", + "print(py_res)\n", + "print(f\"logs:\\n{py_res.logs}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Network Isolation\n", + "\n", + "The sandbox environment can't access the network by default because the network is disabled in the sandbox environment.\n", + "\n", + "Let's try to create a socket to access the network." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] script: InterpreterRequest(protocol=1, lang='PYTHON', code='\\\\nimport socket\\\\n\\\\ndef start_client():\\\\n # Create TCP/IP socket\\\\n client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\\\\n \\\\n # Connect the socket to the server\\\\n server_address = (\\\\'127.0.0.1\\\\', 12345)\\\\n print(f\\\"Connecting to {server_address}\\\")\\\\n client_socket.connect(server_address)\\\\n \\\\n try:\\\\n # Send data\\\\n message = \\\"Hello, Server!\\\"\\\\n print(f\\\"Sending: {message}\\\")\\\\n client_socket.sendall(message.encode())\\\\n \\\\n # Receive data\\\\n data = client_socket.recv(1024).decode()\\\\n print(f\\\"Received: {data}\\\")\\\\n \\\\n finally:\\\\n # Close the connection\\\\n print(\\\"Closing connection\\\")\\\\n client_socket.close()\\\\nstart_client()\\\\n')\"\n", + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"Connecting to ('127.0.0.1', 12345)\"\n", + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] Exception: [Errno 2] Permission denied\"\n", + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] test_result: b'{\\\"content\\\":\\\"Execute script successfully\\\",\\\"exit_code\\\":1,\\\"lang\\\":\\\"Python\\\",\\\"protocol\\\":1,\\\"stderr\\\":\\\"\\\",\\\"stdout\\\":\\\"Connecting to (\\\\'127.0.0.1\\\\', 12345)\\\\\\\\n[Python-InterpreterTask] Exception: [Errno 2] Permission denied\\\\\\\\n\\\",\\\"success\\\":false}'\"\n", + "CodeResult(exit_code=1, stdout=\"Connecting to ('127.0.0.1', 12345)\\n[Python-InterpreterTask] Exception: [Errno 2] Permission denied\\n\", stderr='', output=None)\n" + ] + } + ], + "source": [ + "# Create a socket to connect to a server\n", + "\n", + "py_client_code = \"\"\"\n", + "import socket\n", + "\n", + "def start_client():\n", + " # Create TCP/IP socket\n", + " client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n", + " \n", + " # Connect the socket to the server\n", + " server_address = ('127.0.0.1', 12345)\n", + " print(f\"Connecting to {server_address}\")\n", + " client_socket.connect(server_address)\n", + " \n", + " try:\n", + " # Send data\n", + " message = \"Hello, Server!\"\n", + " print(f\"Sending: {message}\")\n", + " client_socket.sendall(message.encode())\n", + " \n", + " # Receive data\n", + " data = client_socket.recv(1024).decode()\n", + " print(f\"Received: {data}\")\n", + " \n", + " finally:\n", + " # Close the connection\n", + " print(\"Closing connection\")\n", + " client_socket.close()\n", + "start_client()\n", + "\"\"\"\n", + "\n", + "py_res = await lcd.exec(py_client_code, \"python\")\n", + "assert py_res.exit_code !=0, \"Python code should fail\"\n", + "print(py_res)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, the exit code is 1, and the output is \n", + "```python\n", + "CodeResult(exit_code=1, stdout=\"Connecting to ('127.0.0.1', 12345)\\n[Python-InterpreterTask] Exception: [Errno 2] Permission denied\\n\", stderr='', output=None)\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] script: InterpreterRequest(protocol=1, lang='PYTHON', code='\\\\nimport socket\\\\n\\\\ndef start_server():\\\\n # Create TCP/IP socket\\\\n server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\\\\n \\\\n # Bind the address and port\\\\n server_address = (\\\\'127.0.0.1\\\\', 12345)\\\\n server_socket.bind(server_address)\\\\n \\\\n # Begin listening, maximum queue connection is 1\\\\n server_socket.listen(1)\\\\n print(f\\\"Server listening on {server_address}\\\")\\\\n \\\\n while True:\\\\n # Wait for connection\\\\n print(\\\"Waiting for connection...\\\")\\\\n client_socket, client_address = server_socket.accept()\\\\n try:\\\\n print(f\\\"Connected to {client_address}\\\")\\\\n \\\\n # Receive data\\\\n data = client_socket.recv(1024).decode()\\\\n print(f\\\"Received: {data}\\\")\\\\n \\\\n # Send response\\\\n response = f\\\"Server received: {data}\\\"\\\\n client_socket.sendall(response.encode())\\\\n \\\\n finally:\\\\n # Close the connection\\\\n client_socket.close()\\\\nstart_server()\\\\n')\"\n", + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] Exception: [Errno 2] Permission denied\"\n", + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] test_result: b'{\\\"content\\\":\\\"Execute script successfully\\\",\\\"exit_code\\\":1,\\\"lang\\\":\\\"Python\\\",\\\"protocol\\\":1,\\\"stderr\\\":\\\"\\\",\\\"stdout\\\":\\\"[Python-InterpreterTask] Exception: [Errno 2] Permission denied\\\\\\\\n\\\",\\\"success\\\":false}'\"\n", + "CodeResult(exit_code=1, stdout='[Python-InterpreterTask] Exception: [Errno 2] Permission denied\\n', stderr='', output=None)\n" + ] + } + ], + "source": [ + "# Create a server to accept connections\n", + "\n", + "py_server_code = \"\"\"\n", + "import socket\n", + "\n", + "def start_server():\n", + " # Create TCP/IP socket\n", + " server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n", + " \n", + " # Bind the address and port\n", + " server_address = ('127.0.0.1', 12345)\n", + " server_socket.bind(server_address)\n", + " \n", + " # Begin listening, maximum queue connection is 1\n", + " server_socket.listen(1)\n", + " print(f\"Server listening on {server_address}\")\n", + " \n", + " while True:\n", + " # Wait for connection\n", + " print(\"Waiting for connection...\")\n", + " client_socket, client_address = server_socket.accept()\n", + " try:\n", + " print(f\"Connected to {client_address}\")\n", + " \n", + " # Receive data\n", + " data = client_socket.recv(1024).decode()\n", + " print(f\"Received: {data}\")\n", + " \n", + " # Send response\n", + " response = f\"Server received: {data}\"\n", + " client_socket.sendall(response.encode())\n", + " \n", + " finally:\n", + " # Close the connection\n", + " client_socket.close()\n", + "start_server()\n", + "\"\"\"\n", + "\n", + "py_res = await lcd.exec(py_server_code, \"python\")\n", + "assert py_res.exit_code !=0, \"Python code should fail\"\n", + "print(py_res)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Process And Memory Isolation" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] script: InterpreterRequest(protocol=1, lang='PYTHON', code=\\\"\\\\nimport multiprocessing\\\\nimport ctypes\\\\n\\\\n# Try to spawn a process\\\\ndef spawn_process():\\\\n print('Spawned!')\\\\np = multiprocessing.Process(target=spawn_process)\\\\np.start()\\\\n\\\\n# Try to access memory directly\\\\nlibc = ctypes.CDLL('libc.so.6')\\\\n\\\")\"\n", + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] Exception: No module named 'ctypes'\"\n", + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] test_result: b'{\\\"content\\\":\\\"Execute script successfully\\\",\\\"exit_code\\\":1,\\\"lang\\\":\\\"Python\\\",\\\"protocol\\\":1,\\\"stderr\\\":\\\"\\\",\\\"stdout\\\":\\\"[Python-InterpreterTask] Exception: No module named \\\\'ctypes\\\\'\\\\\\\\n\\\",\\\"success\\\":false}'\"\n", + "CodeResult(exit_code=1, stdout=\"[Python-InterpreterTask] Exception: No module named 'ctypes'\\n\", stderr='', output=None)\n" + ] + } + ], + "source": [ + "py_code = \"\"\"\n", + "import multiprocessing\n", + "import ctypes\n", + "\n", + "# Try to spawn a process\n", + "def spawn_process():\n", + " print('Spawned!')\n", + "p = multiprocessing.Process(target=spawn_process)\n", + "p.start()\n", + "\n", + "# Try to access memory directly\n", + "libc = ctypes.CDLL('libc.so.6')\n", + "\"\"\"\n", + "\n", + "py_res = await lcd.exec(py_code, \"python\")\n", + "assert py_res.exit_code !=0, \"Python code should fail\"\n", + "print(py_res)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Time And Scheduler Attack Verification" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] script: InterpreterRequest(protocol=1, lang='PYTHON', code='\\\\nimport threading\\\\nimport time\\\\n\\\\ndef delayed_attack():\\\\n time.sleep(10) \\\\n __import__(\\\\'os\\\\').system(\\\\'echo \\\"Delayed attack!\\\"\\\\')\\\\n\\\\nthread = threading.Thread(target=delayed_attack)\\\\nthread.start()\\\\n')\"\n", + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] Exception: can't start new thread\"\n", + "[\"local_d112c954598914a1@343f4169-9f13-4856-aee3-0c30f6af6551\"]: \"[Python-InterpreterTask] test_result: b'{\\\"content\\\":\\\"Execute script successfully\\\",\\\"exit_code\\\":1,\\\"lang\\\":\\\"Python\\\",\\\"protocol\\\":1,\\\"stderr\\\":\\\"\\\",\\\"stdout\\\":\\\"[Python-InterpreterTask] Exception: can\\\\'t start new thread\\\\\\\\n\\\",\\\"success\\\":false}'\"\n", + "CodeResult(exit_code=1, stdout=\"[Python-InterpreterTask] Exception: can't start new thread\\n\", stderr='', output=None)\n" + ] + } + ], + "source": [ + "py_code = \"\"\"\n", + "import threading\n", + "import time\n", + "\n", + "def delayed_attack():\n", + " time.sleep(10) \n", + " __import__('os').system('echo \"Delayed attack!\"')\n", + "\n", + "thread = threading.Thread(target=delayed_attack)\n", + "thread.start()\n", + "\"\"\"\n", + "\n", + "py_res = await lcd.exec(py_code, \"python\")\n", + "assert py_res.exit_code !=0, \"Python code should fail\"\n", + "print(py_res)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}