Back to Blog

LangChain Tools with Secure Code Execution Using HopX

TutorialsAlin Dobra13 min read

LangChain Tools with Secure Code Execution Using HopX

LangChain's built-in PythonREPL tool has a big problem: it runs code directly on your machine. One hallucinated rm -rf / and your server is gone.

This guide shows you how to replace LangChain's dangerous code execution with secure, isolated HopX sandboxes—while keeping the familiar LangChain patterns you already know.

The Problem with LangChain's Default Code Execution

LangChain includes a PythonREPLTool that lets agents execute Python code:

python
1
# ⚠️ DANGEROUS - Don't use in production
2
from langchain_experimental.tools import PythonREPLTool
3
 
4
tool = PythonREPLTool()
5
result = tool.run("import os; os.system('rm -rf /')")  # Game over
6
 

This executes code directly on your host machine with full access to:

  • Your filesystem
  • Network connections
  • Environment variables (including API keys)
  • System processes

In production, this is a ticking time bomb.

The Solution: HopX Sandboxed Execution

Replace the dangerous PythonREPLTool with a custom tool that runs code in isolated HopX sandboxes:

text
1
2
                    LangChain Agent                          
3
                                                             
4
  "I need to run this Python code to analyze the data..."   
5
6
                              
7
                              
8
9
                   HopX Sandbox Tool                         
10
                   (Custom LangChain Tool)                   
11
12
                              
13
                              
14
15
                    HopX MicroVM                             
16
      
17
    Isolated execution environment                         
18
     No access to host filesystem                        
19
     Network policies enforced                           
20
     Resource limits applied                             
21
     Destroyed after execution                           
22
      
23
24
                              
25
                              
26
                    Results returned to agent
27
 

Prerequisites

Install the required packages:

bash
1
pip install langchain langchain-openai hopx-ai
2
 

Set your API keys:

bash
1
export OPENAI_API_KEY="sk-..."
2
export HOPX_API_KEY="your-hopx-key"
3
 

Step 1: Create a Secure Python Execution Tool

First, let's build a LangChain-compatible tool that executes code in HopX:

python
1
from langchain.tools import BaseTool
2
from hopx import Sandbox
3
from pydantic import BaseModel, Field
4
from typing import Type, Optional
5
 
6
class PythonCodeInput(BaseModel):
7
    """Input schema for Python code execution."""
8
    code: str = Field(description="The Python code to execute")
9
 
10
class SecurePythonTool(BaseTool):
11
    """Execute Python code securely in an isolated HopX sandbox."""
12
    
13
    name: str = "python_executor"
14
    description: str = """Execute Python code in a secure, isolated sandbox.
15
Use this tool when you need to:
16
- Perform calculations or data analysis
17
- Process files or data structures
18
- Run any Python code safely
19
 
20
The sandbox has pandas, numpy, matplotlib, requests, and standard libraries.
21
For visualizations, save to /app/output.png.
22
"""
23
    args_schema: Type[BaseModel] = PythonCodeInput
24
    
25
    # Sandbox configuration
26
    template: str = "code-interpreter"
27
    timeout: int = 60
28
    
29
    def _run(self, code: str) -> str:
30
        """Execute code in isolated sandbox."""
31
        sandbox = None
32
        try:
33
            # Create isolated sandbox
34
            sandbox = Sandbox.create(template=self.template)
35
            
36
            # Execute code with timeout
37
            result = sandbox.runCode(code, language="python", timeout=self.timeout)
38
            
39
            # Format output
40
            if result.exitCode == 0:
41
                output = result.stdout or "Code executed successfully (no output)"
42
                return f"✅ Execution successful:\n{output}"
43
            else:
44
                error = result.stderr or "Unknown error"
45
                return f"❌ Execution failed:\n{error}"
46
                
47
        except Exception as e:
48
            return f"❌ Sandbox error: {str(e)}"
49
            
50
        finally:
51
            if sandbox:
52
                sandbox.kill()
53
    
54
    async def _arun(self, code: str) -> str:
55
        """Async version - runs sync for simplicity."""
