Skip to content

Commit 0e1d452

Browse files
refactor: split Jupyter isolation into a separate change
Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
1 parent 21a34cc commit 0e1d452

10 files changed

Lines changed: 38 additions & 150 deletions

File tree

‎.github/workflows/lint.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ jobs:
5757
run: |
5858
pnpm run lint
5959
60-
- name: Test interpreter authentication and Jupyter transport
60+
- name: Test interpreter server
6161
run: uv run --with-requirements template/requirements-test.txt pytest template/tests
6262

6363
- name: Run formatting

‎template/README.md‎

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,5 @@
11
# Code Interpreter
22

3-
## Authentication and networking
4-
5-
The template requires envd 0.9.1 or later with the local token delivery socket.
6-
The server receives a SHA-512 token verifier over `/run/e2b-auth/token.sock` and
7-
validates `X-Access-Token` locally. It rejects requests before provisioning and
8-
after disconnecting. `/health` needs no bearer token, but remains unavailable
9-
until the auth connection is established. Tokenless sandboxes cannot execute code.
10-
11-
The systemd service creates `/etc/e2b/code-interpreter-auth` when first started.
12-
This marker contains no credentials; it makes envd wait for token delivery to be
13-
acknowledged during initialization, including resume and fork. The auth connection
14-
is also reestablished after an interpreter or envd restart.
15-
16-
Jupyter listens only on `/run/e2b-jupyter/server.sock` (mode `0600`, directory
17-
mode `0700`), including kernel WebSocket channels. Port `49999` remains the
18-
authenticated interpreter API; Jupyter no longer listens on port `8888`.
19-
20-
Deploy envd and SDK context-auth updates before rebuilding this template. Local
21-
Docker development explicitly sets `E2B_LOCAL=true`, which disables auth; do not
22-
use that setting in deployed templates.
23-
24-
Run the server tests locally with:
25-
26-
```sh
27-
uv run --with-requirements template/requirements-test.txt pytest template/tests
28-
```
29-
303
## Building the production template
314

