# Security
This guide covers the security sandbox that protects the Erlang VM when running embedded Python code.
## Overview
When Python runs embedded inside the Erlang VM (BEAM), certain operations must be blocked because they would corrupt or destabilize the runtime. The `erlang_python` library automatically installs a sandbox that blocks these dangerous operations.
### Why Fork/Exec Are Blocked
The Erlang VM is a sophisticated runtime with:
- Multiple scheduler threads managing lightweight processes
- Complex memory management and garbage collection
- Intricate internal state for message passing and I/O
When `fork()` is called, the child process gets a copy of the parent's memory but only the calling thread. This leaves the child with corrupted state - scheduler threads are missing, locks are in inconsistent states, and internal data structures are broken. The child process will crash or behave unpredictably.
Similarly, `exec()` replaces the current process image entirely, terminating the Erlang VM.
## Audit Hook Mechanism
The sandbox uses Python's audit hook system (PEP 578) to intercept dangerous operations at a low level, before they can execute. The hook is installed automatically when `erlang_python` starts and cannot be removed once installed.
This provides defense-in-depth - even if Python code tries to import `os` or `subprocess` directly, the operations are blocked.
## Blocked Operations
| Operation | Module | Reason |
|-----------|--------|--------|
| `fork()` | `os` | Corrupts Erlang VM state |
| `forkpty()` | `os` | Uses fork internally |
| `system()` | `os` | Executes via shell (uses fork) |
| `popen()` | `os` | Opens pipe to subprocess (uses fork) |
| `exec*()` | `os` | Replaces process image |
| `spawn*()` | `os` | Creates subprocess (uses fork) |
| `posix_spawn*()` | `os` | POSIX subprocess creation |
| `Popen` | `subprocess` | Creates subprocess (uses fork) |
| `run()` | `subprocess` | Wrapper around Popen |
| `call()` | `subprocess` | Wrapper around Popen |
## Error Messages
When blocked operations are attempted, you'll see:
```python
>>> import subprocess
>>> subprocess.run(['ls'])
RuntimeError: subprocess.Popen is blocked in Erlang VM context.
fork()/exec() would corrupt the Erlang runtime.
Use Erlang ports (open_port/2) for subprocess management.
```
```python
>>> import os
>>> os.fork()
RuntimeError: os.fork is blocked in Erlang VM context.
fork()/exec() would corrupt the Erlang runtime.
Use Erlang ports (open_port/2) for subprocess management.
```
## Recommended Alternatives
Instead of using Python's subprocess facilities, use Erlang's port mechanism which properly manages external processes.
### From Erlang: Running Shell Commands
```erlang
%% Run a command and capture output
run_command(Cmd) ->
Port = open_port({spawn, Cmd}, [exit_status, binary, stderr_to_stdout]),
collect_output(Port, []).
collect_output(Port, Acc) ->
receive
{Port, {data, Data}} ->
collect_output(Port, [Data | Acc]);
{Port, {exit_status, Status}} ->
{Status, iolist_to_binary(lists:reverse(Acc))}
after 30000 ->
port_close(Port),
{error, timeout}
end.
%% Usage
{0, Output} = run_command("ls -la").
```
### From Python: Calling Erlang to Run Commands
Register an Erlang function that runs commands:
```erlang
%% In Erlang
py:register_function(run_shell, fun([Cmd]) ->
Port = open_port({spawn, binary_to_list(Cmd)},
[exit_status, binary, stderr_to_stdout]),
collect_output(Port, [])
end).
```
```python
# In Python
from erlang import run_shell
# This calls through Erlang, which properly manages the subprocess
result = run_shell("ls -la")
```
### Using Erlang Ports for Long-Running Processes
```erlang
%% Start a long-running process
{ok, Port} = py:call('__main__', start_worker_via_erlang, []),
%% The Python code registers a function:
py:register_function(start_worker_via_erlang, fun([]) ->
Port = open_port({spawn, "python3 worker.py"},
[binary, {line, 1024}, use_stdio]),
Port % Return port reference to Python
end).
```
### Alternative: Use `erlang.send()` for Communication
For Python code that needs to trigger external processes, use message passing to coordinate with Erlang supervisors:
```python
import erlang
# Send a request to an Erlang process that manages subprocesses
erlang.send(supervisor_pid, ('spawn_worker', worker_args))
```
## Checking Sandbox Status
From Python, you can check if the sandbox is active:
```python
from _erlang_impl._sandbox import is_sandboxed
if is_sandboxed():
print("Running inside Erlang VM - subprocess operations blocked")
```
## Signal Handling Note
Signal handling is also not supported in the Erlang event loop. The `ErlangEventLoop` raises `NotImplementedError` for `add_signal_handler()` and `remove_signal_handler()`. Signal handling should be done at the Erlang VM level using Erlang's signal handling facilities.
## See Also
- [Getting Started](getting-started.md) - Basic usage guide
- [Asyncio](asyncio.md) - Erlang-native asyncio event loop
- [Threading](threading.md) - Python threading support