56
        return self._run(code)
57
 

Step 2: Build an Agent with Secure Code Execution

Now create a LangChain agent using our secure tool:

python
1
from langchain_openai import ChatOpenAI
2
from langchain.agents import AgentExecutor, create_openai_tools_agent
3
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
4
 
5
# Initialize the LLM
6
llm = ChatOpenAI(model="gpt-4o", temperature=0)
7
 
8
# Create the secure tool
9
python_tool = SecurePythonTool()
10
 
11
# Define the prompt
12
prompt = ChatPromptTemplate.from_messages([
13
    ("system", """You are a helpful AI assistant with access to a Python executor.
14
 
15
When users ask questions that require computation, data analysis, or code execution:
16
1. Write clear, well-commented Python code
17
2. Use the python_executor tool to run it
18
3. Analyze the results and provide a helpful response
19
 
20
Available libraries: pandas, numpy, matplotlib, seaborn, requests, json, csv, datetime
21
 
22
Tips:
23
- Always print() results you want to see
24
- For charts, save to /app/output.png using plt.savefig()
25
- Handle potential errors in your code
26
"""),
27
    MessagesPlaceholder(variable_name="chat_history", optional=True),
28
    ("human", "{input}"),
29
    MessagesPlaceholder(variable_name="agent_scratchpad"),
30
])
31
 
32
# Create the agent
33
agent = create_openai_tools_agent(llm, [python_tool], prompt)
34
agent_executor = AgentExecutor(
35
    agent=agent, 
36
    tools=[python_tool], 
37
    verbose=True,
38
    max_iterations=5
39
)
40
 
41
# Run it
42
response = agent_executor.invoke({
43
    "input": "Calculate the first 50 prime numbers and their sum"
44
})
45
 
46
print(response["output"])
47
 

Step 3: Add Multiple Tools

Real agents need more than just Python execution. Here's how to combine tools:

python
1
from langchain.tools import BaseTool
2
from hopx import Sandbox
3
from pydantic import BaseModel, Field
4
from typing import Type
5
 
6
class BashCommandInput(BaseModel):
7
    """Input for bash commands."""
8
    command: str = Field(description="The bash command to execute")
9
 
10
class SecureBashTool(BaseTool):
11
    """Execute bash commands in isolated sandbox."""
12
    
13
    name: str = "bash_executor"
14
    description: str = """Execute bash/shell commands securely.
15
Use for: file operations, system commands, package installation.
16
Example: ls -la, cat file.txt, pip install package
17
"""
18
    args_schema: Type[BaseModel] = BashCommandInput
19
    template: str = "code-interpreter"
20
    
21
    def _run(self, command: str) -> str:
22
        sandbox = None
23
        try:
24
            sandbox = Sandbox.create(template=self.template)
25
            result = sandbox.runCode(command, language="bash", timeout=60)
26
            
27
            if result.exitCode == 0:
28
                return f"✅ Command succeeded:\n{result.stdout}"
29
            else:
30
                return f"❌ Command failed (exit {result.exitCode}):\n{result.stderr}"
31
        except Exception as e:
32
            return f"❌ Error: {str(e)}"
33
        finally:
34
            if sandbox:
35
                sandbox.kill()
36
 
37
class FileReadInput(BaseModel):
38
    """Input for reading files."""
39
    path: str = Field(description="Path to the file to read")
40
 
41
class SecureFileReadTool(BaseTool):
42
    """Read files from the sandbox."""
43
    
44
    name: str = "read_file"
45
    description: str = "Read the contents of a file. Use after creating or downloading files."
46
    args_schema: Type[BaseModel] = FileReadInput
47
    
48
    def _run(self, path: str) -> str:
49
        sandbox = None
50
        try:
51
            sandbox = Sandbox.create(template="code-interpreter")
52
            content = sandbox.files.read(path)
53
            return f"File contents:\n{content[:10000]}"  # Truncate large files
54
        except Exception as e:
55
            return f"❌ Could not read file: {str(e)}"
56
        finally:
57
            if sandbox:
