isol8 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # isol8
2
2
 
3
+ [![CI](https://github.com/Illusion47586/isol8/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Illusion47586/isol8/actions/workflows/ci.yml)
4
+ [![Coverage](https://raw.githubusercontent.com/Illusion47586/isol8/main/coverage/coverage-badge.svg)](https://github.com/Illusion47586/isol8/actions)
5
+ [![npm](https://img.shields.io/npm/v/isol8)](https://www.npmjs.com/package/isol8)
6
+ [![license](https://img.shields.io/npm/l/isol8)](./LICENSE)
7
+
3
8
  Secure code execution engine for AI agents. Run untrusted Python, Node.js, Bun, Deno, and Bash code inside locked-down Docker containers with network filtering, resource limits, and output controls.
4
9
 
5
10
  ## Features
@@ -270,6 +275,8 @@ const isol8 = new DockerIsol8({
270
275
  });
271
276
  ```
272
277
 
278
+ In `filtered` mode, iptables rules are applied at the kernel level to ensure the `sandbox` user can **only** reach the internal filtering proxy (`127.0.0.1:8118`). All other outbound traffic from the sandbox user is dropped, preventing bypass via raw sockets or non-HTTP protocols.
279
+
273
280
  ## Configuration
274
281
 
275
282
  Create `isol8.config.json` in your project root or `~/.isol8/config.json`.
package/dist/cli.js CHANGED
@@ -6316,23 +6316,12 @@ var require_bcrypt_pbkdf = __commonJS((exports, module) => {
6316
6316
  };
6317
6317
  });
6318
6318
 
6319
- // node_modules/cpu-features/build/Release/cpufeatures.node
6320
- var require_cpufeatures = __commonJS((exports, module) => {
6321
- module.exports = __require("./cpufeatures-tjjrgpt7.node");
6322
- });
6323
-
6324
- // node_modules/cpu-features/lib/index.js
6325
- var require_lib2 = __commonJS((exports, module) => {
6326
- var binding = require_cpufeatures();
6327
- module.exports = binding.getCPUInfo;
6328
- });
6329
-
6330
6319
  // node_modules/ssh2/lib/protocol/constants.js
6331
6320
  var require_constants = __commonJS((exports, module) => {
6332
6321
  var crypto = __require("crypto");
6333
6322
  var cpuInfo;
6334
6323
  try {
6335
- cpuInfo = require_lib2()();
6324
+ cpuInfo = (()=>{throw new Error("Cannot require module "+"cpu-features");})()();
6336
6325
  } catch {}
6337
6326
  var { bindingAvailable, CIPHER_INFO, MAC_INFO } = require_crypto();
6338
6327
  var eddsaSupported = (() => {
@@ -20975,7 +20964,7 @@ var require_keygen = __commonJS((exports, module) => {
20975
20964
  });
20976
20965
 
20977
20966
  // node_modules/ssh2/lib/index.js
20978
- var require_lib3 = __commonJS((exports, module) => {
20967
+ var require_lib2 = __commonJS((exports, module) => {
20979
20968
  var {
20980
20969
  AgentProtocol,
20981
20970
  BaseAgent,
@@ -21021,7 +21010,7 @@ var require_lib3 = __commonJS((exports, module) => {
21021
21010
 
21022
21011
  // node_modules/docker-modem/lib/ssh.js
21023
21012
  var require_ssh = __commonJS((exports, module) => {
21024
- var Client = require_lib3().Client;
21013
+ var Client = require_lib2().Client;
21025
21014
  var http = __require("http");
21026
21015
  module.exports = function(opt) {
21027
21016
  var conn = new Client;
@@ -55088,7 +55077,11 @@ class ContainerPool {
55088
55077
  }
55089
55078
  try {
55090
55079
  const killExec = await container.exec({
55091
- Cmd: ["sh", "-c", "pkill -9 -u sandbox 2>/dev/null; true"]
55080
+ Cmd: [
55081
+ "sh",
55082
+ "-c",
55083
+ "pkill -9 -u sandbox 2>/dev/null; /usr/sbin/iptables -F OUTPUT 2>/dev/null; true"
55084
+ ]
55092
55085
  });
55093
55086
  await killExec.start({ Detach: true });
55094
55087
  let killInfo = await killExec.inspect();
@@ -55334,7 +55327,7 @@ async function startProxy(container, networkFilter) {
55334
55327
  }
55335
55328
  const envPrefix = envParts.length > 0 ? `${envParts.join(" ")} ` : "";
55336
55329
  const startExec = await container.exec({
55337
- Cmd: ["sh", "-c", `${envPrefix}node /usr/local/bin/proxy.mjs &`]
55330
+ Cmd: ["sh", "-c", `${envPrefix}bash /usr/local/bin/proxy.sh &`]
55338
55331
  });
55339
55332
  await startExec.start({ Detach: true });
55340
55333
  const deadline = Date.now() + PROXY_STARTUP_TIMEOUT_MS;
@@ -55357,6 +55350,27 @@ async function startProxy(container, networkFilter) {
55357
55350
  }
55358
55351
  throw new Error("Proxy failed to start within timeout");
55359
55352
  }
55353
+ async function setupIptables(container) {
55354
+ const rules = [
55355
+ "/usr/sbin/iptables -A OUTPUT -o lo -j ACCEPT",
55356
+ "/usr/sbin/iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT",
55357
+ `/usr/sbin/iptables -A OUTPUT -p tcp -d 127.0.0.1 --dport ${PROXY_PORT} -m owner --uid-owner 100 -j ACCEPT`,
55358
+ "/usr/sbin/iptables -A OUTPUT -m owner --uid-owner 100 -j DROP"
55359
+ ].join(" && ");
55360
+ const exec = await container.exec({
55361
+ Cmd: ["sh", "-c", rules]
55362
+ });
55363
+ await exec.start({ Detach: true });
55364
+ let info2 = await exec.inspect();
55365
+ while (info2.Running) {
55366
+ await new Promise((r) => setTimeout(r, 50));
55367
+ info2 = await exec.inspect();
55368
+ }
55369
+ if (info2.ExitCode !== 0) {
55370
+ throw new Error(`Failed to set up iptables rules (exit code ${info2.ExitCode})`);
55371
+ }
55372
+ logger.debug("[Filtered] iptables rules applied — sandbox user restricted to proxy only");
55373
+ }
55360
55374
  function wrapWithTimeout(cmd, timeoutSec) {
55361
55375
  return ["timeout", "-s", "KILL", String(timeoutSec), ...cmd];
55362
55376
  }
@@ -55543,6 +55557,7 @@ class DockerIsol8 {
55543
55557
  await container.start();
55544
55558
  if (this.network === "filtered") {
55545
55559
  await startProxy(container, this.networkFilter);
55560
+ await setupIptables(container);
55546
55561
  }
55547
55562
  const ext = req.fileExtension ?? adapter.getFileExtension();
55548
55563
  const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
@@ -55623,6 +55638,7 @@ class DockerIsol8 {
55623
55638
  try {
55624
55639
  if (this.network === "filtered") {
55625
55640
  await startProxy(container, this.networkFilter);
55641
+ await setupIptables(container);
55626
55642
  }
55627
55643
  const ext = req.fileExtension ?? adapter.getFileExtension();
55628
55644
  const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
@@ -55782,6 +55798,7 @@ class DockerIsol8 {
55782
55798
  await this.container.start();
55783
55799
  if (this.network === "filtered") {
55784
55800
  await startProxy(this.container, this.networkFilter);
55801
+ await setupIptables(this.container);
55785
55802
  }
55786
55803
  this.persistentRuntime = adapter;
55787
55804
  }
@@ -55802,6 +55819,7 @@ class DockerIsol8 {
55802
55819
  };
55803
55820
  if (this.network === "filtered") {
55804
55821
  config.NetworkMode = "bridge";
55822
+ config.CapAdd = ["NET_ADMIN"];
55805
55823
  } else if (this.network === "host") {
55806
55824
  config.NetworkMode = "host";
55807
55825
  }
@@ -55861,9 +55879,11 @@ class DockerIsol8 {
55861
55879
  env2.push(`${key}=${value}`);
55862
55880
  }
55863
55881
  }
55864
- if (this.network === "filtered" && this.networkFilter) {
55865
- env2.push(`ISOL8_WHITELIST=${JSON.stringify(this.networkFilter.whitelist)}`);
55866
- env2.push(`ISOL8_BLACKLIST=${JSON.stringify(this.networkFilter.blacklist)}`);
55882
+ if (this.network === "filtered") {
55883
+ if (this.networkFilter) {
55884
+ env2.push(`ISOL8_WHITELIST=${JSON.stringify(this.networkFilter.whitelist)}`);
55885
+ env2.push(`ISOL8_BLACKLIST=${JSON.stringify(this.networkFilter.blacklist)}`);
55886
+ }
55867
55887
  env2.push(`HTTP_PROXY=http://127.0.0.1:${PROXY_PORT}`);
55868
55888
  env2.push(`HTTPS_PROXY=http://127.0.0.1:${PROXY_PORT}`);
55869
55889
  env2.push(`http_proxy=http://127.0.0.1:${PROXY_PORT}`);
@@ -56025,7 +56045,7 @@ var package_default;
56025
56045
  var init_package = __esm(() => {
56026
56046
  package_default = {
56027
56047
  name: "isol8",
56028
- version: "0.6.2",
56048
+ version: "0.8.0",
56029
56049
  description: "Secure code execution engine for AI agents",
56030
56050
  author: "Illusion47586",
56031
56051
  license: "MIT",
@@ -56086,6 +56106,8 @@ var init_package = __esm(() => {
56086
56106
  },
56087
56107
  devDependencies: {
56088
56108
  "@biomejs/biome": "^2.3.15",
56109
+ "@commitlint/cli": "^20.4.1",
56110
+ "@commitlint/config-conventional": "^20.4.1",
56089
56111
  "@semantic-release/changelog": "^6.0.3",
56090
56112
  "@semantic-release/exec": "^7.1.0",
56091
56113
  "@semantic-release/git": "^10.0.1",
@@ -56118,7 +56140,8 @@ var init_package = __esm(() => {
56118
56140
  }
56119
56141
  ],
56120
56142
  "simple-git-hooks": {
56121
- "pre-commit": "bun run lint-staged"
56143
+ "pre-commit": "bun run lint-staged",
56144
+ "commit-msg": "bunx commitlint --edit $1"
56122
56145
  },
56123
56146
  "lint-staged": {
56124
56147
  "*.{ts,tsx}": [
@@ -56128,6 +56151,9 @@ var init_package = __esm(() => {
56128
56151
  "src/types.ts": [
56129
56152
  "bash -c 'bun run schema'",
56130
56153
  "git add schema/isol8.config.schema.json"
56154
+ ],
56155
+ "*.{yaml,yml,json}": [
56156
+ "ultracite fix"
56131
56157
  ]
56132
56158
  }
56133
56159
  };
@@ -61365,7 +61391,7 @@ async function buildBaseImages(docker, onProgress) {
61365
61391
  const target = adapter.name;
61366
61392
  onProgress?.({ runtime: target, status: "building" });
61367
61393
  try {
61368
- const stream = await docker.buildImage({ context: DOCKERFILE_DIR, src: ["Dockerfile", "proxy.mjs"] }, {
61394
+ const stream = await docker.buildImage({ context: DOCKERFILE_DIR, src: ["Dockerfile", "proxy.sh", "proxy-handler.sh"] }, {
61369
61395
  t: adapter.image,
61370
61396
  target,
61371
61397
  dockerfile: "Dockerfile"
@@ -62026,4 +62052,4 @@ if (!process.argv.slice(2).length) {
62026
62052
  }
62027
62053
  program2.parse();
62028
62054
 
62029
- //# debugId=F86C7DBAE803AC0664756E2164756E21
62055
+ //# debugId=F5B0FBC3FF234CB364756E2164756E21
@@ -1,9 +1,10 @@
1
1
  # ── Base ──────────────────────────────────────────────────────────────
2
2
  FROM alpine:3.21 AS base
3
- RUN apk add --no-cache tini curl ca-certificates \
3
+ RUN apk add --no-cache tini curl ca-certificates iptables bash \
4
4
  && addgroup -S sandbox && adduser -S sandbox -G sandbox -h /sandbox
5
- COPY proxy.mjs /usr/local/bin/proxy.mjs
6
- RUN chmod +x /usr/local/bin/proxy.mjs
5
+ COPY proxy.sh /usr/local/bin/proxy.sh
6
+ COPY proxy-handler.sh /usr/local/bin/proxy-handler.sh
7
+ RUN chmod +x /usr/local/bin/proxy.sh /usr/local/bin/proxy-handler.sh
7
8
  WORKDIR /sandbox
8
9
  ENTRYPOINT ["/sbin/tini", "--"]
9
10
 
@@ -19,7 +20,7 @@ CMD ["node"]
19
20
 
20
21
  # ── Bun ───────────────────────────────────────────────────────────────
21
22
  FROM base AS bun
22
- RUN apk add --no-cache bash unzip libstdc++ libgcc \
23
+ RUN apk add --no-cache unzip libstdc++ libgcc \
23
24
  && curl -fsSL https://bun.sh/install | bash \
24
25
  && mv /root/.bun/bin/bun /usr/local/bin/bun \
25
26
  && ln -s /usr/local/bin/bun /usr/local/bin/bunx
@@ -27,15 +28,15 @@ CMD ["bun"]
27
28
 
28
29
  # ── Deno ──────────────────────────────────────────────────────────────
29
30
  FROM denoland/deno:alpine AS deno
30
- RUN apk add --no-cache tini curl ca-certificates \
31
+ RUN apk add --no-cache tini curl ca-certificates iptables bash \
31
32
  && addgroup -S sandbox && adduser -S sandbox -G sandbox -h /sandbox
32
- COPY proxy.mjs /usr/local/bin/proxy.mjs
33
- RUN chmod +x /usr/local/bin/proxy.mjs
33
+ COPY proxy.sh /usr/local/bin/proxy.sh
34
+ COPY proxy-handler.sh /usr/local/bin/proxy-handler.sh
35
+ RUN chmod +x /usr/local/bin/proxy.sh /usr/local/bin/proxy-handler.sh
34
36
  WORKDIR /sandbox
35
37
  ENTRYPOINT ["/sbin/tini", "--"]
36
38
  CMD ["deno"]
37
39
 
38
40
  # ── Bash ──────────────────────────────────────────────────────────────
39
41
  FROM base AS bash
40
- RUN apk add --no-cache bash
41
42
  CMD ["bash"]
@@ -0,0 +1,134 @@
1
+ #!/bin/bash
2
+ # isol8 proxy handler — handles a single proxied connection.
3
+ #
4
+ # Invoked by: nc -lk -e proxy-handler.sh
5
+ # stdin/stdout are wired to the client socket by nc.
6
+ #
7
+ # Env vars (inherited from proxy.sh launcher):
8
+ # ISOL8_WHITELIST_FILE - Path to file with whitelist regex patterns
9
+ # ISOL8_BLACKLIST_FILE - Path to file with blacklist regex patterns
10
+ #
11
+ # Supports:
12
+ # - HTTPS CONNECT tunneling (bidirectional relay via exec nc)
13
+ # - HTTP forwarding (GET/POST/etc via bash /dev/tcp)
14
+
15
+ WL="${ISOL8_WHITELIST_FILE:-}"
16
+ BL="${ISOL8_BLACKLIST_FILE:-}"
17
+
18
+ is_allowed() {
19
+ local host="$1"
20
+
21
+ # Check blacklist first
22
+ if [ -n "$BL" ] && [ -s "$BL" ]; then
23
+ if echo "$host" | grep -qEf "$BL" 2>/dev/null; then
24
+ return 1
25
+ fi
26
+ fi
27
+
28
+ # If whitelist is empty or missing, allow all
29
+ if [ -z "$WL" ] || [ ! -s "$WL" ]; then
30
+ return 0
31
+ fi
32
+
33
+ # Must match at least one whitelist pattern
34
+ if echo "$host" | grep -qEf "$WL" 2>/dev/null; then
35
+ return 0
36
+ fi
37
+
38
+ return 1
39
+ }
40
+
41
+ # Read the request line
42
+ # e.g. "CONNECT host:443 HTTP/1.1" or "GET http://host/path HTTP/1.1"
43
+ read -r request_line || exit 0
44
+ request_line="${request_line%%$'\r'}"
45
+
46
+ method="${request_line%% *}"
47
+ rest="${request_line#* }"
48
+ target="${rest%% *}"
49
+
50
+ # Read and store all headers until blank line
51
+ headers=""
52
+ content_length=0
53
+ while IFS= read -r hline; do
54
+ hline="${hline%%$'\r'}"
55
+ [ -z "$hline" ] && break
56
+ headers="${headers}${hline}"$'\n'
57
+ # Extract Content-Length
58
+ case "$hline" in
59
+ [Cc]ontent-[Ll]ength:*)
60
+ content_length="${hline#*: }"
61
+ content_length="${content_length// /}"
62
+ ;;
63
+ esac
64
+ done
65
+
66
+ # ── CONNECT (HTTPS tunneling) ──────────────────────────────────────────
67
+ if [ "$method" = "CONNECT" ]; then
68
+ host="${target%%:*}"
69
+ port="${target##*:}"
70
+ [ "$port" = "$host" ] && port=443
71
+
72
+ if ! is_allowed "$host"; then
73
+ msg="isol8: CONNECT to ${host} blocked by network filter"
74
+ printf "HTTP/1.1 403 Forbidden\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s" \
75
+ "${#msg}" "$msg"
76
+ exit 0
77
+ fi
78
+
79
+ # Send 200 then replace this process with nc for bidirectional relay.
80
+ # nc inherits the client socket on stdin/stdout from the nc -lk -e parent.
81
+ printf "HTTP/1.1 200 Connection Established\r\n\r\n"
82
+ exec nc "$host" "$port"
83
+ fi
84
+
85
+ # ── HTTP forwarding ────────────────────────────────────────────────────
86
+ # Proxy HTTP requests use absolute URLs: GET http://host:port/path HTTP/1.1
87
+ url_rest="${target#*://}"
88
+ hostport="${url_rest%%/*}"
89
+ path="/${url_rest#*/}"
90
+ # Handle URLs with no path component
91
+ [ "$path" = "/${url_rest}" ] && path="/"
92
+
93
+ host="${hostport%%:*}"
94
+ port="${hostport##*:}"
95
+ [ "$port" = "$host" ] && port=80
96
+
97
+ if ! is_allowed "$host"; then
98
+ msg="isol8: request to ${host} blocked by network filter"
99
+ printf "HTTP/1.1 403 Forbidden\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s" \
100
+ "${#msg}" "$msg"
101
+ exit 0
102
+ fi
103
+
104
+ # Open TCP connection via bash /dev/tcp
105
+ if ! exec 3<>/dev/tcp/"$host"/"$port" 2>/dev/null; then
106
+ msg="isol8: proxy error: connection to ${host}:${port} failed"
107
+ printf "HTTP/1.1 502 Bad Gateway\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s" \
108
+ "${#msg}" "$msg"
109
+ exit 0
110
+ fi
111
+
112
+ # Send request line with relative path (not absolute URL)
113
+ printf "%s %s HTTP/1.1\r\n" "$method" "$path" >&3
114
+
115
+ # Forward headers, skipping Proxy-* headers
116
+ while IFS= read -r h; do
117
+ [ -z "$h" ] && continue
118
+ case "$h" in
119
+ Proxy-*|proxy-*) continue ;;
120
+ esac
121
+ printf "%s\r\n" "$h" >&3
122
+ done <<< "$headers"
123
+ printf "\r\n" >&3
124
+
125
+ # Forward request body if present
126
+ if [ "$content_length" -gt 0 ] 2>/dev/null; then
127
+ head -c "$content_length" >&3
128
+ fi
129
+
130
+ # Relay response back to client
131
+ cat <&3
132
+
133
+ exec 3>&-
134
+ exit 0
@@ -0,0 +1,53 @@
1
+ #!/bin/bash
2
+ # isol8 proxy launcher — parses env vars and starts the proxy listener.
3
+ #
4
+ # Env vars:
5
+ # ISOL8_WHITELIST - JSON array of regex strings (allow these)
6
+ # ISOL8_BLACKLIST - JSON array of regex strings (block these)
7
+ # ISOL8_PROXY_PORT - Port to listen on (default: 8118)
8
+ #
9
+ # This script:
10
+ # 1. Parses ISOL8_WHITELIST/BLACKLIST JSON arrays into grep-compatible pattern files
11
+ # 2. Starts nc -lk -e /usr/local/bin/proxy-handler.sh on the specified port
12
+ #
13
+ # The pattern files are stored in /tmp/isol8-proxy/ and exported as env vars
14
+ # so the handler (forked by nc -e) can access them via inherited environment.
15
+
16
+ PORT="${ISOL8_PROXY_PORT:-8118}"
17
+ PROXY_DIR="/tmp/isol8-proxy"
18
+ mkdir -p "$PROXY_DIR"
19
+
20
+ WL_FILE="$PROXY_DIR/whitelist"
21
+ BL_FILE="$PROXY_DIR/blacklist"
22
+
23
+ # Parse JSON array of regex strings into a file with one ERE pattern per line.
24
+ # Input: JSON like '["^example\\.com$","^api\\."]'
25
+ # Output: file with one grep -E compatible pattern per line
26
+ parse_patterns() {
27
+ local json="$1" outfile="$2"
28
+ : > "$outfile"
29
+ if [ -z "$json" ] || [ "$json" = "[]" ]; then
30
+ return
31
+ fi
32
+ # Strip brackets, split on "," → one quoted pattern per line
33
+ # Then strip quotes and unescape doubled backslashes from JSON encoding
34
+ echo "$json" \
35
+ | sed 's/^\[//; s/\]$//' \
36
+ | sed 's/","/"\n"/g' \
37
+ | sed 's/^"//; s/"$//' \
38
+ | sed 's/\\\\/\\/g' \
39
+ > "$outfile"
40
+ }
41
+
42
+ parse_patterns "${ISOL8_WHITELIST:-}" "$WL_FILE"
43
+ parse_patterns "${ISOL8_BLACKLIST:-}" "$BL_FILE"
44
+
45
+ # Export paths so the handler (forked by nc -e) can find them
46
+ export ISOL8_WHITELIST_FILE="$WL_FILE"
47
+ export ISOL8_BLACKLIST_FILE="$BL_FILE"
48
+
49
+ echo "isol8 proxy listening on 127.0.0.1:${PORT}"
50
+
51
+ # Start listening — nc -lk provides a persistent server that forks
52
+ # a handler for each connection with stdin/stdout wired to the socket
53
+ exec nc -lk -s 127.0.0.1 -p "$PORT" -e /usr/local/bin/proxy-handler.sh
package/dist/index.js CHANGED
@@ -264,7 +264,11 @@ class ContainerPool {
264
264
  }
265
265
  try {
266
266
  const killExec = await container.exec({
267
- Cmd: ["sh", "-c", "pkill -9 -u sandbox 2>/dev/null; true"]
267
+ Cmd: [
268
+ "sh",
269
+ "-c",
270
+ "pkill -9 -u sandbox 2>/dev/null; /usr/sbin/iptables -F OUTPUT 2>/dev/null; true"
271
+ ]
268
272
  });
269
273
  await killExec.start({ Detach: true });
270
274
  let killInfo = await killExec.inspect();
@@ -503,7 +507,7 @@ async function startProxy(container, networkFilter) {
503
507
  }
504
508
  const envPrefix = envParts.length > 0 ? `${envParts.join(" ")} ` : "";
505
509
  const startExec = await container.exec({
506
- Cmd: ["sh", "-c", `${envPrefix}node /usr/local/bin/proxy.mjs &`]
510
+ Cmd: ["sh", "-c", `${envPrefix}bash /usr/local/bin/proxy.sh &`]
507
511
  });
508
512
  await startExec.start({ Detach: true });
509
513
  const deadline = Date.now() + PROXY_STARTUP_TIMEOUT_MS;
@@ -526,6 +530,27 @@ async function startProxy(container, networkFilter) {
526
530
  }
527
531
  throw new Error("Proxy failed to start within timeout");
528
532
  }
533
+ async function setupIptables(container) {
534
+ const rules = [
535
+ "/usr/sbin/iptables -A OUTPUT -o lo -j ACCEPT",
536
+ "/usr/sbin/iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT",
537
+ `/usr/sbin/iptables -A OUTPUT -p tcp -d 127.0.0.1 --dport ${PROXY_PORT} -m owner --uid-owner 100 -j ACCEPT`,
538
+ "/usr/sbin/iptables -A OUTPUT -m owner --uid-owner 100 -j DROP"
539
+ ].join(" && ");
540
+ const exec = await container.exec({
541
+ Cmd: ["sh", "-c", rules]
542
+ });
543
+ await exec.start({ Detach: true });
544
+ let info = await exec.inspect();
545
+ while (info.Running) {
546
+ await new Promise((r) => setTimeout(r, 50));
547
+ info = await exec.inspect();
548
+ }
549
+ if (info.ExitCode !== 0) {
550
+ throw new Error(`Failed to set up iptables rules (exit code ${info.ExitCode})`);
551
+ }
552
+ logger.debug("[Filtered] iptables rules applied — sandbox user restricted to proxy only");
553
+ }
529
554
  function wrapWithTimeout(cmd, timeoutSec) {
530
555
  return ["timeout", "-s", "KILL", String(timeoutSec), ...cmd];
531
556
  }
@@ -712,6 +737,7 @@ class DockerIsol8 {
712
737
  await container.start();
713
738
  if (this.network === "filtered") {
714
739
  await startProxy(container, this.networkFilter);
740
+ await setupIptables(container);
715
741
  }
716
742
  const ext = req.fileExtension ?? adapter.getFileExtension();
717
743
  const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
@@ -792,6 +818,7 @@ class DockerIsol8 {
792
818
  try {
793
819
  if (this.network === "filtered") {
794
820
  await startProxy(container, this.networkFilter);
821
+ await setupIptables(container);
795
822
  }
796
823
  const ext = req.fileExtension ?? adapter.getFileExtension();
797
824
  const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
@@ -951,6 +978,7 @@ class DockerIsol8 {
951
978
  await this.container.start();
952
979
  if (this.network === "filtered") {
953
980
  await startProxy(this.container, this.networkFilter);
981
+ await setupIptables(this.container);
954
982
  }
955
983
  this.persistentRuntime = adapter;
956
984
  }
@@ -971,6 +999,7 @@ class DockerIsol8 {
971
999
  };
972
1000
  if (this.network === "filtered") {
973
1001
  config.NetworkMode = "bridge";
1002
+ config.CapAdd = ["NET_ADMIN"];
974
1003
  } else if (this.network === "host") {
975
1004
  config.NetworkMode = "host";
976
1005
  }
@@ -1030,9 +1059,11 @@ class DockerIsol8 {
1030
1059
  env.push(`${key}=${value}`);
1031
1060
  }
1032
1061
  }
1033
- if (this.network === "filtered" && this.networkFilter) {
1034
- env.push(`ISOL8_WHITELIST=${JSON.stringify(this.networkFilter.whitelist)}`);
1035
- env.push(`ISOL8_BLACKLIST=${JSON.stringify(this.networkFilter.blacklist)}`);
1062
+ if (this.network === "filtered") {
1063
+ if (this.networkFilter) {
1064
+ env.push(`ISOL8_WHITELIST=${JSON.stringify(this.networkFilter.whitelist)}`);
1065
+ env.push(`ISOL8_BLACKLIST=${JSON.stringify(this.networkFilter.blacklist)}`);
1066
+ }
1036
1067
  env.push(`HTTP_PROXY=http://127.0.0.1:${PROXY_PORT}`);
1037
1068
  env.push(`HTTPS_PROXY=http://127.0.0.1:${PROXY_PORT}`);
1038
1069
  env.push(`http_proxy=http://127.0.0.1:${PROXY_PORT}`);
@@ -1393,7 +1424,7 @@ init_logger();
1393
1424
  // package.json
1394
1425
  var package_default = {
1395
1426
  name: "isol8",
1396
- version: "0.6.2",
1427
+ version: "0.8.0",
1397
1428
  description: "Secure code execution engine for AI agents",
1398
1429
  author: "Illusion47586",
1399
1430
  license: "MIT",
@@ -1454,6 +1485,8 @@ var package_default = {
1454
1485
  },
1455
1486
  devDependencies: {
1456
1487
  "@biomejs/biome": "^2.3.15",
1488
+ "@commitlint/cli": "^20.4.1",
1489
+ "@commitlint/config-conventional": "^20.4.1",
1457
1490
  "@semantic-release/changelog": "^6.0.3",
1458
1491
  "@semantic-release/exec": "^7.1.0",
1459
1492
  "@semantic-release/git": "^10.0.1",
@@ -1486,7 +1519,8 @@ var package_default = {
1486
1519
  }
1487
1520
  ],
1488
1521
  "simple-git-hooks": {
1489
- "pre-commit": "bun run lint-staged"
1522
+ "pre-commit": "bun run lint-staged",
1523
+ "commit-msg": "bunx commitlint --edit $1"
1490
1524
  },
1491
1525
  "lint-staged": {
1492
1526
  "*.{ts,tsx}": [
@@ -1496,6 +1530,9 @@ var package_default = {
1496
1530
  "src/types.ts": [
1497
1531
  "bash -c 'bun run schema'",
1498
1532
  "git add schema/isol8.config.schema.json"
1533
+ ],
1534
+ "*.{yaml,yml,json}": [
1535
+ "ultracite fix"
1499
1536
  ]
1500
1537
  }
1501
1538
  };
@@ -1722,4 +1759,4 @@ export {
1722
1759
  BunAdapter
1723
1760
  };
1724
1761
 
1725
- //# debugId=1AFCD54347509D5A64756E2164756E21
1762
+ //# debugId=90624257FBE4C46A64756E2164756E21
@@ -1 +1 @@
1
- {"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../../src/engine/docker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,MAAM,MAAM,WAAW,CAAC;AAG/B,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,WAAW,EAEX,YAAY,EAIZ,WAAW,EACZ,MAAM,UAAU,CAAC;AAoPlB,2HAA2H;AAC3H,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,WAAY,YAAW,WAAW;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAY;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAsB;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAElC,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,IAAI,CAA8B;IAE1C;;;OAGG;gBACS,OAAO,GAAE,kBAAuB,EAAE,aAAa,SAAK;IAwBhE;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B,kFAAkF;IAC5E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAuB3B;;;OAGG;IACG,OAAO,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAW9D;;;;;;;OAOG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYpE;;;;;;OAMG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmB5C,6GAA6G;IAC7G,IAAI,WAAW,IAAI,MAAM,GAAG,IAAI,CAE/B;IAED;;;OAGG;IACI,aAAa,CAAC,GAAG,EAAE,gBAAgB,GAAG,aAAa,CAAC,WAAW,CAAC;YAqFzD,YAAY;YAcZ,gBAAgB;YAyGhB,iBAAiB;YA8FjB,aAAa;YAkBb,oBAAoB;YASpB,wBAAwB;IA2BtC,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,eAAe;IAsBvB,OAAO,CAAC,iBAAiB;IA+BzB,OAAO,CAAC,yBAAyB;IAyBjC,OAAO,CAAC,QAAQ;YAmCD,gBAAgB;YA8EjB,iBAAiB;IA8D/B,OAAO,CAAC,iBAAiB;IAYzB;;;;;;;;;;;;;;;;;;;;OAoBG;WACU,OAAO,CAClB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CA2BlE"}
1
+ {"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../../src/engine/docker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,MAAM,MAAM,WAAW,CAAC;AAG/B,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,WAAW,EAEX,YAAY,EAIZ,WAAW,EACZ,MAAM,UAAU,CAAC;AAqSlB,2HAA2H;AAC3H,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,WAAY,YAAW,WAAW;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAY;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAsB;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAElC,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,IAAI,CAA8B;IAE1C;;;OAGG;gBACS,OAAO,GAAE,kBAAuB,EAAE,aAAa,SAAK;IAwBhE;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B,kFAAkF;IAC5E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAuB3B;;;OAGG;IACG,OAAO,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAW9D;;;;;;;OAOG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYpE;;;;;;OAMG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmB5C,6GAA6G;IAC7G,IAAI,WAAW,IAAI,MAAM,GAAG,IAAI,CAE/B;IAED;;;OAGG;IACI,aAAa,CAAC,GAAG,EAAE,gBAAgB,GAAG,aAAa,CAAC,WAAW,CAAC;YAsFzD,YAAY;YAcZ,gBAAgB;YA0GhB,iBAAiB;YA8FjB,aAAa;YAkBb,oBAAoB;YASpB,wBAAwB;IA4BtC,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,eAAe;IA2BvB,OAAO,CAAC,iBAAiB;IA+BzB,OAAO,CAAC,yBAAyB;IAyBjC,OAAO,CAAC,QAAQ;YAwCD,gBAAgB;YA8EjB,iBAAiB;IA8D/B,OAAO,CAAC,iBAAiB;IAYzB;;;;;;;;;;;;;;;;;;;;OAoBG;WACU,OAAO,CAClB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CA2BlE"}
@@ -1 +1 @@
1
- {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../../src/engine/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,MAAM,MAAM,WAAW,CAAC;AAGpC,4CAA4C;AAC5C,MAAM,WAAW,WAAW;IAC1B,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC;CAC7D;AAOD;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA+C;IAC7E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IACxD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA4B;gBAEtD,OAAO,EAAE,WAAW;IAMhC;;;;OAIG;IACG,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;IAcvD;;;;;OAKG;IACG,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8CxE;;;OAGG;IACG,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBxC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAgBd,eAAe;IAW7B,2DAA2D;IAC3D,OAAO,CAAC,SAAS;CAuClB"}
1
+ {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../../src/engine/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,MAAM,MAAM,WAAW,CAAC;AAGpC,4CAA4C;AAC5C,MAAM,WAAW,WAAW;IAC1B,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC;CAC7D;AAOD;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA+C;IAC7E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IACxD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA4B;gBAEtD,OAAO,EAAE,WAAW;IAMhC;;;;OAIG;IACG,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;IAcvD;;;;;OAKG;IACG,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkDxE;;;OAGG;IACG,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBxC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAgBd,eAAe;IAW7B,2DAA2D;IAC3D,OAAO,CAAC,SAAS;CAuClB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isol8",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Secure code execution engine for AI agents",
5
5
  "author": "Illusion47586",
6
6
  "license": "MIT",
@@ -61,6 +61,8 @@
61
61
  },
62
62
  "devDependencies": {
63
63
  "@biomejs/biome": "^2.3.15",
64
+ "@commitlint/cli": "^20.4.1",
65
+ "@commitlint/config-conventional": "^20.4.1",
64
66
  "@semantic-release/changelog": "^6.0.3",
65
67
  "@semantic-release/exec": "^7.1.0",
66
68
  "@semantic-release/git": "^10.0.1",
@@ -93,7 +95,8 @@
93
95
  }
94
96
  ],
95
97
  "simple-git-hooks": {
96
- "pre-commit": "bun run lint-staged"
98
+ "pre-commit": "bun run lint-staged",
99
+ "commit-msg": "bunx commitlint --edit $1"
97
100
  },
98
101
  "lint-staged": {
99
102
  "*.{ts,tsx}": [
@@ -103,6 +106,9 @@
103
106
  "src/types.ts": [
104
107
  "bash -c 'bun run schema'",
105
108
  "git add schema/isol8.config.schema.json"
109
+ ],
110
+ "*.{yaml,yml,json}": [
111
+ "ultracite fix"
106
112
  ]
107
113
  }
108
114
  }
@@ -1,127 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * isol8 network proxy — lightweight HTTP/HTTPS filtering proxy.
5
- *
6
- * Reads whitelist/blacklist regex patterns from env vars and blocks
7
- * non-matching outbound requests with 403.
8
- *
9
- * Env vars:
10
- * ISOL8_WHITELIST - JSON array of regex strings (allow these)
11
- * ISOL8_BLACKLIST - JSON array of regex strings (block these)
12
- * ISOL8_PROXY_PORT - Port to listen on (default: 8118)
13
- *
14
- * Logic:
15
- * 1. If blacklist matches → BLOCK
16
- * 2. If whitelist is non-empty and hostname doesn't match → BLOCK
17
- * 3. Otherwise → ALLOW
18
- */
19
-
20
- import http from "node:http";
21
- import net from "node:net";
22
-
23
- const port = Number.parseInt(process.env.ISOL8_PROXY_PORT || "8118", 10);
24
-
25
- const whitelist = parsePatterns(process.env.ISOL8_WHITELIST);
26
- const blacklist = parsePatterns(process.env.ISOL8_BLACKLIST);
27
-
28
- function parsePatterns(envVar) {
29
- if (!envVar) {
30
- return [];
31
- }
32
- try {
33
- const arr = JSON.parse(envVar);
34
- return arr.map((p) => new RegExp(p));
35
- } catch {
36
- return [];
37
- }
38
- }
39
-
40
- function isAllowed(hostname) {
41
- // Check blacklist first
42
- for (const re of blacklist) {
43
- if (re.test(hostname)) {
44
- return false;
45
- }
46
- }
47
-
48
- // If whitelist is empty, allow all (only blacklist applies)
49
- if (whitelist.length === 0) {
50
- return true;
51
- }
52
-
53
- // Otherwise, must match at least one whitelist pattern
54
- for (const re of whitelist) {
55
- if (re.test(hostname)) {
56
- return true;
57
- }
58
- }
59
-
60
- return false;
61
- }
62
-
63
- const server = http.createServer((req, res) => {
64
- const url = new URL(req.url, `http://${req.headers.host}`);
65
- const hostname = url.hostname;
66
-
67
- if (!isAllowed(hostname)) {
68
- res.writeHead(403, { "Content-Type": "text/plain" });
69
- res.end(`isol8: request to ${hostname} blocked by network filter`);
70
- return;
71
- }
72
-
73
- // Forward HTTP request
74
- const proxyReq = http.request(
75
- {
76
- hostname: url.hostname,
77
- port: url.port || 80,
78
- path: url.pathname + url.search,
79
- method: req.method,
80
- headers: req.headers,
81
- },
82
- (proxyRes) => {
83
- res.writeHead(proxyRes.statusCode, proxyRes.headers);
84
- proxyRes.pipe(res);
85
- }
86
- );
87
-
88
- proxyReq.on("error", (err) => {
89
- res.writeHead(502, { "Content-Type": "text/plain" });
90
- res.end(`isol8: proxy error: ${err.message}`);
91
- });
92
-
93
- req.pipe(proxyReq);
94
- });
95
-
96
- // Handle HTTPS CONNECT tunneling
97
- server.on("connect", (req, clientSocket, head) => {
98
- const [hostname, port] = req.url.split(":");
99
-
100
- if (!isAllowed(hostname)) {
101
- clientSocket.write(
102
- "HTTP/1.1 403 Forbidden\r\nContent-Type: text/plain\r\n\r\n" +
103
- `isol8: CONNECT to ${hostname} blocked by network filter`
104
- );
105
- clientSocket.end();
106
- return;
107
- }
108
-
109
- const serverSocket = net.connect(Number.parseInt(port || "443", 10), hostname, () => {
110
- clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
111
- serverSocket.write(head);
112
- serverSocket.pipe(clientSocket);
113
- clientSocket.pipe(serverSocket);
114
- });
115
-
116
- serverSocket.on("error", (err) => {
117
- clientSocket.write(
118
- "HTTP/1.1 502 Bad Gateway\r\nContent-Type: text/plain\r\n\r\n" +
119
- `isol8: tunnel error: ${err.message}`
120
- );
121
- clientSocket.end();
122
- });
123
- });
124
-
125
- server.listen(port, "127.0.0.1", () => {
126
- console.log(`isol8 proxy listening on 127.0.0.1:${port}`);
127
- });