325
To build the official `code-interpreter-v1` template from this repo, use
@@ -149,3 +122,28 @@ Inside a running sandbox you can also inspect things directly:
149122
journalctl -u jupyter -u code-interpreter
150123
systemctl status code-interpreter
151124
```
125+
126+
## Interpreter authentication
127+
128+
The template requires envd 0.9.1 or later with the local token delivery socket.
129+
The server receives a SHA-512 token verifier over `/run/e2b-auth/token.sock` and
130+
validates `X-Access-Token` locally. It rejects requests before provisioning and
131+
after disconnecting. `/health` needs no bearer token, but remains unavailable
132+
until the auth connection is established. Tokenless sandboxes cannot execute code.
133+
134+
The systemd service creates `/etc/e2b/code-interpreter-auth` when first started.
135+
This marker contains no credentials; it makes envd wait for token delivery to be
136+
acknowledged during initialization, including resume and fork. The auth connection
137+
is also reestablished after an interpreter or envd restart.
138+
139+
Deploy envd and SDK context-auth updates before rebuilding this template. Local
140+
Docker development explicitly sets `E2B_LOCAL=true`, which disables auth; do not
141+
use that setting in deployed templates. Authentication protects the interpreter
142+
API on port `49999`; it does not change Jupyter's listener or secure direct
143+
access to Jupyter. Jupyter isolation is a separate transport change.
144+
145+
Run the authentication tests locally with:
146+
147+
```sh
148+
uv run --with-requirements template/requirements-test.txt pytest template/tests/test_auth.py
149+
```

‎template/jupyter-healthcheck.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ MAX_RETRIES=50
66
RETRY_INTERVAL=0.2
77

88
for i in $(seq 1 $MAX_RETRIES); do
9-
status_code=$(curl --unix-socket /run/e2b-jupyter/server.sock -s -o /dev/null -w "%{http_code}" "http://localhost/api/status")
9+
status_code=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8888/api/status")
1010

1111
if [ "$status_code" -eq 200 ]; then
1212
echo "Jupyter Server is healthy"

‎template/jupyter_server_config.py‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
c = get_config() # noqa
44

5-
c.ServerApp.sock = "/run/e2b-jupyter/server.sock"
6-
c.ServerApp.sock_mode = "0600"
7-
85

96
# Pin the contents root directory.
107
#
@@ -25,7 +22,7 @@
2522
#
2623
# Takes precedence over allow_origin_pat.
2724
# Default: ''
28-
c.ServerApp.allow_origin = ""
25+
c.ServerApp.allow_origin = "*"
2926

3027

3128
# Allow requests where the Host header doesn't point to a local server
@@ -41,7 +38,7 @@
4138
# Local IP addresses (such as 127.0.0.1 and ::1) are allowed as local,
4239
# along with hostnames configured in local_hostnames.
4340
# Default: False
44-
c.ServerApp.allow_remote_access = False
41+
c.ServerApp.allow_remote_access = True
4542

4643
# Disable cross-site-request-forgery protection
4744
#

‎template/server/consts.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
JUPYTER_SOCKET_PATH = "/run/e2b-jupyter/server.sock"
2-
JUPYTER_BASE_URL = "http://localhost"
1+
JUPYTER_BASE_URL = "http://localhost:8888"

‎template/server/main.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from api.models.create_context import CreateContext
1515
from api.models.execution_request import ExecutionRequest
1616
from auth import AccessToken, EnvdAuthMiddleware
17-
from consts import JUPYTER_BASE_URL, JUPYTER_SOCKET_PATH
17+
from consts import JUPYTER_BASE_URL
1818
from contexts import create_context, normalize_language
1919
from envs import LOCAL
2020
from messaging import ContextWebSocket
@@ -37,9 +37,7 @@
3737
async def lifespan(app: FastAPI):
3838
global client
3939
auth_task = None if LOCAL else asyncio.create_task(access_token.subscribe())
40-
client = httpx.AsyncClient(
41-
transport=httpx.AsyncHTTPTransport(uds=JUPYTER_SOCKET_PATH), trust_env=False
42-
)
40+
client = httpx.AsyncClient()
4341

4442
try:
4543
python_context = await create_context(

‎template/server/messaging.py‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
Union,
1414
)
1515
from pydantic import StrictStr
16-
from websockets.client import WebSocketClientProtocol, unix_connect
16+
from websockets.client import WebSocketClientProtocol, connect
1717
from websockets.exceptions import (
1818
ConnectionClosedError,
1919
WebSocketException,
@@ -28,7 +28,7 @@
2828
OutputType,
2929
UnexpectedEndOfExecution,
3030
)
31-
from consts import JUPYTER_BASE_URL, JUPYTER_SOCKET_PATH
31+
from consts import JUPYTER_BASE_URL
3232
from errors import ExecutionError
3333
from envs import get_envs
3434

@@ -67,7 +67,7 @@ def __init__(self, context_id: str, session_id: str, language: str, cwd: str):
6767
self.language = language
6868
self.cwd = cwd
6969
self.context_id = context_id
70-
self.url = f"ws://localhost/api/kernels/{context_id}/channels"
70+
self.url = f"ws://localhost:8888/api/kernels/{context_id}/channels"
7171
self.session_id = session_id
7272
self._executions: Dict[str, Execution] = {}
7373
self._lock = asyncio.Lock()
@@ -87,9 +87,8 @@ async def connect(self):
8787
ws_logger = logger.getChild("websockets.client")
8888
ws_logger.setLevel(logging.ERROR)
8989

90-
self._ws = await unix_connect(
91-
JUPYTER_SOCKET_PATH,
92-
uri=self.url,
90+
self._ws = await connect(
91+
self.url,
9392
ping_timeout=PING_TIMEOUT,
9493
max_size=None,
9594
max_queue=None,
@@ -105,10 +104,7 @@ async def connect(self):
105104
async def interrupt(self):
106105
"""Interrupt the current kernel execution via the Jupyter REST API."""
107106
try:
108-
async with httpx.AsyncClient(
109-
transport=httpx.AsyncHTTPTransport(uds=JUPYTER_SOCKET_PATH),
110-
trust_env=False,
111-
) as client:
107+
async with httpx.AsyncClient() as client:
112108
response = await client.post(
113109
f"{JUPYTER_BASE_URL}/api/kernels/{self.context_id}/interrupt"
114110
)

‎template/start-up.sh‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
#!/bin/bash
22

3-
install -d -m 0700 /run/e2b-jupyter
4-
53
function start_code_interpreter() {
64
/root/.jupyter/jupyter-healthcheck.sh
75
if [ $? -ne 0 ]; then

‎template/systemd/jupyter.service‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ StartLimitBurst=0
66

77
[Service]
88
Type=simple
9-
RuntimeDirectory=e2b-jupyter
10-
RuntimeDirectoryMode=0700
119
Environment=MATPLOTLIBRC=/root/.config/matplotlib/.matplotlibrc
1210
ExecStart=/usr/local/bin/jupyter server --IdentityProvider.token=""
1311
ExecStartPost=-/usr/bin/systemctl reset-failed code-interpreter

‎template/tests/test_jupyter_socket.py‎

Lines changed: 0 additions & 96 deletions
This file was deleted.

0 commit comments

Comments
 (0)