58
                sandbox.kill()
59
 
60
# Create multi-tool agent
61
tools = [
62
    SecurePythonTool(),
63
    SecureBashTool(),
64
    SecureFileReadTool(),
65
]
66
 
67
agent = create_openai_tools_agent(llm, tools, prompt)
68
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
69
 

Step 4: Persistent Sandbox for Multi-Step Tasks

For complex tasks that require multiple code executions, reuse the same sandbox:

python
1
from langchain.tools import BaseTool
2
from hopx import Sandbox
3
from typing import Optional
4
import threading
5
 
6
class PersistentSandboxManager:
7
    """Manage a persistent sandbox for multi-step execution."""
8
    
9
    _instance: Optional['PersistentSandboxManager'] = None
10
    _lock = threading.Lock()
11
    
12
    def __init__(self):
13
        self.sandbox: Optional[Sandbox] = None
14
        self.ttl = 300  # 5 minutes
15
    
16
    @classmethod
17
    def get_instance(cls) -> 'PersistentSandboxManager':
18
        if cls._instance is None:
19
            with cls._lock:
20
                if cls._instance is None:
21
                    cls._instance = cls()
22
        return cls._instance
23
    
24
    def get_sandbox(self) -> Sandbox:
25
        """Get or create sandbox."""
26
        if self.sandbox is None:
27
            self.sandbox = Sandbox.create(
28
                template="code-interpreter",
29
                ttl=self.ttl
30
            )
31
        return self.sandbox
32
    
33
    def reset(self):
34
        """Destroy and recreate sandbox."""
35
        if self.sandbox:
36
            try:
37
                self.sandbox.kill()
38
            except:
39
                pass
40
        self.sandbox = None
41
 
42
 
43
class PersistentPythonTool(BaseTool):
44
    """Python tool with persistent sandbox state."""
45
    
46
    name: str = "python"
47
    description: str = """Execute Python code with persistent state.
48
Variables and imports persist between calls.
49
Use for multi-step data analysis where you need to build on previous results.
50
"""
51
    
52
    def _run(self, code: str) -> str:
53
        manager = PersistentSandboxManager.get_instance()
54
        
55
        try:
56
            sandbox = manager.get_sandbox()
57
            result = sandbox.runCode(code, language="python", timeout=60)
58
            
59
            if result.exitCode == 0:
60
                return result.stdout or "Executed (no output)"
61
            else:
62
                return f"Error: {result.stderr}"
63
                
64
        except Exception as e:
65
            # Sandbox might have expired, reset and retry
66
            manager.reset()
67
            return f"Sandbox error (will retry with fresh sandbox): {str(e)}"
68
 

Step 5: Data Analysis Agent with File Handling

Here's a complete example for data analysis tasks:

python
1
from langchain_openai import ChatOpenAI
2
from langchain.agents import AgentExecutor, create_openai_tools_agent
3
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
4
from langchain.tools import BaseTool
5
from hopx import Sandbox
6
from pydantic import BaseModel, Field
7
from typing import Type, Optional
8
import base64
9
 
10
class DataAnalysisTool(BaseTool):
11
    """Comprehensive data analysis tool with file support."""
12
    
13
    name: str = "analyze_data"
14
    description: str = """Analyze data using Python in a secure sandbox.
15
    
16
Capabilities:
17
- Load CSV, JSON, Excel files
18
- Statistical analysis with pandas
19
- Visualizations with matplotlib/seaborn
20
- Machine learning with scikit-learn
21
 
22
Input your Python code. For charts, save to /app/chart.png.
23
Uploaded files are available at /app/data/
24
"""
25
    
26
    # Keep sandbox alive for the session
27
    sandbox: Optional[Sandbox] = None
28
    
29
    def get_sandbox(self) -> Sandbox:
30
        if self.sandbox is None:
31
            self.sandbox = Sandbox.create(
32
                template="code-interpreter",
33
                ttl=300  # 5 minute TTL
34
            )
35
        return self.sandbox
36
    
37
    def _run(self, code: str) -> str:
