@kortix/agent-tunnel 0.13.50 → 0.13.52

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
@@ -169,6 +169,12 @@ The local config is the maximum boundary. The server cannot widen configured
169
169
  filesystem paths, blocked paths, shell commands, timeouts, file sizes, or desktop
170
170
  features.
171
171
 
172
+ A `shell` grant is local code execution. The allow list matches the executable
173
+ name exactly and the block list also matches every path ending in that name
174
+ (`/bin/rm` is `rm`). Arguments are not restricted: an allowed `git`, `find`, or
175
+ `python` can run any code through its own flags. Grant `shell` only to a session
176
+ you trust with this computer.
177
+
172
178
  ## Computer Use driver
173
179
 
174
180
  Agent Tunnel never downloads or executes a desktop driver. Install `cua-driver`
package/dist/agent-cli.js CHANGED
@@ -19,15 +19,13 @@ var __toESM = (mod, isNodeMode, target) => {
19
19
  return cached;
20
20
  }
21
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
22
- const to = isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
23
- if (mod && typeof mod === "object" || typeof mod === "function") {
24
- for (let key of __getOwnPropNames(mod))
25
- if (!__hasOwnProp.call(to, key))
26
- __defProp(to, key, {
27
- get: __accessProp.bind(mod, key),
28
- enumerable: true
29
- });
30
- }
22
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
23
+ for (let key of __getOwnPropNames(mod))
24
+ if (!__hasOwnProp.call(to, key))
25
+ __defProp(to, key, {
26
+ get: __accessProp.bind(mod, key),
27
+ enumerable: true
28
+ });
31
29
  if (canCache)
32
30
  cache.set(mod, to);
33
31
  return to;
@@ -46,21 +44,11 @@ var __export = (target, all) => {
46
44
  set: __exportSetter.bind(all, name)
47
45
  });
48
46
  };
49
- var __esm = (fn, res, err) => () => {
50
- if (fn)
51
- try {
52
- res = fn(fn = 0);
53
- } catch (e) {
54
- err = [e];
55
- }
56
- if (err)
57
- throw err[0];
58
- return res;
59
- };
47
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
60
48
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
61
49
 
