Skip to content

Commit 6355ccf

Browse files
committed
Rewrite egress header docs to focus on usage and value
1 parent dc26200 commit 6355ccf

1 file changed

Lines changed: 43 additions & 260 deletions

File tree

docs/template/examples/egress-header.mdx

Lines changed: 43 additions & 260 deletions
Original file line numberDiff line numberDiff line change
@@ -3,303 +3,86 @@ title: "Egress header"
33
description: "Sandbox with automatic HTTP header injection on outbound requests"
44
---
55

6-
This template runs a transparent [mitmproxy](https://mitmproxy.org/) inside the sandbox that automatically injects a custom `X-Sandbox-ID` header into every outbound HTTP and HTTPS request. This is useful for tracking, auditing, or routing requests that originate from specific sandboxes.
6+
This template automatically tags every outbound HTTP and HTTPS request from the sandbox with a custom header containing the sandbox ID. No code changes needed — any request your agent makes to external APIs will carry the `X-Sandbox-ID` header transparently.
77

8-
The template includes:
9-
- **[mitmproxy](https://mitmproxy.org/)** running in transparent mode to intercept outbound traffic
10-
- **iptables** rules that redirect HTTP/HTTPS traffic through the proxy
11-
- **Automatic CA certificate trust** so HTTPS requests work without certificate errors in Python, Node.js, and OpenSSL
12-
- **Socket marking** via an `LD_PRELOAD` library to prevent proxy redirect loops
8+
This enables you to:
9+
- **Track API usage per sandbox** — correlate external API calls back to the sandbox that made them
10+
- **Route traffic** — use the header in your API gateway or proxy to apply per-sandbox rate limits, logging, or access policies
11+
- **Audit agent behavior** — see exactly which external services each sandbox is calling
1312

14-
## Template definition
13+
## How it works
1514

16-
The template installs mitmproxy with its dependencies, copies the proxy addon and startup scripts, and compiles a small C library used to prevent redirect loops. The start command launches the proxy and waits for it to signal readiness.
15+
A transparent proxy runs inside the sandbox and intercepts all outbound HTTP/HTTPS traffic. It injects an `X-Sandbox-ID` header into every request before forwarding it to the destination. The sandbox's CA certificate is automatically trusted so HTTPS works without any configuration in Python, Node.js, or any other runtime.
1716

18-
<CodeGroup>
19-
20-
```typescript JavaScript & TypeScript expandable
21-
// template.ts
22-
import { Template, waitForFile } from 'e2b'
23-
24-
export const template = Template()
25-
.fromBaseImage()
26-
.aptInstall([
27-
'python3',
28-
'python3-pip',
29-
'iptables',
30-
'ca-certificates',
31-
'curl',
32-
'gcc',
33-
'libc-dev',
34-
])
35-
.runCmd('pip3 install mitmproxy --break-system-packages', { user: 'root' })
36-
.runCmd('mkdir -p /opt/mitmproxy', { user: 'root' })
37-
.copy('files/mitmproxy/add_header.py', '/opt/mitmproxy/add_header.py')
38-
.copy('files/mitmproxy/sockmark.c', '/opt/mitmproxy/sockmark.c')
39-
.copy('files/scripts/start-proxy.sh', '/opt/mitmproxy/start-proxy.sh')
40-
.runCmd('gcc -shared -fPIC -o /opt/mitmproxy/sockmark.so /opt/mitmproxy/sockmark.c -ldl', { user: 'root' })
41-
.runCmd('chmod +x /opt/mitmproxy/start-proxy.sh', { user: 'root' })
42-
.setStartCmd('sudo /opt/mitmproxy/start-proxy.sh', waitForFile('/tmp/proxy-ready'))
43-
```
44-
45-
```python Python expandable
46-
# template.py
47-
from e2b import Template, wait_for_file
48-
49-
template = (
50-
Template()
51-
.from_base_image()
52-
.apt_install([
53-
"python3",
54-
"python3-pip",
55-
"iptables",
56-
"ca-certificates",
57-
"curl",
58-
"gcc",
59-
"libc-dev",
60-
])
61-
.run_cmd("pip3 install mitmproxy --break-system-packages", user="root")
62-
.run_cmd("mkdir -p /opt/mitmproxy", user="root")
63-
.copy("files/mitmproxy/add_header.py", "/opt/mitmproxy/add_header.py")
64-
.copy("files/mitmproxy/sockmark.c", "/opt/mitmproxy/sockmark.c")
65-
.copy("files/scripts/start-proxy.sh", "/opt/mitmproxy/start-proxy.sh")
66-
.run_cmd("gcc -shared -fPIC -o /opt/mitmproxy/sockmark.so /opt/mitmproxy/sockmark.c -ldl", user="root")
67-
.run_cmd("chmod +x /opt/mitmproxy/start-proxy.sh", user="root")
68-
.set_start_cmd("sudo /opt/mitmproxy/start-proxy.sh", wait_for_file("/tmp/proxy-ready"))
69-
)
70-
```
71-
72-
</CodeGroup>
73-
74-
## Proxy addon
75-
76-
The mitmproxy addon reads the sandbox ID from `/run/e2b/.E2B_SANDBOX_ID` (written by E2B at sandbox boot) and injects it as a header into every outbound request. The header name defaults to `X-Sandbox-ID` but can be configured via the `HEADER_NAME` environment variable.
77-
78-
```python add_header.py
79-
import os
80-
from mitmproxy import http
81-
82-
HEADER_NAME = os.environ.get("HEADER_NAME", "X-Sandbox-ID")
83-
SANDBOX_ID_FILE = "/run/e2b/.E2B_SANDBOX_ID"
84-
_sandbox_id: str | None = None
85-
86-
87-
def request(flow: http.HTTPFlow) -> None:
88-
global _sandbox_id
89-
90-
if _sandbox_id is not None:
91-
flow.request.headers[HEADER_NAME] = _sandbox_id
92-
return
93-
94-
# Try reading the sandbox ID file (written by envd after sandbox boot).
95-
sid = ""
96-
try:
97-
with open(SANDBOX_ID_FILE) as f:
98-
sid = f.read().strip()
99-
except Exception:
100-
pass
101-
102-
# Fall back to E2B_SANDBOX_ID env var.
103-
if not sid or sid == "unknown":
104-
sid = os.environ.get("E2B_SANDBOX_ID", "").strip()
105-
106-
# Cache only once we have a real value; otherwise retry next request.
107-
if sid and sid != "unknown":
108-
_sandbox_id = sid
109-
else:
110-
sid = "unknown"
111-
112-
flow.request.headers[HEADER_NAME] = sid
113-
```
114-
115-
## Socket marking library
116-
117-
To prevent redirect loops, mitmproxy's own outbound connections need to bypass the iptables redirect rules. This small C library is loaded via `LD_PRELOAD` and marks all sockets mitmproxy creates with `SO_MARK=1`. The iptables rules then skip any packets with that mark.
118-
119-
```c sockmark.c
120-
#define _GNU_SOURCE
121-
#include <sys/socket.h>
122-
#include <dlfcn.h>
123-
124-
int socket(int domain, int type, int protocol) {
125-
int (*real_socket)(int, int, int) = dlsym(RTLD_NEXT, "socket");
126-
int fd = real_socket(domain, type, protocol);
127-
if (fd >= 0 && (domain == AF_INET || domain == AF_INET6)) {
128-
int mark = 1;
129-
setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(mark));
130-
}
131-
return fd;
132-
}
133-
```
134-
135-
## Startup script
136-
137-
The startup script runs when the sandbox starts. It fetches the sandbox ID from the instance metadata service, generates and installs the mitmproxy CA certificate, configures iptables to redirect HTTP (port 80) and HTTPS (port 443) traffic through the proxy, and launches mitmproxy in transparent mode.
138-
139-
```bash start-proxy.sh expandable
140-
#!/usr/bin/env bash
141-
set -euo pipefail
142-
143-
PROXY_PORT="${PROXY_PORT:-8080}"
144-
ADDON_PATH="/opt/mitmproxy/add_header.py"
145-
SOCKMARK_LIB="/opt/mitmproxy/sockmark.so"
146-
CONFDIR="/root/.mitmproxy"
147-
148-
echo "=== Egress Header Proxy Setup ==="
149-
echo "Proxy port: ${PROXY_PORT}"
150-
151-
# --- 0. Resolve sandbox ID from MMDS (Firecracker metadata service) ---
152-
MMDS_TOKEN=$(curl -sf -X PUT "http://169.254.169.254/latest/api/token" \
153-
-H "X-metadata-token-ttl-seconds: 21600" 2>/dev/null || echo "")
154-
if [ -n "${MMDS_TOKEN}" ]; then
155-
SANDBOX_ID=$(curl -sf -H "X-metadata-token: ${MMDS_TOKEN}" \
156-
"http://169.254.169.254/instanceID" 2>/dev/null || echo "unknown")
157-
else
158-
SANDBOX_ID="${E2B_SANDBOX_ID:-unknown}"
159-
fi
160-
echo "Sandbox ID: ${SANDBOX_ID}"
161-
162-
# --- 1. Generate mitmproxy CA certificate ---
163-
echo "Generating mitmproxy CA certificate..."
164-
mitmdump --mode transparent --listen-port "${PROXY_PORT}" \
165-
--set confdir="${CONFDIR}" \
166-
-s "${ADDON_PATH}" &
167-
GEN_PID=$!
168-
169-
for i in $(seq 1 30); do
170-
[ -f "${CONFDIR}/mitmproxy-ca-cert.pem" ] && break
171-
sleep 0.5
172-
done
173-
174-
kill "${GEN_PID}" 2>/dev/null || true
175-
wait "${GEN_PID}" 2>/dev/null || true
176-
177-
# --- 2. Install CA certificate system-wide ---
178-
echo "Installing CA certificate..."
179-
cp "${CONFDIR}/mitmproxy-ca-cert.pem" /usr/local/share/ca-certificates/mitmproxy.crt
180-
update-ca-certificates 2>/dev/null
181-
182-
# Set CA env vars for Python, Node.js, and OpenSSL
183-
cat > /etc/profile.d/mitmproxy-ca.sh << 'ENVEOF'
184-
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
185-
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
186-
export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
187-
ENVEOF
188-
189-
cat >> /etc/environment << 'ENVEOF'
190-
REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
191-
SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
192-
NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
193-
ENVEOF
17+
Your code doesn't need to know the proxy exists — it works at the network level.
19418

195-
# --- 3. Set up iptables rules ---
196-
# Skip packets marked with 1 (mitmproxy's own outbound traffic)
197-
echo "Configuring iptables..."
198-
iptables -t nat -A OUTPUT -m mark --mark 1 -j RETURN
199-
iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j RETURN
200-
iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port "${PROXY_PORT}"
201-
iptables -t nat -A OUTPUT -p tcp --dport 443 -j REDIRECT --to-port "${PROXY_PORT}"
19+
## Usage
20220

203-
# --- 4. Start mitmproxy with LD_PRELOAD for loop prevention ---
204-
echo "Starting mitmproxy in transparent mode..."
205-
LD_PRELOAD="${SOCKMARK_LIB}" \
206-
E2B_SANDBOX_ID="${SANDBOX_ID}" \
207-
HEADER_NAME="${HEADER_NAME:-X-Sandbox-ID}" \
208-
mitmdump --mode transparent --listen-port "${PROXY_PORT}" \
209-
-s "${ADDON_PATH}" --set confdir="${CONFDIR}" \
210-
--quiet &
211-
MITM_PID=$!
212-
213-
# --- 5. Wait for proxy to be ready ---
214-
sleep 2
215-
if kill -0 "${MITM_PID}" 2>/dev/null; then
216-
echo "Proxy is ready (PID: ${MITM_PID})"
217-
else
218-
echo "WARNING: mitmproxy may not have started correctly"
219-
fi
220-
touch /tmp/proxy-ready
221-
222-
echo "=== Egress Header Proxy Running ==="
223-
wait "${MITM_PID}"
224-
```
225-
226-
## Build the template
227-
228-
Build the template with 2 CPUs and 2 GB of RAM. The build compiles the C socket marking library and installs mitmproxy, which may take a few minutes.
21+
Create a sandbox from the template. Every outbound request automatically includes the `X-Sandbox-ID` header.
22922

23023
<CodeGroup>
23124

23225
```typescript JavaScript & TypeScript
233-
// build.ts
234-
import { Template, defaultBuildLogger } from 'e2b'
235-
import { template as egressTemplate } from './template'
236-
237-
await Template.build(egressTemplate, 'sandbox-egress-header', {
238-
cpuCount: 2,
239-
memoryMB: 2048,
240-
onBuildLogs: defaultBuildLogger(),
241-
})
242-
```
243-
244-
```python Python
245-
# build.py
246-
from e2b import Template, default_build_logger
247-
from .template import template as egressTemplate
248-
249-
Template.build(egressTemplate, "sandbox-egress-header",
250-
cpu_count=2,
251-
memory_mb=2048,
252-
on_build_logs=default_build_logger(),
253-
)
254-
```
255-
256-
</CodeGroup>
257-
258-
## Verify the header injection
259-
260-
Create a sandbox from the template and make a request to [httpbin.org](https://httpbin.org) to confirm the `X-Sandbox-ID` header is being injected into outbound requests.
261-
262-
<CodeGroup>
263-
264-
```typescript JavaScript & TypeScript
265-
// sandbox.ts
26626
import { Sandbox } from 'e2b'
26727

26828
const sbx = await Sandbox.create('sandbox-egress-header', { timeoutMs: 120_000 })
26929

270-
const result = await sbx.commands.run('curl -s https://httpbin.org/headers', {
271-
envs: { SSL_CERT_FILE: '/etc/ssl/certs/ca-certificates.crt' },
272-
})
30+
// Any outbound request from the sandbox will include X-Sandbox-ID
31+
const result = await sbx.commands.run('curl -s https://httpbin.org/headers')
27332
console.log(result.stdout)
27433

27534
await sbx.kill()
27635
```
27736

27837
```python Python
279-
# sandbox.py
28038
from e2b import Sandbox
28139

28240
sbx = Sandbox.create("sandbox-egress-header", timeout=120)
28341

284-
result = sbx.commands.run(
285-
"curl -s https://httpbin.org/headers",
286-
envs={"SSL_CERT_FILE": "/etc/ssl/certs/ca-certificates.crt"},
287-
)
42+
# Any outbound request from the sandbox will include X-Sandbox-ID
43+
result = sbx.commands.run("curl -s https://httpbin.org/headers")
28844
print(result.stdout)
28945

29046
sbx.kill()
29147
```
29248

29349
</CodeGroup>
29450

295-
The output includes the injected header with the sandbox's unique ID:
51+
The response from httpbin.org shows the injected header:
29652

29753
```json
29854
{
29955
"headers": {
30056
"Accept": "*/*",
30157
"Host": "httpbin.org",
302-
"X-Sandbox-Id": "sandbox-id-here"
58+
"X-Sandbox-Id": "i3sb4m2knepruowf004y7"
30359
}
30460
}
30561
```
62+
63+
## Configuration
64+
65+
The header name defaults to `X-Sandbox-ID`. You can change it by setting the `HEADER_NAME` environment variable when creating the sandbox.
66+
67+
<CodeGroup>
68+
69+
```typescript JavaScript & TypeScript
70+
const sbx = await Sandbox.create('sandbox-egress-header', {
71+
timeoutMs: 120_000,
72+
envs: { HEADER_NAME: 'X-Agent-ID' },
73+
})
74+
```
75+
76+
```python Python
77+
sbx = Sandbox.create(
78+
"sandbox-egress-header",
79+
timeout=120,
80+
envs={"HEADER_NAME": "X-Agent-ID"},
81+
)
82+
```
83+
84+
</CodeGroup>
85+
86+
## Source code
87+
88+
The full template source including the proxy addon, startup script, and build configuration is available on [GitHub](https://github.com/beran-t/customer-starter-templates/tree/main/templates/sandbox-egress-header).

0 commit comments

Comments
 (0)