38
        try:
39
            sandbox = self.get_sandbox()
40
            result = sandbox.runCode(code, language="python", timeout=120)
41
            
42
            output_parts = []
43
            
44
            if result.stdout:
45
                output_parts.append(f"Output:\n{result.stdout}")
46
            
47
            if result.stderr and result.exitCode != 0:
48
                output_parts.append(f"Error:\n{result.stderr}")
49
            
50
            # Check if a chart was created
51
            try:
52
                chart_data = sandbox.files.read("/app/chart.png")
53
                output_parts.append("\n📊 Chart saved to /app/chart.png")
54
            except:
55
                pass
56
            
57
            return "\n\n".join(output_parts) or "Code executed successfully"
58
            
59
        except Exception as e:
60
            self.sandbox = None  # Reset on error
61
            return f"Execution error: {str(e)}"
62
    
63
    def upload_data(self, filename: str, content: bytes):
64
        """Upload data file to sandbox."""
65
        sandbox = self.get_sandbox()
66
        sandbox.files.write(f"/app/data/{filename}", content)
67
    
68
    def cleanup(self):
69
        """Destroy sandbox when done."""
70
        if self.sandbox:
71
            self.sandbox.kill()
72
            self.sandbox = None
73
 
74
 
75
# Create the analysis agent
76
llm = ChatOpenAI(model="gpt-4o", temperature=0)
77
data_tool = DataAnalysisTool()
78
 
79
analysis_prompt = ChatPromptTemplate.from_messages([
80
    ("system", """You are an expert data analyst assistant.
81
 
82
When users provide data or ask analytical questions:
83
1. First explore the data structure (head, info, describe)
84
2. Perform the requested analysis
85
3. Create visualizations when appropriate
86
4. Explain your findings clearly
87
 
88
Always show your work with code. Use pandas for data manipulation.
89
Save visualizations to /app/chart.png using plt.savefig('/app/chart.png', dpi=150, bbox_inches='tight')
90
"""),
91
    ("human", "{input}"),
92
    MessagesPlaceholder(variable_name="agent_scratchpad"),
93
])
94
 
95
agent = create_openai_tools_agent(llm, [data_tool], analysis_prompt)
96
data_agent = AgentExecutor(agent=agent, tools=[data_tool], verbose=True)
97
 
98
# Example: Multi-step analysis
99
response = data_agent.invoke({
100
    "input": """Create a sample sales dataset with:
101
    - 500 rows
102
    - Columns: date, product, region, quantity, revenue
103
    - Random but realistic data
104
    
105
    Then:
106
    1. Show basic statistics
107
    2. Find top 5 products by revenue
108
    3. Create a bar chart of revenue by region
109
    """
110
})
111
 
112
print(response["output"])
113
 
114
# Cleanup
115
data_tool.cleanup()
116
 

LangChain Expression Language (LCEL) Integration

For more complex chains, integrate with LCEL:

python
1
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
2
from langchain_core.output_parsers import StrOutputParser
3
from langchain_openai import ChatOpenAI
4
from hopx import Sandbox
5
 
6
def execute_code_safely(code: str) -> str:
7
    """Execute code in sandbox and return result."""
8
    sandbox = Sandbox.create(template="code-interpreter")
9
    try:
10
        result = sandbox.runCode(code, language="python", timeout=60)
11
        return result.stdout if result.exitCode == 0 else f"Error: {result.stderr}"
12
    finally:
13
        sandbox.kill()
14
 
15
# Build an LCEL chain that generates and executes code
16
generate_code_prompt = ChatPromptTemplate.from_messages([
17
    ("system", "You are a Python expert. Generate ONLY executable Python code, no explanations."),
18
    ("human", "Write Python code to: {task}")
19
])
20
 
21
explain_result_prompt = ChatPromptTemplate.from_messages([
22
    ("system", "Explain the following code execution result in plain English."),
23
    ("human", "Task: {task}\n\nCode result:\n{result}")
24
])
25
 
26
llm = ChatOpenAI(model="gpt-4o")
27
 
