Skip to content

Commit 59ec6f4

Browse files
authored
Added secure traffic access (#180)
* secure traffic access * changed type syntax * bugbot suggestions * update test setup conf * format * cursor suggestion * increase default timeout * sandbox not callable * nit
1 parent 6acf33a commit 59ec6f4

File tree

13 files changed

+404
-113
lines changed

13 files changed

+404
-113
lines changed

.changeset/fast-dogs-brush.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@e2b/code-interpreter-python': patch
3+
'@e2b/code-interpreter': patch
4+
---
5+
6+
secure traffic access

js/src/sandbox.ts

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ export class Sandbox extends BaseSandbox {
223223
const headers: Record<string, string> = {
224224
'Content-Type': 'application/json',
225225
}
226+
227+
if (this.trafficAccessToken) {
228+
headers['E2B-Traffic-Access-Token'] = this.trafficAccessToken
229+
}
226230
if (this.envdAccessToken) {
227231
headers['X-Access-Token'] = this.envdAccessToken
228232
}
@@ -296,12 +300,17 @@ export class Sandbox extends BaseSandbox {
296300
*/
297301
async createCodeContext(opts?: CreateCodeContextOpts): Promise<Context> {
298302
try {
303+
const headers: Record<string, string> = {
304+
'Content-Type': 'application/json',
305+
}
306+
307+
if (this.trafficAccessToken) {
308+
headers['E2B-Traffic-Access-Token'] = this.trafficAccessToken
309+
}
310+
299311
const res = await fetch(`${this.jupyterUrl}/contexts`, {
300312
method: 'POST',
301-
headers: {
302-
'Content-Type': 'application/json',
303-
...this.connectionConfig.headers,
304-
},
313+
headers,
305314
body: JSON.stringify({
306315
language: opts?.language,
307316
cwd: opts?.cwd,
@@ -331,12 +340,17 @@ export class Sandbox extends BaseSandbox {
331340
async removeCodeContext(context: Context | string): Promise<void> {
332341
try {
333342
const id = typeof context === 'string' ? context : context.id
343+
const headers: Record<string, string> = {
344+
'Content-Type': 'application/json',
345+
}
346+
347+
if (this.trafficAccessToken) {
348+
headers['E2B-Traffic-Access-Token'] = this.trafficAccessToken
349+
}
350+
334351
const res = await fetch(`${this.jupyterUrl}/contexts/${id}`, {
335352
method: 'DELETE',
336-
headers: {
337-
'Content-Type': 'application/json',
338-
...this.connectionConfig.headers,
339-
},
353+
headers,
340354
keepalive: true,
341355
signal: this.connectionConfig.getSignal(
342356
this.connectionConfig.requestTimeoutMs
@@ -359,12 +373,17 @@ export class Sandbox extends BaseSandbox {
359373
*/
360374
async listCodeContexts(): Promise<Context[]> {
361375
try {
376+
const headers: Record<string, string> = {
377+
'Content-Type': 'application/json',
378+
}
379+
380+
if (this.trafficAccessToken) {
381+
headers['E2B-Traffic-Access-Token'] = this.trafficAccessToken
382+
}
383+
362384
const res = await fetch(`${this.jupyterUrl}/contexts`, {
363385
method: 'GET',
364-
headers: {
365-
'Content-Type': 'application/json',
366-
...this.connectionConfig.headers,
367-
},
386+
headers,
368387
keepalive: true,
369388
signal: this.connectionConfig.getSignal(
370389
this.connectionConfig.requestTimeoutMs
@@ -392,12 +411,17 @@ export class Sandbox extends BaseSandbox {
392411
async restartCodeContext(context: Context | string): Promise<void> {
393412
try {
394413
const id = typeof context === 'string' ? context : context.id
414+
const headers: Record<string, string> = {
415+
'Content-Type': 'application/json',
416+
}
417+
418+
if (this.trafficAccessToken) {
419+
headers['E2B-Traffic-Access-Token'] = this.trafficAccessToken
420+
}
421+
395422
const res = await fetch(`${this.jupyterUrl}/contexts/${id}/restart`, {
396423
method: 'POST',
397-
headers: {
398-
'Content-Type': 'application/json',
399-
...this.connectionConfig.headers,
400-
},
424+
headers,
401425
keepalive: true,
402426
signal: this.connectionConfig.getSignal(
403427
this.connectionConfig.requestTimeoutMs

js/tests/basic.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { expect } from 'vitest'
2-
3-
import { sandboxTest } from './setup'
2+
import { isDebug, sandboxTest, secureSandboxTest } from './setup'
43

54
sandboxTest('basic', async ({ sandbox }) => {
65
const result = await sandbox.runCode('x =1; x')
76

87
expect(result.text).toEqual('1')
98
})
9+
10+
secureSandboxTest.skipIf(isDebug)('secure access', async ({ sandbox }) => {
11+
const result = await sandbox.runCode('x =1; x')
12+
13+
expect(result.text).toEqual('1')
14+
})

js/tests/contexts.test.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from 'vitest'
22

3-
import { sandboxTest } from './setup'
3+
import { isDebug, sandboxTest, secureSandboxTest } from './setup'
44

55
sandboxTest('create context with no options', async ({ sandbox }) => {
66
const context = await sandbox.createCodeContext()
@@ -61,3 +61,65 @@ sandboxTest('restart context', async ({ sandbox }) => {
6161
expect(execution.error?.name).toBe('NameError')
6262
expect(execution.error?.value).toBe("name 'x' is not defined")
6363
})
64+
65+
secureSandboxTest.skipIf(isDebug)(
66+
'create context (secure traffic)',
67+
async ({ sandbox }) => {
68+
const context = await sandbox.createCodeContext()
69+
70+
const contexts = await sandbox.listCodeContexts()
71+
const lastContext = contexts[contexts.length - 1]
72+
73+
expect(lastContext.id).toBe(context.id)
74+
expect(lastContext.language).toBe(context.language)
75+
expect(lastContext.cwd).toBe(context.cwd)
76+
}
77+
)
78+
79+
secureSandboxTest.skipIf(isDebug)(
80+
'remove context (secure traffic)',
81+
async ({ sandbox }) => {
82+
const context = await sandbox.createCodeContext()
83+
84+
await sandbox.removeCodeContext(context.id)
85+
const contexts = await sandbox.listCodeContexts()
86+
87+
expect(contexts.map((context) => context.id)).not.toContain(context.id)
88+
89+
await sandbox.kill()
90+
}
91+
)
92+
93+
secureSandboxTest.skipIf(isDebug)(
94+
'list contexts (secure traffic)',
95+
async ({ sandbox }) => {
96+
const contexts = await sandbox.listCodeContexts()
97+
98+
// default contexts should include python and javascript
99+
expect(contexts.map((context) => context.language)).toContain('python')
100+
expect(contexts.map((context) => context.language)).toContain('javascript')
101+
102+
await sandbox.kill()
103+
}
104+
)
105+
106+
secureSandboxTest.skipIf(isDebug)(
107+
'restart context (secure traffic)',
108+
async ({ sandbox }) => {
109+
const context = await sandbox.createCodeContext()
110+
111+
// set a variable in the context
112+
await sandbox.runCode('x = 1', { context: context })
113+
114+
// restart the context
115+
await sandbox.restartCodeContext(context.id)
116+
117+
// check that the variable no longer exists
118+
const execution = await sandbox.runCode('x', { context: context })
119+
120+
// check for an NameError with message "name 'x' is not defined"
121+
expect(execution.error).toBeDefined()
122+
expect(execution.error?.name).toBe('NameError')
123+
expect(execution.error?.value).toBe("name 'x' is not defined")
124+
}
125+
)

js/tests/setup.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
1-
import { Sandbox } from '../src'
21
import { test as base } from 'vitest'
3-
4-
const timeoutMs = 60_000
5-
6-
const template = process.env.E2B_TESTS_TEMPLATE || 'code-interpreter-v1'
2+
import { Sandbox, SandboxOpts } from '../src'
73

84
interface SandboxFixture {
95
sandbox: Sandbox
106
template: string
7+
sandboxTestId: string
8+
sandboxOpts: Partial<SandboxOpts>
119
}
1210

11+
const template = process.env.E2B_TESTS_TEMPLATE || 'code-interpreter-v1'
12+
1313
export const sandboxTest = base.extend<SandboxFixture>({
14-
sandbox: [
14+
template,
15+
sandboxTestId: [
1516
// eslint-disable-next-line no-empty-pattern
1617
async ({}, use) => {
18+
const id = `test-${generateRandomString()}`
19+
await use(id)
20+
},
21+
{ auto: true },
22+
],
23+
sandboxOpts: {},
24+
sandbox: [
25+
async ({ sandboxTestId, sandboxOpts }, use) => {
1726
const sandbox = await Sandbox.create(template, {
18-
timeoutMs,
27+
metadata: { sandboxTestId },
28+
...sandboxOpts,
1929
})
2030
try {
2131
await use(sandbox)
@@ -31,13 +41,30 @@ export const sandboxTest = base.extend<SandboxFixture>({
3141
}
3242
}
3343
},
34-
{ auto: true },
44+
{ auto: false },
3545
],
36-
template,
3746
})
3847

3948
export const isDebug = process.env.E2B_DEBUG !== undefined
49+
export const isIntegrationTest = process.env.E2B_INTEGRATION_TEST !== undefined
50+
51+
export const secureSandboxTest = sandboxTest.extend({
52+
sandboxOpts: {
53+
secure: true,
54+
network: {
55+
allowPublicTraffic: false,
56+
},
57+
},
58+
})
59+
60+
function generateRandomString(length: number = 8): string {
61+
return Math.random()
62+
.toString(36)
63+
.substring(2, length + 2)
64+
}
4065

4166
export async function wait(ms: number) {
4267
return new Promise((resolve) => setTimeout(resolve, ms))
4368
}
69+
70+
export { template }

pnpm-lock.yaml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/e2b_code_interpreter/code_interpreter_async.py

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,15 @@ async def run_code(
190190
timeout = None if timeout == 0 else (timeout or DEFAULT_TIMEOUT)
191191
request_timeout = request_timeout or self.connection_config.request_timeout
192192
context_id = context.id if context else None
193-
194-
headers: Dict[str, str] = {}
195-
if self._envd_access_token:
196-
headers = {"X-Access-Token": self._envd_access_token}
197-
198193
try:
194+
headers = {
195+
"Content-Type": "application/json",
196+
}
197+
if self._envd_access_token:
198+
headers["X-Access-Token"] = self._envd_access_token
199+
if self.traffic_access_token:
200+
headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
201+
199202
async with self._client.stream(
200203
"POST",
201204
f"{self._jupyter_url}/execute",
@@ -253,11 +256,13 @@ async def create_code_context(
253256
if cwd:
254257
data["cwd"] = cwd
255258

256-
headers: Dict[str, str] = {}
257-
if self._envd_access_token:
258-
headers = {"X-Access-Token": self._envd_access_token}
259-
260259
try:
260+
headers = {
261+
"Content-Type": "application/json",
262+
}
263+
if self.traffic_access_token:
264+
headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
265+
261266
response = await self._client.post(
262267
f"{self._jupyter_url}/contexts",
263268
headers=headers,
@@ -287,11 +292,13 @@ async def remove_code_context(
287292
"""
288293
context_id = context.id if isinstance(context, Context) else context
289294

290-
headers: Dict[str, str] = {}
291-
if self._envd_access_token:
292-
headers = {"X-Access-Token": self._envd_access_token}
293-
294295
try:
296+
headers = {
297+
"Content-Type": "application/json",
298+
}
299+
if self.traffic_access_token:
300+
headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
301+
295302
response = await self._client.delete(
296303
f"{self._jupyter_url}/contexts/{context_id}",
297304
headers=headers,
@@ -310,11 +317,13 @@ async def list_code_contexts(self) -> List[Context]:
310317
311318
:return: List of contexts.
312319
"""
313-
headers: Dict[str, str] = {}
314-
if self._envd_access_token:
315-
headers = {"X-Access-Token": self._envd_access_token}
316-
317320
try:
321+
headers = {
322+
"Content-Type": "application/json",
323+
}
324+
if self.traffic_access_token:
325+
headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
326+
318327
response = await self._client.get(
319328
f"{self._jupyter_url}/contexts",
320329
headers=headers,
@@ -342,12 +351,13 @@ async def restart_code_context(
342351
:return: None
343352
"""
344353
context_id = context.id if isinstance(context, Context) else context
345-
346-
headers: Dict[str, str] = {}
347-
if self._envd_access_token:
348-
headers = {"X-Access-Token": self._envd_access_token}
349-
350354
try:
355+
headers = {
356+
"Content-Type": "application/json",
357+
}
358+
if self.traffic_access_token:
359+
headers["E2B-Traffic-Access-Token"] = self.traffic_access_token
360+
351361
response = await self._client.post(
352362
f"{self._jupyter_url}/contexts/{context_id}/restart",
353363
headers=headers,

0 commit comments

Comments
 (0)