Files
2026-07-13 12:47:58 +08:00

360 lines
10 KiB
TypeScript

import { assert, expect, describe } from 'vitest'
import { CommandExitError } from '../../src'
import { sandboxTest, isDebug } from '../setup.js'
describe('allow only 1.1.1.1', () => {
sandboxTest.scoped({
sandboxOpts: {
network: {
denyOut: ({ allTraffic }) => [allTraffic],
allowOut: ['1.1.1.1'],
},
},
})
sandboxTest.skipIf(isDebug)(
'allow specific IP with deny all traffic',
async ({ sandbox }) => {
// Test that allowed IP works
const result = await sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://1.1.1.1"
)
assert.equal(result.exitCode, 0)
assert.equal(result.stdout.trim(), '301')
// Test that other IPs are denied
await expect(
sandbox.commands.run(
'curl --connect-timeout 3 --max-time 5 -Is https://8.8.8.8'
)
).rejects.toBeInstanceOf(CommandExitError)
}
)
})
describe('deny specific IP address', () => {
sandboxTest.scoped({
sandboxOpts: {
network: {
denyOut: ['8.8.8.8'],
},
},
})
sandboxTest.skipIf(isDebug)(
'deny specific IP address',
async ({ sandbox }) => {
// Test that denied IP fails
await expect(
sandbox.commands.run(
'curl --connect-timeout 3 --max-time 5 -Is https://8.8.8.8'
)
).rejects.toBeInstanceOf(CommandExitError)
// Test that other IPs work
const result = await sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://1.1.1.1"
)
assert.equal(result.exitCode, 0)
assert.equal(result.stdout.trim(), '301')
}
)
})
describe('deny all traffic using allTraffic selector', () => {
sandboxTest.scoped({
sandboxOpts: {
network: {
denyOut: ({ allTraffic }) => [allTraffic],
},
},
})
sandboxTest.skipIf(isDebug)(
'deny all traffic using allTraffic selector',
async ({ sandbox }) => {
// Test that all traffic is denied
await expect(
sandbox.commands.run(
'curl --connect-timeout 3 --max-time 5 -Is https://1.1.1.1'
)
).rejects.toBeInstanceOf(CommandExitError)
await expect(
sandbox.commands.run(
'curl --connect-timeout 3 --max-time 5 -Is https://8.8.8.8'
)
).rejects.toBeInstanceOf(CommandExitError)
}
)
})
describe('allow takes precedence over deny', () => {
sandboxTest.scoped({
sandboxOpts: {
network: {
denyOut: ({ allTraffic }) => [allTraffic],
allowOut: ['1.1.1.1', '8.8.8.8'],
},
},
})
sandboxTest.skipIf(isDebug)(
'allow takes precedence over deny',
async ({ sandbox }) => {
// Test that 1.1.1.1 works (explicitly allowed)
const result1 = await sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://1.1.1.1"
)
assert.equal(result1.exitCode, 0)
assert.equal(result1.stdout.trim(), '301')
// Test that 8.8.8.8 also works (explicitly allowed, takes precedence over denyOut)
const result2 = await sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://8.8.8.8"
)
assert.equal(result2.exitCode, 0)
assert.equal(result2.stdout.trim(), '302')
}
)
})
describe('allowPublicTraffic=false', () => {
sandboxTest.scoped({
sandboxOpts: {
network: {
allowPublicTraffic: false,
},
},
})
sandboxTest.skipIf(isDebug)(
'sandbox requires traffic access token',
async ({ sandbox }) => {
// Verify the sandbox was created successfully and has a traffic access token
assert(sandbox.trafficAccessToken)
// Start a simple HTTP server in the sandbox
const port = 8080
sandbox.commands.run(`python3 -m http.server ${port}`, {
background: true,
})
// Wait for server to start
await new Promise((resolve) => setTimeout(resolve, 3000))
// Get the public URL for the sandbox
const sandboxUrl = `https://${sandbox.getHost(port)}`
// Test 1: Request without traffic access token should fail with 403
const response1 = await fetch(sandboxUrl)
assert.equal(response1.status, 403)
// Test 2: Request with valid traffic access token should succeed
const response2 = await fetch(sandboxUrl, {
headers: {
'e2b-traffic-access-token': sandbox.trafficAccessToken,
},
})
assert.equal(response2.status, 200)
}
)
})
describe('allowPublicTraffic=true', () => {
sandboxTest.scoped({
sandboxOpts: {
network: {
allowPublicTraffic: true,
},
},
})
sandboxTest.skipIf(isDebug)(
'sandbox works without token',
async ({ sandbox }) => {
// Start a simple HTTP server in the sandbox
const port = 8080
sandbox.commands.run(`python3 -m http.server ${port}`, {
background: true,
})
// Wait for server to start
await new Promise((resolve) => setTimeout(resolve, 3000))
// Get the public URL for the sandbox
const sandboxUrl = `https://${sandbox.getHost(port)}`
// Request without traffic access token should succeed (public access enabled)
const response = await fetch(sandboxUrl)
assert.equal(response.status, 200)
}
)
})
describe('firewall transform injects headers', () => {
const injectedHeader = 'X-E2B-Test-Token'
const injectedValue = 'e2b-transform-value-123'
sandboxTest.scoped({
sandboxOpts: {
network: {
rules: {
'httpbin.e2b.team': [
{
transform: {
headers: {
[injectedHeader]: injectedValue,
},
},
},
],
},
},
},
})
sandboxTest.skipIf(isDebug)(
'injected header is reflected by httpbin.e2b.team/headers',
async ({ sandbox }) => {
const result = await sandbox.commands.run(
'curl -sS --max-time 10 https://httpbin.e2b.team/headers'
)
assert.equal(result.exitCode, 0)
const parsed = JSON.parse(result.stdout) as {
headers: Record<string, string>
}
const reflected = parsed.headers[injectedHeader]
assert.equal(
reflected,
injectedValue,
`expected httpbin to reflect ${injectedHeader}=${injectedValue}, got headers: ${JSON.stringify(parsed.headers)}`
)
}
)
})
describe('updateNetwork applies new egress rules', () => {
sandboxTest.skipIf(isDebug)(
'denies a previously reachable IP after update',
async ({ sandbox }) => {
// Baseline: 8.8.8.8 is reachable.
const before = await sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://8.8.8.8"
)
assert.equal(before.exitCode, 0)
await sandbox.updateNetwork({ denyOut: ['8.8.8.8'] })
// 8.8.8.8 should now be denied.
await expect(
sandbox.commands.run(
'curl --connect-timeout 3 --max-time 5 -Is https://8.8.8.8'
)
).rejects.toBeInstanceOf(CommandExitError)
// Other destinations stay reachable.
const after = await sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://1.1.1.1"
)
assert.equal(after.exitCode, 0)
}
)
})
describe('updateNetwork clears existing rules when fields are omitted', () => {
sandboxTest.scoped({
sandboxOpts: {
network: {
denyOut: ({ allTraffic }) => [allTraffic],
allowOut: ['1.1.1.1'],
},
},
})
sandboxTest.skipIf(isDebug)(
'omitting fields replaces all egress rules',
async ({ sandbox }) => {
// Baseline from create-time config: 8.8.8.8 denied.
await expect(
sandbox.commands.run(
'curl --connect-timeout 3 --max-time 5 -Is https://8.8.8.8'
)
).rejects.toBeInstanceOf(CommandExitError)
// Empty update clears allow_out / deny_out entirely.
await sandbox.updateNetwork({})
const r1 = await sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://1.1.1.1"
)
assert.equal(r1.exitCode, 0)
const r2 = await sandbox.commands.run(
"curl -s -o /dev/null -w '%{http_code}' https://8.8.8.8"
)
assert.equal(r2.exitCode, 0)
}
)
})
describe('maskRequestHost option', () => {
sandboxTest.scoped({
sandboxOpts: {
network: {
maskRequestHost: 'custom-host.example.com:${PORT}',
},
},
})
sandboxTest.skipIf(isDebug)(
'verify maskRequestHost modifies Host header correctly',
async ({ sandbox }) => {
const port = 8080
const outputFile = '/tmp/headers.txt'
// Start a Python HTTP server that captures request headers and writes them to a file
sandbox.commands.run(
`python3 -c "
import http.server
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
with open('${outputFile}', 'w') as f:
for k, v in self.headers.items():
f.write(k + ': ' + v + chr(10))
self.send_response(200)
self.end_headers()
def log_message(self, *a): pass
http.server.HTTPServer(('', ${port}), H).handle_request()
"`,
{ background: true }
)
await new Promise((resolve) => setTimeout(resolve, 2000))
// Get the public URL for the sandbox
const sandboxUrl = `https://${sandbox.getHost(port)}`
// Make a request from OUTSIDE the sandbox through the proxy
// The Host header should be modified according to maskRequestHost
try {
await fetch(sandboxUrl, { signal: AbortSignal.timeout(5000) })
} catch (error) {
// Request may timeout, but headers are captured by the server
}
await new Promise((resolve) => setTimeout(resolve, 1000))
// Read the captured headers from inside the sandbox
const result = await sandbox.commands.run(`cat ${outputFile}`)
// Verify the Host header was modified according to maskRequestHost
assert.include(result.stdout, 'Host:')
assert.include(result.stdout, 'custom-host.example.com')
assert.include(result.stdout, `${port}`)
}
)
})