28
# Chain: Generate code → Execute → Explain
29
chain = (
30
    {"task": RunnablePassthrough()}
31
    | RunnablePassthrough.assign(
32
        code=generate_code_prompt | llm | StrOutputParser()
33
    )
34
    | RunnablePassthrough.assign(
35
        result=lambda x: execute_code_safely(x["code"])
36
    )
37
    | explain_result_prompt
38
    | llm
39
    | StrOutputParser()
40
)
41
 
42
# Run it
43
result = chain.invoke("Calculate the factorial of 20 and check if it's divisible by 7")
44
print(result)
45
 

Error Handling and Retry Logic

Production agents need robust error handling:

python
1
from langchain.tools import BaseTool
2
from hopx import Sandbox
3
from typing import Optional
4
import time
5
 
6
class RobustPythonTool(BaseTool):
7
    """Python execution with retry logic and error recovery."""
8
    
9
    name: str = "python"
10
    description: str = "Execute Python code with automatic error recovery"
11
    
12
    max_retries: int = 3
13
    retry_delay: float = 1.0
14
    
15
    def _run(self, code: str) -> str:
16
        last_error = None
17
        
18
        for attempt in range(self.max_retries):
19
            sandbox = None
20
            try:
21
                sandbox = Sandbox.create(template="code-interpreter")
22
                result = sandbox.runCode(code, language="python", timeout=60)
23
                
24
                if result.exitCode == 0:
25
                    return result.stdout or "Success (no output)"
26
                else:
27
                    # Code error - don't retry, return error for LLM to fix
28
                    return f"Code error:\n{result.stderr}"
29
                    
30
            except Exception as e:
31
                last_error = str(e)
32
                if attempt < self.max_retries - 1:
33
                    time.sleep(self.retry_delay)
34
                    continue
35
                    
36
            finally:
37
                if sandbox:
38
                    try:
39
                        sandbox.kill()
40
                    except:
41
                        pass
42
        
43
        return f"Sandbox failed after {self.max_retries} attempts: {last_error}"
44
 

Comparing with Built-in PythonREPL

FeatureLangChain PythonREPLHopX Secure Tool
Isolation❌ Runs on host✅ Isolated microVM
Security❌ Full system access✅ No host access
Resource Limits❌ Unlimited✅ CPU/memory limits
Network Control❌ Open✅ Configurable policies
Cleanup❌ Artifacts persist✅ VM destroyed
Speed✅ Instant✅ ~100ms startup
State Persistence✅ Session state✅ With persistent sandbox

Best Practices

1. Always Set Timeouts

python
1
result = sandbox.runCode(code, language="python", timeout=60)
2
 

2. Limit Output Size

python
1
def _run(self, code: str) -> str:
2
    result = sandbox.runCode(code, language="python", timeout=60)
3
    output = result.stdout[:10000]  # Truncate large outputs
4
    return output
5
 

3. Use Custom Templates for Specialized Tasks

python
1
# For data science tasks
2
sandbox = Sandbox.create(template="data-science")
3
 
4
# For web scraping
5
sandbox = Sandbox.create(template="web-scraper")
6
 

4. Implement Conversation Memory

python
1
from langchain.memory import ConversationBufferMemory
2
 
3
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
4
 
5
agent_executor = AgentExecutor(
6
    agent=agent,
7
    tools=tools,
8
    memory=memory,
9
    verbose=True
10
)
11
 

5. Log All Executions

python
1
import logging
2
 
3
logger = logging.getLogger("secure_python_tool")
4
 
5
def _run(self, code: str) -> str:
6
    logger.info(f"Executing code: {code[:100]}...")
7
    result = self._execute(code)
8
    logger.info(f"Result: {result[:100]}...")
9
    return result
10
 

Complete Working Example

Here's a production-ready implementation:

python
1
"""
2
Secure LangChain Agent with HopX Code Execution
3
"""
4
 