62
50
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/constants.js
63
- var require_constants = __commonJS(function(exports, module) {
51
+ var require_constants = __commonJS((exports, module) => {
64
52
  var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"];
65
53
  var hasBlob = typeof Blob !== "undefined";
66
54
  if (hasBlob)
@@ -80,7 +68,7 @@ var require_constants = __commonJS(function(exports, module) {
80
68
  });
81
69
 
82
70
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/buffer-util.js
83
- var require_buffer_util = __commonJS(function(exports, module) {
71
+ var require_buffer_util = __commonJS((exports, module) => {
84
72
  var { EMPTY_BUFFER } = require_constants();
85
73
  var FastBuffer = Buffer[Symbol.species];
86
74
  function concat(list, totalLength) {
@@ -141,11 +129,11 @@ var require_buffer_util = __commonJS(function(exports, module) {
141
129
  if (!process.env.WS_NO_BUFFER_UTIL) {
142
130
  try {
143
131
  const bufferUtil = (()=>{throw new Error("Cannot require module "+"bufferutil");})();
144
- module.exports.mask = function(source, mask2, output, offset, length) {
132
+ module.exports.mask = function(source, mask, output, offset, length) {
145
133
  if (length < 48)
146
- _mask(source, mask2, output, offset, length);
134
+ _mask(source, mask, output, offset, length);
147
135
  else
148
- bufferUtil.mask(source, mask2, output, offset, length);
136
+ bufferUtil.mask(source, mask, output, offset, length);
149
137
  };
150
138
  module.exports.unmask = function(buffer, mask) {
151
139
  if (buffer.length < 32)
@@ -158,7 +146,7 @@ var require_buffer_util = __commonJS(function(exports, module) {
158
146
  });
159
147
 
160
148
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/limiter.js
161
- var require_limiter = __commonJS(function(exports, module) {
149
+ var require_limiter = __commonJS((exports, module) => {
162
150
  var kDone = Symbol("kDone");
163
151
  var kRun = Symbol("kRun");
164
152
 
@@ -190,7 +178,7 @@ var require_limiter = __commonJS(function(exports, module) {
190
178
  });
191
179
 
192
180
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/permessage-deflate.js
193
- var require_permessage_deflate = __commonJS(function(exports, module) {
181
+ var require_permessage_deflate = __commonJS((exports, module) => {
194
182
  var zlib = __require("zlib");
195
183
  var bufferUtil = require_buffer_util();
196
184
  var Limiter = require_limiter();
@@ -378,7 +366,7 @@ var require_permessage_deflate = __commonJS(function(exports, module) {
378
366
  callback(err);
379
367
  return;
380
368
  }
381
- const data = bufferUtil.concat(this._inflate[kBuffers], this._inflate[kTotalLength]);
369
+ const data2 = bufferUtil.concat(this._inflate[kBuffers], this._inflate[kTotalLength]);
382
370
  if (this._inflate._readableState.endEmitted) {
383
371
  this._inflate.close();
384
372
  this._inflate = null;
@@ -389,7 +377,7 @@ var require_permessage_deflate = __commonJS(function(exports, module) {
389
377
  this._inflate.reset();
390
378
  }
391
379
  }
392
- callback(null, data);
380
+ callback(null, data2);
393
381
  });
394
382
  }
395
383
  _compress(data, fin, callback) {
@@ -411,9 +399,9 @@ var require_permessage_deflate = __commonJS(function(exports, module) {
411
399
  if (!this._deflate) {
412
400
  return;
413
401
  }
414
- let data = bufferUtil.concat(this._deflate[kBuffers], this._deflate[kTotalLength]);
402
+ let data2 = bufferUtil.concat(this._deflate[kBuffers], this._deflate[kTotalLength]);
415
403
  if (fin) {
416
- data = new FastBuffer(data.buffer, data.byteOffset, data.length - 4);
404
+ data2 = new FastBuffer(data2.buffer, data2.byteOffset, data2.length - 4);
417
405
  }
418
406
  this._deflate[kCallback] = null;
419
407
  this._deflate[kTotalLength] = 0;
@@ -421,7 +409,7 @@ var require_permessage_deflate = __commonJS(function(exports, module) {
421
409
  if (fin && this.params[`${endpoint}_no_context_takeover`]) {
422
410
  this._deflate.reset();
423
411
  }
424
- callback(null, data);
412
+ callback(null, data2);
425
413
  });
426
414
  }
427
415
  }
@@ -454,7 +442,7 @@ var require_permessage_deflate = __commonJS(function(exports, module) {
454
442
  });
455
443
 
456
444
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/validation.js
457
- var require_validation = __commonJS(function(exports, module) {
445
+ var require_validation = __commonJS((exports, module) => {
458
446
  var { isUtf8 } = __require("buffer");
459
447
  var { hasBlob } = require_constants();
460
448
  var tokenChars = [
@@ -641,7 +629,7 @@ var require_validation = __commonJS(function(exports, module) {
641
629
  });
642
630
 
643
631
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/receiver.js
644
- var require_receiver = __commonJS(function(exports, module) {
632
+ var require_receiver = __commonJS((exports, module) => {
645
633
  var { Writable } = __require("stream");
646
634
  var PerMessageDeflate = require_permessage_deflate();
647
635
  var {
@@ -1035,7 +1023,7 @@ var require_receiver = __commonJS(function(exports, module) {
1035
1023
  });
1036
1024
 
1037
1025
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/sender.js
1038
- var require_sender = __commonJS(function(exports, module) {
1026
+ var require_sender = __commonJS((exports, module) => {
1039
1027
  var { Duplex } = __require("stream");
1040
1028
  var { randomFillSync } = __require("crypto");
1041
1029
  var {
@@ -1394,7 +1382,7 @@ var require_sender = __commonJS(function(exports, module) {
1394
1382
  });
1395
1383
 
1396
1384
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/event-target.js
1397
- var require_event_target = __commonJS(function(exports, module) {
1385
+ var require_event_target = __commonJS((exports, module) => {
1398
1386
  var { kForOnEventAttribute, kListener } = require_constants();
1399
1387
  var kCode = Symbol("kCode");
1400
1388
  var kData = Symbol("kData");
@@ -1545,7 +1533,7 @@ var require_event_target = __commonJS(function(exports, module) {
1545
1533
  });
1546
1534
 
1547
1535
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/extension.js
1548
- var require_extension = __commonJS(function(exports, module) {
1536
+ var require_extension = __commonJS((exports, module) => {
1549
1537
  var { tokenChars } = require_validation();
1550
1538
  function push(dest, name, elem) {
1551
1539
  if (dest[name] === undefined)
@@ -1710,7 +1698,7 @@ var require_extension = __commonJS(function(exports, module) {
1710
1698
  });
1711
1699
 
1712
1700
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/websocket.js
1713
- var require_websocket = __commonJS(function(exports, module) {
1701
+ var require_websocket = __commonJS((exports, module) => {
1714
1702
  var EventEmitter = __require("events");
1715
1703
  var https = __require("https");
1716
1704
  var http = __require("http");
@@ -2133,7 +2121,7 @@ var require_websocket = __commonJS(function(exports, module) {
2133
2121
  }
2134
2122
  const defaultPort = isSecure ? 443 : 80;
2135
2123
  const key = randomBytes(16).toString("base64");
2136
- const request3 = isSecure ? https.request : http.request;
2124
+ const request = isSecure ? https.request : http.request;
2137
2125
  const protocolSet = new Set;
2138
2126
  let perMessageDeflate;
2139
2127
  opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
@@ -2192,8 +2180,8 @@ var require_websocket = __commonJS(function(exports, module) {
2192
2180
  const headers = options && options.headers;
2193
2181
  options = { ...options, headers: {} };
2194
2182
  if (headers) {
2195
- for (const [key, value] of Object.entries(headers)) {
2196
- options.headers[key.toLowerCase()] = value;
2183
+ for (const [key2, value] of Object.entries(headers)) {
2184
+ options.headers[key2.toLowerCase()] = value;
2197
2185
  }
2198
2186
  }
2199
2187
  } else if (websocket.listenerCount("redirect") === 0) {
@@ -2209,12 +2197,12 @@ var require_websocket = __commonJS(function(exports, module) {
2209
2197
  if (opts.auth && !options.headers.authorization) {
2210
2198
  options.headers.authorization = "Basic " + Buffer.from(opts.auth).toString("base64");
2211
2199
  }
2212
- req = websocket._req = request3(opts);
2200
+ req = websocket._req = request(opts);
2213
2201
  if (websocket._redirects) {
2214
2202
  websocket.emit("redirect", websocket.url, req);
2215
2203
  }
2216
2204
  } else {
2217
- req = websocket._req = request3(opts);
2205
+ req = websocket._req = request(opts);
2218
2206
  }
2219
2207
  if (opts.timeout) {
2220
2208
  req.on("timeout", () => {
@@ -2482,7 +2470,7 @@ var require_websocket = __commonJS(function(exports, module) {
2482
2470
  });
2483
2471
 
2484
2472
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/stream.js
2485
- var require_stream = __commonJS(function(exports, module) {
2473
+ var require_stream = __commonJS((exports, module) => {
2486
2474
  var WebSocket2 = require_websocket();
2487
2475
  var { Duplex } = __require("stream");
2488
2476
  function emitClose(stream) {
@@ -2532,9 +2520,9 @@ var require_stream = __commonJS(function(exports, module) {
2532
2520
  return;
2533
2521
  }
2534
2522
  let called = false;
2535
- ws.once("error", function error(err) {
2523
+ ws.once("error", function error(err2) {
2536
2524
  called = true;
2537
- callback(err);
2525
+ callback(err2);
2538
2526
  });
2539
2527
  ws.once("close", function close() {
2540
2528
  if (!called)
@@ -2585,7 +2573,7 @@ var require_stream = __commonJS(function(exports, module) {
2585
2573
  });
2586
2574
 
2587
2575
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/subprotocol.js
2588
- var require_subprotocol = __commonJS(function(exports, module) {
2576
+ var require_subprotocol = __commonJS((exports, module) => {
2589
2577
  var { tokenChars } = require_validation();
2590
2578
  function parse(header) {
2591
2579
  const protocols = new Set;
@@ -2606,11 +2594,11 @@ var require_subprotocol = __commonJS(function(exports, module) {
2606
2594
  }
2607
2595
  if (end === -1)
2608
2596
  end = i;
2609
- const protocol = header.slice(start, end);
2610
- if (protocols.has(protocol)) {
2611
- throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
2597
+ const protocol2 = header.slice(start, end);
2598
+ if (protocols.has(protocol2)) {
2599
+ throw new SyntaxError(`The "${protocol2}" subprotocol is duplicated`);
2612
2600
  }
2613
- protocols.add(protocol);
2601
+ protocols.add(protocol2);
2614
2602
  start = end = -1;
2615
2603
  } else {
2616
2604
  throw new SyntaxError(`Unexpected character at index ${i}`);
@@ -2630,7 +2618,7 @@ var require_subprotocol = __commonJS(function(exports, module) {
2630
2618
  });
2631
2619
 
2632
2620
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/lib/websocket-server.js
2633
- var require_websocket_server = __commonJS(function(exports, module) {
2621
+ var require_websocket_server = __commonJS((exports, module) => {
2634
2622
  var EventEmitter = __require("events");
2635
2623
  var http = __require("http");
2636
2624
  var { Duplex } = __require("stream");
@@ -2942,8 +2930,15 @@ var require_websocket_server = __commonJS(function(exports, module) {
2942
2930
  // ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/wrapper.mjs
2943
2931
  var exports_wrapper = {};
2944
2932
  __export(exports_wrapper, {
2933
+ subprotocol: () => import_subprotocol.default,
2934
+ extension: () => import_extension.default,
2935
+ default: () => wrapper_default,
2936
+ createWebSocketStream: () => import_stream.default,
2937
+ WebSocketServer: () => import_websocket_server.default,
2945
2938
  WebSocket: () => import_websocket.default,
2946
- default: () => wrapper_default
2939
+ Sender: () => import_sender.default,
2940
+ Receiver: () => import_receiver.default,
2941
+ PerMessageDeflate: () => import_permessage_deflate.default
2947
2942
  });
2948
2943
  var import_stream, import_extension, import_permessage_deflate, import_receiver, import_sender, import_subprotocol, import_websocket, import_websocket_server, wrapper_default;
2949
2944
  var init_wrapper = __esm(() => {
@@ -2962,7 +2957,7 @@ var init_wrapper = __esm(() => {
2962
2957
  if (typeof globalThis.WebSocket === "undefined") {
2963
2958
  try {
2964
2959
  const ws = await Promise.resolve().then(() => (init_wrapper(), exports_wrapper));
2965
- const WebSocketImpl = ws.WebSocket ?? wrapper_default;
2960
+ const WebSocketImpl = ws.WebSocket ?? ws.default;
2966
2961
  globalThis.WebSocket = WebSocketImpl;
2967
2962
  } catch {
2968
2963
  console.error('[agent-tunnel] WebSocket is not available. Install the "ws" package or use Node.js 22+.');
@@ -3928,7 +3923,7 @@ function visibleLength(value) {
3928
3923
  function clearScreen() {
3929
3924
  process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
3930
3925
  }
3931
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3926
+ var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
3932
3927
  var glyph = {
3933
3928
  on: `${c.green}●${c.reset}`,
3934
3929
  off: `${c.gray}○${c.reset}`,
@@ -4449,7 +4444,7 @@ class TunnelAgent {
4449
4444
  } catch {}
4450
4445
  const deadline = Date.now() + hold;
4451
4446
  while (Date.now() < deadline) {
4452
- await new Promise((resolve) => setTimeout(resolve, Math.min(100, hold / 4)));
4447
+ await new Promise((resolve2) => setTimeout(resolve2, Math.min(100, hold / 4)));
4453
4448
  const decision = decideAccess(readAccess(this.home));
4454
4449
  if (decision === "ask")
4455
4450
  continue;
@@ -4747,7 +4742,7 @@ function hostBundleId() {
4747
4742
  }
4748
4743
  }
4749
4744
  function execFile(cmd, args, timeoutMs = 30000, env = driverEnvironment()) {
4750
- return new Promise((resolve, reject) => {
4745
+ return new Promise((resolve2, reject) => {
4751
4746
  const proc = spawn4(cmd, args, {
4752
4747
  stdio: ["ignore", "pipe", "pipe"],
4753
4748
  env
@@ -4792,7 +4787,7 @@ function execFile(cmd, args, timeoutMs = 30000, env = driverEnvironment()) {
4792
4787
  const detail = stderr.trim() || stdout.trim();
4793
4788
  reject(new Error(`${cmd} failed (${code})${detail ? `: ${detail}` : ""}`));
4794
4789
  } else {
4795
- resolve({ stdout, stderr });
4790
+ resolve2({ stdout, stderr });
4796
4791
  }
4797
4792
  });
4798
4793
  proc.on("error", (err) => {
@@ -4814,7 +4809,7 @@ function isDaemonProxyFallback(message) {
4814
4809
  return message.includes("daemon proxy") && message.includes("Resource temporarily unavailable");
4815
4810
  }
4816
4811
  async function sleep2(ms) {
4817
- await new Promise((resolve) => setTimeout(resolve, ms));
4812
+ await new Promise((resolve2) => setTimeout(resolve2, ms));
4818
4813
  }
4819
4814
  function sanitizeArgs(args) {
4820
4815
  const sanitized = {};
@@ -5395,7 +5390,8 @@ function validateCommand(command, allowedCommands, blockedCommands) {
5395
5390
  throw new Error(`Command contains disallowed characters: "${trimmed}"`);
5396
5391
  }
5397
5392
  const executable = trimmed;
5398
- if (blockedCommands.length > 0 && blockedCommands.includes(executable)) {
5393
+ const name = executable.slice(executable.lastIndexOf("/") + 1);
5394
+ if (blockedCommands.length > 0 && (blockedCommands.includes(executable) || blockedCommands.includes(name))) {
5399
5395
  throw new Error(`Command "${executable}" is blocked`);
5400
5396
  }
5401
5397
  if (allowedCommands.length > 0) {
@@ -5446,7 +5442,7 @@ function createShellCapability(config) {
5446
5442
  safeEnv[key] = process.env[key];
5447
5443
  }
5448
5444
  }
5449
- return new Promise((resolve, reject) => {
5445
+ return new Promise((resolve3, reject) => {
5450
5446
  const proc = spawn5(executable, args, {
5451
5447
  cwd,
5452
5448
  shell: false,
@@ -5489,7 +5485,7 @@ function createShellCapability(config) {
5489
5485
  reject(new Error(`Command failed to start: ${err.message}`));
5490
5486
  });
5491
5487
  proc.on("close", (code, signal) => {
5492
- resolve({
5488
+ resolve3({
5493
5489
  exitCode: code,
5494
5490
  signal,
5495
5491
  stdout,
@@ -5578,12 +5574,12 @@ function clearSavedCredentials() {
5578
5574
  // src/agent/credential-probe.ts
5579
5575
  var DEFAULT_PROBE_TIMEOUT_MS = 1e4;
5580
5576
  function probeCredentials(config, options = {}) {
5581
- return new Promise((resolve) => {
5577
+ return new Promise((resolve3) => {
5582
5578
  let socket;
5583
5579
  try {
5584
5580
  socket = new WebSocket(new URL(buildTunnelWsUrl(config)));
5585
5581
  } catch {
5586
- resolve("unreachable");
5582
+ resolve3("unreachable");
5587
5583
  return;
5588
5584
  }
5589
5585
  const settle = (result) => {
@@ -5591,7 +5587,7 @@ function probeCredentials(config, options = {}) {
5591
5587
  try {
5592
5588
  socket.close(1000, "probe complete");
5593
5589
  } catch {}
5594
- resolve(result);
5590
+ resolve3(result);
5595
5591
  };
5596
5592
  const timer = setTimeout(() => settle("unreachable"), options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS);
5597
5593
  socket.addEventListener("open", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kortix/agent-tunnel",
3
- "version": "0.13.50",
3
+ "version": "0.13.52",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/kortix-ai/suna.git",
@@ -0,0 +1,17 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { validateCommand } from './command-validator';
3
+
4
+ describe('validateCommand blocked commands', () => {
5
+ test('a blocked name blocks its absolute and relative path spellings', () => {
6
+ for (const spelling of ['rm', '/bin/rm', '/usr/bin/rm', './rm']) {
7
+ expect(() => validateCommand(spelling, [], ['rm'])).toThrow(/is blocked/);
8
+ }
9
+ });
10
+ test('an unrelated command with the blocked name inside a directory still runs', () => {
11
+ expect(validateCommand('/opt/rm-tools/ls', [], ['rm'])).toBe('/opt/rm-tools/ls');
12
+ });
13
+ test('an allowlist stays an exact match', () => {
14
+ expect(() => validateCommand('/usr/bin/git', ['git'], [])).toThrow(/not in the allowed/);
15
+ expect(validateCommand('git', ['git'], [])).toBe('git');
16
+ });
17
+ });
@@ -17,7 +17,11 @@ export function validateCommand(
17
17
 
18
18
  const executable = trimmed;
19
19
 
20
- if (blockedCommands.length > 0 && blockedCommands.includes(executable)) {
20
+ // A blocked name also blocks every path that ends in it: `/bin/rm` is `rm`.
21
+ // Arguments stay unrestricted: an allowed `git` or `python` runs code through
22
+ // its own flags, so a `shell` grant is local code execution, not a sandbox.
23
+ const name = executable.slice(executable.lastIndexOf('/') + 1);
24
+ if (blockedCommands.length > 0 && (blockedCommands.includes(executable) || blockedCommands.includes(name))) {
21
25
  throw new Error(`Command "${executable}" is blocked`);
22
26
  }
23
27