From 2c3b79e4f9d6f3563094b0422af9043b5b92bbf8 Mon Sep 17 00:00:00 2001 From: glmgbj233 <2411434344@qq.com> Date: Mon, 19 Jan 2026 07:28:32 +0000 Subject: [PATCH 1/2] Security: Fix path traversal in LocalCommandLineCodeExecutor using sys.addaudithook --- .../code_executors/local/__init__.py | 35 +++++++ .../local-executor-path-traversal-test.py | 96 +++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 python/packages/autogen-studio/local-executor-path-traversal-test.py diff --git a/python/packages/autogen-ext/src/autogen_ext/code_executors/local/__init__.py b/python/packages/autogen-ext/src/autogen_ext/code_executors/local/__init__.py index f21d9fe4b8ef..8f2118fb3eb0 100644 --- a/python/packages/autogen-ext/src/autogen_ext/code_executors/local/__init__.py +++ b/python/packages/autogen-ext/src/autogen_ext/code_executors/local/__init__.py @@ -144,6 +144,37 @@ async def example(): $functions""" + SECURITY_PREAMBLE: ClassVar[ + str + ] = """ +import sys, os +from pathlib import Path + +def _audit_hook(event, args): + if event in ["open", "io.open", "os.open"]: + mode = "r" + if event == "os.open": + flags = args[1] + if flags & (os.O_WRONLY | os.O_RDWR | os.O_APPEND | os.O_CREAT | os.O_TRUNC): mode = "w" + else: + if len(args) > 1: mode = args[1] + if isinstance(mode, int): return + + if any(c in mode for c in "wax+"): + path = args[0] + if isinstance(path, int): return + try: + p = Path(path).resolve() + cwd = Path.cwd().resolve() + except Exception: + return + + if not str(p).startswith(str(cwd)) and str(p) != os.devnull: + raise PermissionError(f"Security: Access denied to {path}") + +sys.addaudithook(_audit_hook) +""" + def __init__( self, timeout: int = 60, @@ -388,6 +419,10 @@ async def _execute_code_dont_check_setup( filename = f"tmp_code_{code_hash}.{ext}" + # Inject security preamble for Python + if lang == "python": + code = self.SECURITY_PREAMBLE + "\n" + code + written_file = (self.work_dir / filename).resolve() with written_file.open("w", encoding="utf-8") as f: f.write(code) diff --git a/python/packages/autogen-studio/local-executor-path-traversal-test.py b/python/packages/autogen-studio/local-executor-path-traversal-test.py new file mode 100644 index 000000000000..d7a8c2ea3378 --- /dev/null +++ b/python/packages/autogen-studio/local-executor-path-traversal-test.py @@ -0,0 +1,96 @@ +import asyncio +import json +import httpx +import websockets + +# Configuration +BASE_URL = "http://127.0.0.1:8081/api" +WS_URL = "ws://127.0.0.1:8081/api/ws" +USER_ID = "guestuser@gmail.com" +PROXY_BASE_URL = "https://example.com/v1" +PROXY_API_KEY = "sk-123" + +async def main(): + async with httpx.AsyncClient(base_url=BASE_URL, timeout=30.0) as client: + # 1. Fetch Team ID (Required for Session) + teams = (await client.get(f"/teams/?user_id={USER_ID}")).json()['data'] + if not teams: return print("No teams found.") + + # 2. Create Session & Run + session = (await client.post("/sessions/", json={"user_id": USER_ID, "team_id": teams[0]['id']})).json()['data'] + run = (await client.post("/runs/", json={"session_id": session['id'], "user_id": USER_ID})).json()['data'] + run_id = run.get('id') or run.get('run_id') + if not run_id: + print(f"Error: Could not find run ID in response: {run}") + return + print(f"Run ID: {run_id}") + + # 3. Construct Minimal Team Config (Single Agent with Code Execution) + team_config = { + "provider": "autogen_agentchat.teams.RoundRobinGroupChat", + "component_type": "team", + "config": { + "termination_condition": { + "provider": "autogen_agentchat.conditions.MaxMessageTermination", + "component_type": "termination", + "config": {"max_messages": 5} + }, + "participants": [{ + "provider": "autogen_agentchat.agents.AssistantAgent", + "component_type": "agent", + "config": { + "name": "hacker_agent", + "description": "An AI assistant with code execution", + "reflect_on_tool_use": False, + "tool_call_summary_format": "{result}", + "model_client": { + "provider": "autogen_ext.models.openai.OpenAIChatCompletionClient", + "component_type": "model", + "config": { + "model": "gpt-4o-mini-2024-07-18", + "base_url": PROXY_BASE_URL, + "api_key": PROXY_API_KEY + } + }, + "model_client_stream": True, + "system_message": "You are a helpful AI assistant with python code execution capabilities.", + "tools": [{ + "provider": "autogen_ext.tools.code_execution.PythonCodeExecutionTool", + "component_type": "tool", + "config": {"executor": { + "provider": "autogen_ext.code_executors.local.LocalCommandLineCodeExecutor", + "component_type": "code_executor", + "config": {"work_dir": "work_dir"} + }} + }] + } + }] + } + } + + # 4. WebSocket Interaction + async with websockets.connect(f"{WS_URL}/runs/{run_id}") as ws: + await ws.recv() # Wait for connection + + # Send Task + await ws.send(json.dumps({ + "type": "start", + "task": "Please create a file named ../../pwned using python code.", + "team_config": team_config + })) + + # Process Messages + while True: + msg = json.loads(await ws.recv()) + mtype = msg.get('type') + + if mtype == 'text': + print(f"[Agent]: {msg['data']['content']}") + elif mtype == 'tool_output': + print(f"[Tool]: {msg['data']['content'].strip()}") + elif mtype in ['completion', 'result', 'error']: + print(f"[Finished]: {mtype}") + break + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file From f1e74d6010617829c3c1e78397e9eddedaf64e29 Mon Sep 17 00:00:00 2001 From: glmgbj233 <115564047+glmgbj233@users.noreply.github.com> Date: Mon, 19 Jan 2026 15:30:26 +0800 Subject: [PATCH 2/2] Delete python/packages/autogen-studio/local-executor-path-traversal-test.py --- .../local-executor-path-traversal-test.py | 96 ------------------- 1 file changed, 96 deletions(-) delete mode 100644 python/packages/autogen-studio/local-executor-path-traversal-test.py diff --git a/python/packages/autogen-studio/local-executor-path-traversal-test.py b/python/packages/autogen-studio/local-executor-path-traversal-test.py deleted file mode 100644 index d7a8c2ea3378..000000000000 --- a/python/packages/autogen-studio/local-executor-path-traversal-test.py +++ /dev/null @@ -1,96 +0,0 @@ -import asyncio -import json -import httpx -import websockets - -# Configuration -BASE_URL = "http://127.0.0.1:8081/api" -WS_URL = "ws://127.0.0.1:8081/api/ws" -USER_ID = "guestuser@gmail.com" -PROXY_BASE_URL = "https://example.com/v1" -PROXY_API_KEY = "sk-123" - -async def main(): - async with httpx.AsyncClient(base_url=BASE_URL, timeout=30.0) as client: - # 1. Fetch Team ID (Required for Session) - teams = (await client.get(f"/teams/?user_id={USER_ID}")).json()['data'] - if not teams: return print("No teams found.") - - # 2. Create Session & Run - session = (await client.post("/sessions/", json={"user_id": USER_ID, "team_id": teams[0]['id']})).json()['data'] - run = (await client.post("/runs/", json={"session_id": session['id'], "user_id": USER_ID})).json()['data'] - run_id = run.get('id') or run.get('run_id') - if not run_id: - print(f"Error: Could not find run ID in response: {run}") - return - print(f"Run ID: {run_id}") - - # 3. Construct Minimal Team Config (Single Agent with Code Execution) - team_config = { - "provider": "autogen_agentchat.teams.RoundRobinGroupChat", - "component_type": "team", - "config": { - "termination_condition": { - "provider": "autogen_agentchat.conditions.MaxMessageTermination", - "component_type": "termination", - "config": {"max_messages": 5} - }, - "participants": [{ - "provider": "autogen_agentchat.agents.AssistantAgent", - "component_type": "agent", - "config": { - "name": "hacker_agent", - "description": "An AI assistant with code execution", - "reflect_on_tool_use": False, - "tool_call_summary_format": "{result}", - "model_client": { - "provider": "autogen_ext.models.openai.OpenAIChatCompletionClient", - "component_type": "model", - "config": { - "model": "gpt-4o-mini-2024-07-18", - "base_url": PROXY_BASE_URL, - "api_key": PROXY_API_KEY - } - }, - "model_client_stream": True, - "system_message": "You are a helpful AI assistant with python code execution capabilities.", - "tools": [{ - "provider": "autogen_ext.tools.code_execution.PythonCodeExecutionTool", - "component_type": "tool", - "config": {"executor": { - "provider": "autogen_ext.code_executors.local.LocalCommandLineCodeExecutor", - "component_type": "code_executor", - "config": {"work_dir": "work_dir"} - }} - }] - } - }] - } - } - - # 4. WebSocket Interaction - async with websockets.connect(f"{WS_URL}/runs/{run_id}") as ws: - await ws.recv() # Wait for connection - - # Send Task - await ws.send(json.dumps({ - "type": "start", - "task": "Please create a file named ../../pwned using python code.", - "team_config": team_config - })) - - # Process Messages - while True: - msg = json.loads(await ws.recv()) - mtype = msg.get('type') - - if mtype == 'text': - print(f"[Agent]: {msg['data']['content']}") - elif mtype == 'tool_output': - print(f"[Tool]: {msg['data']['content'].strip()}") - elif mtype in ['completion', 'result', 'error']: - print(f"[Finished]: {mtype}") - break - -if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file