5
from langchain_openai import ChatOpenAI
6
from langchain.agents import AgentExecutor, create_openai_tools_agent
7
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
8
from langchain.tools import BaseTool
9
from langchain.memory import ConversationBufferMemory
10
from hopx import Sandbox
11
from pydantic import BaseModel, Field
12
from typing import Type, Optional
13
import os
14
 
15
# Ensure API keys are set
16
assert os.environ.get("OPENAI_API_KEY"), "Set OPENAI_API_KEY"
17
assert os.environ.get("HOPX_API_KEY"), "Set HOPX_API_KEY"
18
 
19
 
20
class CodeInput(BaseModel):
21
    code: str = Field(description="Python code to execute")
22
 
23
 
24
class SecurePythonExecutor(BaseTool):
25
    name: str = "execute_python"
26
    description: str = """Execute Python code in a secure isolated sandbox.
27
    
28
Available: pandas, numpy, matplotlib, seaborn, scikit-learn, requests.
29
For charts: plt.savefig('/app/chart.png')
30
Print results you want to see."""
31
    
32
    args_schema: Type[BaseModel] = CodeInput
33
    sandbox: Optional[Sandbox] = None
34
    
35
    def get_or_create_sandbox(self) -> Sandbox:
36
        if self.sandbox is None:
37
            self.sandbox = Sandbox.create(template="code-interpreter", ttl=300)
38
        return self.sandbox
39
    
40
    def _run(self, code: str) -> str:
41
        try:
42
            sandbox = self.get_or_create_sandbox()
43
            result = sandbox.runCode(code, language="python", timeout=60)
44
            
45
            if result.exitCode == 0:
46
                return result.stdout or "✅ Executed successfully"
47
            return f"❌ Error:\n{result.stderr}"
48
            
49
        except Exception as e:
50
            self.sandbox = None
51
            return f"❌ Sandbox error: {e}"
52
    
53
    def cleanup(self):
54
        if self.sandbox:
55
            self.sandbox.kill()
56
            self.sandbox = None
57
 
58
 
59
def create_secure_agent():
60
    """Create a LangChain agent with secure code execution."""
61
    
62
    llm = ChatOpenAI(model="gpt-4o", temperature=0)
63
    tool = SecurePythonExecutor()
64
    
65
    prompt = ChatPromptTemplate.from_messages([
66
        ("system", """You are a helpful AI assistant that can execute Python code safely.
67
 
68
When you need to compute, analyze data, or run code:
69
1. Write clear Python code
70
2. Use the execute_python tool
71
3. Explain the results
72
 
73
Be concise and helpful."""),
74
        MessagesPlaceholder(variable_name="chat_history", optional=True),
75
        ("human", "{input}"),
76
        MessagesPlaceholder(variable_name="agent_scratchpad"),
77
    ])
78
    
79
    agent = create_openai_tools_agent(llm, [tool], prompt)
80
    memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
81
    
82
    return AgentExecutor(
83
        agent=agent,
84
        tools=[tool],
85
        memory=memory,
86
        verbose=True,
87
        max_iterations=5
88
    ), tool
89
 
90
 
91
if __name__ == "__main__":
92
    agent, tool = create_secure_agent()
93
    
94
    try:
95
        # Example conversation
96
        print("\n" + "="*60)
97
        response = agent.invoke({"input": "What's 2^100 exactly?"})
98
        print(f"\nAgent: {response['output']}")
99
        
100
        print("\n" + "="*60)
101
        response = agent.invoke({
102
            "input": "Create a list of the first 10 fibonacci numbers and calculate their average"
103
        })
104
        print(f"\nAgent: {response['output']}")
105
        
106
    finally:
107
        tool.cleanup()
108
 

Conclusion

By replacing LangChain's PythonREPLTool with HopX sandboxed execution, you get:

  • Security: Code runs in isolated microVMs, not your host
  • Same API: Drop-in replacement for existing LangChain patterns
  • Production-ready: Timeouts, error handling, resource limits
  • Flexibility: Custom tools for any use case

The LLM gets the power of code execution. Your infrastructure stays safe.


Ready to secure your LangChain agents? Get started with HopX — sandboxes that spin up in 100ms.

Further Reading