withruntime 0.8.4 → 0.10.0

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.
Files changed (132) hide show
  1. package/BLAXEL.md +4 -4
  2. package/COMPATIBILITY.md +145 -0
  3. package/DAYTONA.md +13 -7
  4. package/E2B.md +34 -20
  5. package/README.md +22 -11
  6. package/README.src.md +22 -11
  7. package/VERCEL.md +13 -3
  8. package/compatibility-lock.json +357 -0
  9. package/dist/ai-harness/index.js +2 -2
  10. package/dist/api-defaults.d.ts +17 -0
  11. package/dist/api-defaults.js +22 -0
  12. package/dist/blaxel/errors.js +1 -1
  13. package/dist/blaxel/lookup.d.ts +3 -2
  14. package/dist/blaxel/lookup.js +3 -2
  15. package/dist/blaxel/pagination.d.ts +2 -2
  16. package/dist/blaxel/pagination.js +45 -17
  17. package/dist/blaxel/preview.d.ts +3 -2
  18. package/dist/blaxel/preview.js +16 -14
  19. package/dist/blaxel/process.d.ts +1 -1
  20. package/dist/blaxel/process.js +66 -11
  21. package/dist/blaxel/sandbox.d.ts +2 -0
  22. package/dist/blaxel/sandbox.js +2 -0
  23. package/dist/blaxel/snapshot.js +3 -5
  24. package/dist/cli-words.d.ts +27 -0
  25. package/dist/cli-words.js +135 -0
  26. package/dist/cli.d.ts +2 -0
  27. package/dist/cli.js +351 -85
  28. package/dist/client.d.ts +33 -4
  29. package/dist/client.js +47 -12
  30. package/dist/cloudflare/identity.d.ts +7 -0
  31. package/dist/cloudflare/identity.js +13 -0
  32. package/dist/cloudflare/index.d.ts +303 -0
  33. package/dist/cloudflare/index.js +592 -0
  34. package/dist/cloudflare/worker.d.ts +419 -0
  35. package/dist/cloudflare/worker.js +447 -0
  36. package/dist/codesandbox/guest-service.d.ts +31 -0
  37. package/dist/codesandbox/guest-service.js +490 -0
  38. package/dist/codesandbox/index.d.ts +386 -0
  39. package/dist/codesandbox/index.js +1347 -0
  40. package/dist/compat/core.d.ts +40 -0
  41. package/dist/compat/core.js +238 -0
  42. package/dist/compat/files.d.ts +26 -0
  43. package/dist/compat/files.js +133 -0
  44. package/dist/compat/shell.d.ts +4 -0
  45. package/dist/compat/shell.js +165 -0
  46. package/dist/credentials.d.ts +6 -1
  47. package/dist/credentials.js +107 -38
  48. package/dist/daytona/context.js +2 -1
  49. package/dist/daytona/daytona.js +11 -4
  50. package/dist/daytona/filesystem.d.ts +17 -4
  51. package/dist/daytona/filesystem.js +121 -38
  52. package/dist/daytona/index.d.ts +1 -1
  53. package/dist/daytona/process.d.ts +1 -1
  54. package/dist/daytona/process.js +27 -4
  55. package/dist/daytona/sandbox.d.ts +1 -1
  56. package/dist/daytona/sandbox.js +16 -7
  57. package/dist/e2b/client.js +1 -1
  58. package/dist/e2b/code-interpreter.d.ts +4 -5
  59. package/dist/e2b/code-interpreter.js +54 -56
  60. package/dist/e2b/commands.d.ts +35 -12
  61. package/dist/e2b/commands.js +169 -73
  62. package/dist/e2b/errors.js +1 -1
  63. package/dist/e2b/filesystem.d.ts +8 -6
  64. package/dist/e2b/filesystem.js +127 -28
  65. package/dist/e2b/index.d.ts +1 -0
  66. package/dist/e2b/index.js +1 -0
  67. package/dist/e2b/pty.d.ts +24 -0
  68. package/dist/e2b/pty.js +80 -0
  69. package/dist/e2b/sandbox.d.ts +6 -4
  70. package/dist/e2b/sandbox.js +59 -25
  71. package/dist/errors.d.ts +8 -0
  72. package/dist/errors.js +15 -2
  73. package/dist/file-source.d.ts +16 -0
  74. package/dist/file-source.js +162 -0
  75. package/dist/freestyle/index.d.ts +309 -0
  76. package/dist/freestyle/index.js +343 -0
  77. package/dist/index.d.ts +3 -1
  78. package/dist/login.js +16 -10
  79. package/dist/mcp.js +2 -2
  80. package/dist/modal/index.d.ts +205 -0
  81. package/dist/modal/index.js +486 -0
  82. package/dist/network-cli.js +112 -44
  83. package/dist/openai-agents/index.js +18 -71
  84. package/dist/page.d.ts +1 -1
  85. package/dist/page.js +2 -2
  86. package/dist/products/audit.d.ts +3 -0
  87. package/dist/products/index.d.ts +14 -0
  88. package/dist/products/index.js +12 -0
  89. package/dist/products/interpreter.d.ts +11 -4
  90. package/dist/products/interpreter.js +33 -9
  91. package/dist/products/jobs.js +3 -2
  92. package/dist/products/limits.d.ts +10 -0
  93. package/dist/products/observability.d.ts +44 -0
  94. package/dist/products/observability.js +6 -0
  95. package/dist/products/previews.d.ts +18 -4
  96. package/dist/products/previews.js +14 -5
  97. package/dist/products/tailscale.d.ts +57 -0
  98. package/dist/products/tailscale.js +17 -0
  99. package/dist/products/volumes.d.ts +42 -0
  100. package/dist/products/volumes.js +36 -0
  101. package/dist/products/watch.d.ts +25 -4
  102. package/dist/products/watch.js +132 -18
  103. package/dist/proxy.js +3 -1
  104. package/dist/runloop/index.d.ts +326 -0
  105. package/dist/runloop/index.js +535 -0
  106. package/dist/runloop/pagination.d.ts +46 -0
  107. package/dist/runloop/pagination.js +82 -0
  108. package/dist/runloop/snapshots.d.ts +52 -0
  109. package/dist/runloop/snapshots.js +244 -0
  110. package/dist/sandbox.d.ts +76 -16
  111. package/dist/sandbox.js +696 -135
  112. package/dist/snapshots.d.ts +20 -5
  113. package/dist/snapshots.js +11 -1
  114. package/dist/sprites/index.d.ts +251 -0
  115. package/dist/sprites/index.js +588 -0
  116. package/dist/tar.d.ts +10 -1
  117. package/dist/tar.js +311 -35
  118. package/dist/tools/index.js +2 -1
  119. package/dist/transport.d.ts +23 -8
  120. package/dist/transport.js +247 -127
  121. package/dist/types.d.ts +52 -3
  122. package/dist/vercel/command.d.ts +1 -1
  123. package/dist/vercel/command.js +15 -3
  124. package/dist/vercel/errors.d.ts +2 -2
  125. package/dist/vercel/errors.js +19 -2
  126. package/dist/vercel/filesystem.d.ts +21 -18
  127. package/dist/vercel/filesystem.js +95 -48
  128. package/dist/vercel/sandbox.d.ts +8 -8
  129. package/dist/vercel/sandbox.js +159 -64
  130. package/dist/vercel/snapshot.d.ts +3 -2
  131. package/dist/vercel/snapshot.js +9 -5
  132. package/package.json +46 -3
@@ -10,6 +10,16 @@ export type Volume = {
10
10
  labels: Record<string, string>;
11
11
  region: string;
12
12
  sizeMiB: number;
13
+ /** Fixed at creation; shared disks support concurrent writers. */
14
+ shared?: boolean;
15
+ resize?: {
16
+ id: string;
17
+ operationId: string;
18
+ sizeMiB: number;
19
+ state: "pending" | "running" | "completed" | "failed";
20
+ generation: number;
21
+ error: string | null;
22
+ } | null;
13
23
  filesystem: "ext4";
14
24
  usedMiB: number | null;
15
25
  measuredAt: string | null;
@@ -24,6 +34,8 @@ export type Volume = {
24
34
  /** The backup this volume was restored from, if any. */
25
35
  restoredFrom: string | null;
26
36
  attachments: {
37
+ id?: string;
38
+ state?: VolumeAttachment["state"];
27
39
  sandboxId: string;
28
40
  mode: "rw" | "snapshot";
29
41
  path: string;
@@ -34,6 +46,8 @@ export type Volume = {
34
46
  readyAt: string | null;
35
47
  };
36
48
  export type CreateVolume = {
49
+ /** Available where shared disks are enabled. Fixed at creation. */
50
+ shared?: boolean;
37
51
  /** Required unless restoring: a restore is the backup's size. */
38
52
  sizeMiB?: number;
39
53
  /** A ready backup to restore into the new volume, on any host in its region. */
@@ -42,6 +56,18 @@ export type CreateVolume = {
42
56
  labels?: Record<string, string>;
43
57
  region?: string;
44
58
  };
59
+ export type VolumeAttachment = {
60
+ id: string;
61
+ volumeId: string;
62
+ sandboxId: string;
63
+ path: string;
64
+ mode: "rw";
65
+ state: "attaching" | "active" | "detaching" | "detached";
66
+ generation: number;
67
+ error: string | null;
68
+ attachedAt: string;
69
+ changedAt: string;
70
+ };
45
71
  export type VolumeBackupState = "pending" | "ready" | "failed" | "deleting" | "deleted";
46
72
  /** A point-in-time copy of a volume, kept off its host. */
47
73
  export type VolumeBackup = {
@@ -78,6 +104,22 @@ export declare function volumes(t: Transport): {
78
104
  limit?: number;
79
105
  cursor?: string;
80
106
  }, options?: RequestOptions) => Promise<Page<Volume>>;
107
+ /** Grow a detached ordinary disk; wait up to 10 s for the operation. */
108
+ resize: (id: string, input: {
109
+ sizeMiB: number;
110
+ }, options?: RequestOptions & {
111
+ wait?: number;
112
+ }) => Promise<Volume>;
113
+ attach: (id: string, input: {
114
+ sandboxId: string;
115
+ path: string;
116
+ }, options?: RequestOptions & {
117
+ wait?: number;
118
+ }) => Promise<VolumeAttachment>;
119
+ detach: (id: string, attachmentId: string, options?: RequestOptions & {
120
+ wait?: number;
121
+ }) => Promise<VolumeAttachment>;
122
+ getAttachment: (id: string, attachmentId: string, options?: RequestOptions) => Promise<VolumeAttachment>;
81
123
  delete: (id: string, options?: RequestOptions) => Promise<Volume>;
82
124
  /** Back a volume up now, off its host, and wait (up to a minute, or
83
125
  * `wait` seconds) for it to be ready. */
@@ -29,6 +29,42 @@ export function volumes(t) {
29
29
  create: (input, options) => t.json({ method: "POST", path: "/v1/volumes", body: input, wait: 10, ...options }),
30
30
  get,
31
31
  list,
32
+ /** Grow a detached ordinary disk; wait up to 10 s for the operation. */
33
+ resize: (id, input, options = {}) => {
34
+ const { wait = 10, ...rest } = options;
35
+ return t.json({
36
+ method: "POST",
37
+ path: `/v1/volumes/${enc(id)}:resize`,
38
+ body: input,
39
+ wait,
40
+ ...rest,
41
+ });
42
+ },
43
+ attach: (id, input, options = {}) => {
44
+ const { wait = 10, ...rest } = options;
45
+ return t.json({
46
+ method: "POST",
47
+ path: `/v1/volumes/${enc(id)}:attach`,
48
+ body: input,
49
+ wait,
50
+ ...rest,
51
+ });
52
+ },
53
+ detach: (id, attachmentId, options = {}) => {
54
+ const { wait = 10, ...rest } = options;
55
+ return t.json({
56
+ method: "POST",
57
+ path: `/v1/volumes/${enc(id)}/attachments/${enc(attachmentId)}:detach`,
58
+ body: {},
59
+ wait,
60
+ ...rest,
61
+ });
62
+ },
63
+ getAttachment: (id, attachmentId, options) => t.json({
64
+ method: "GET",
65
+ path: `/v1/volumes/${enc(id)}/attachments/${enc(attachmentId)}`,
66
+ ...options,
67
+ }),
32
68
  delete: (id, options) => t.json({
33
69
  method: "POST",
34
70
  path: `/v1/volumes/${enc(id)}:delete`,
@@ -1,5 +1,5 @@
1
1
  import type { RequestOptions, Transport } from "../transport.js";
2
- export type FileEventType = "create" | "write" | "remove" | "rename" | "chmod";
2
+ export type FileEventType = "create" | "write" | "remove" | "rename" | "chmod" | "access";
3
3
  export type FileEvent = {
4
4
  type: FileEventType;
5
5
  /** Absolute path in the sandbox. */
@@ -23,7 +23,7 @@ export type WatchNotice = {
23
23
  };
24
24
  export type WatchOptions = Omit<RequestOptions, "timeoutMs"> & {
25
25
  recursive?: boolean;
26
- /** Only these event types; all five by default. */
26
+ /** Only these event types; the five mutation types by default; access is opt-in. */
27
27
  events?: FileEventType[];
28
28
  /** Globs relative to the path: only matching paths are reported. */
29
29
  include?: string[];
@@ -31,7 +31,7 @@ export type WatchOptions = Omit<RequestOptions, "timeoutMs"> & {
31
31
  exclude?: string[];
32
32
  /** Events are gathered this long and delivered together. Default 100. */
33
33
  batchMs?: number;
34
- /** The watch ends by itself after running this long. Default 1 hour. */
34
+ /** The watch ends by itself after running this long. Default 1 hour; 0 runs until stopped or the sandbox ends. */
35
35
  timeoutMs?: number;
36
36
  maxWatches?: number;
37
37
  /** A batch at a time, rather than one call per event. */
@@ -49,6 +49,16 @@ export type WatchInfo = {
49
49
  processId: string;
50
50
  state: string;
51
51
  startedAt: number;
52
+ /** Its changes go to the account's webhooks as `sandbox.files.changed`. */
53
+ webhook?: boolean;
54
+ };
55
+ /** A watch whose changes Runtime sends to the account's webhooks. */
56
+ export type WebhookWatchOptions = Omit<WatchOptions, "onBatch" | "onNotice" | "onExit" | "timeoutMs"> & {
57
+ /** The watch ends by itself after running this long; 0 (the default here)
58
+ * runs until stopped or the sandbox ends. */
59
+ timeoutMs?: number;
60
+ /** Its own id, 1 to 40 lowercase letters, digits or dashes. */
61
+ id?: string;
52
62
  };
53
63
  /** A running watch. Events arrive through the callbacks until `stop()`. */
54
64
  export declare class WatchHandle {
@@ -65,7 +75,9 @@ export declare class WatchHandle {
65
75
  get done(): Promise<void>;
66
76
  /** Start delivering again from where it left off, after a pause. */
67
77
  resume(): void;
68
- /** Stop the watch in the sandbox, and stop delivering. */
78
+ /** Stop the watch in the sandbox. What changed before the stop is still
79
+ * delivered: the watch reports it and ends, and this returns once that has
80
+ * arrived (at most 5 s). Called from inside a callback, it stops at once. */
69
81
  stop(options?: RequestOptions): Promise<void>;
70
82
  /** @internal */
71
83
  begin(): this;
@@ -85,6 +97,15 @@ export declare function sandboxWatches(t: Transport, sandboxId: string): {
85
97
  lostBytes: number;
86
98
  ended: boolean;
87
99
  }>;
100
+ /** Starts a watch whose changes Runtime sends to the account's webhooks
101
+ * as `sandbox.files.changed` events, with nothing reading it here:
102
+ *
103
+ * await sbx.files.watches.webhook("/workspace/app", { recursive: true });
104
+ *
105
+ * Runs until stopped (timeoutMs 0) unless given a timeout. */
106
+ webhook: (path: string, input?: WebhookWatchOptions) => Promise<WatchInfo & {
107
+ cursor: number;
108
+ }>;
88
109
  stop: (id: string, options?: RequestOptions) => Promise<{
89
110
  stopped: boolean;
90
111
  }>;
@@ -1,3 +1,5 @@
1
+ import { WEBHOOK_WATCH_TIMEOUT_MS } from "../api-defaults.js";
2
+ import { RuntimeError } from "../errors.js";
1
3
  /** A running watch. Events arrive through the callbacks until `stop()`. */
2
4
  export class WatchHandle {
3
5
  t;
@@ -8,7 +10,15 @@ export class WatchHandle {
8
10
  onEvent;
9
11
  cursor;
10
12
  #stopped = false;
13
+ /** stop() was called: deliver what the watch reports until its end. */
14
+ #stopping = false;
15
+ #stopPending;
16
+ #remoteStop;
17
+ /** Callbacks under way. */
18
+ #delivering = 0;
19
+ #exitNotified = false;
11
20
  #following;
21
+ #followingActive = false;
12
22
  #abort = new AbortController();
13
23
  constructor(t, base, id, path, cursor, options, onEvent) {
14
24
  this.t = t;
@@ -25,24 +35,84 @@ export class WatchHandle {
25
35
  }
26
36
  /** Start delivering again from where it left off, after a pause. */
27
37
  resume() {
28
- if (this.#stopped)
38
+ if (this.#stopped || this.#stopping)
29
39
  throw new Error("This watch was stopped");
40
+ if (this.#followingActive)
41
+ throw new Error("This watch is already delivering");
30
42
  this.#abort = new AbortController();
31
43
  this.begin();
32
44
  }
33
- /** Stop the watch in the sandbox, and stop delivering. */
45
+ /** Stop the watch in the sandbox. What changed before the stop is still
46
+ * delivered: the watch reports it and ends, and this returns once that has
47
+ * arrived (at most 5 s). Called from inside a callback, it stops at once. */
34
48
  async stop(options) {
35
49
  if (this.#stopped)
36
50
  return;
51
+ if (this.#stopping) {
52
+ // Even a callback must observe whether the remote delete succeeded.
53
+ // It cannot join the drain, which is waiting for that callback to finish.
54
+ await this.#remoteStop;
55
+ if (this.#delivering === 0)
56
+ await this.#stopPending;
57
+ else
58
+ this.#finishLocalStop();
59
+ return;
60
+ }
61
+ this.#stopping = true;
62
+ this.#remoteStop = (async () => await this.t.json({
63
+ method: "DELETE",
64
+ path: `${this.base}/${encodeURIComponent(this.id)}`,
65
+ ...options,
66
+ }))();
67
+ const pending = this.#finishStop(this.#remoteStop);
68
+ this.#stopPending = pending;
69
+ try {
70
+ await pending;
71
+ }
72
+ finally {
73
+ if (this.#stopPending === pending) {
74
+ this.#stopPending = undefined;
75
+ this.#remoteStop = undefined;
76
+ }
77
+ }
78
+ }
79
+ async #finishStop(remote) {
80
+ try {
81
+ await remote;
82
+ }
83
+ catch (error) {
84
+ // A failed delete does not prove the remote watch stopped. Close local
85
+ // delivery, but let the caller observe the failure and retry the delete.
86
+ this.#abort.abort();
87
+ this.#stopping = false;
88
+ throw error;
89
+ }
90
+ // Inside a callback, #following is awaiting the caller: waiting for it
91
+ // here would make that callback await itself.
92
+ if (this.#following && this.#delivering === 0) {
93
+ let timer;
94
+ await Promise.race([
95
+ this.#following.catch(() => undefined),
96
+ new Promise((resolve) => {
97
+ timer = setTimeout(resolve, 5_000);
98
+ }),
99
+ ]);
100
+ clearTimeout(timer);
101
+ }
102
+ this.#finishLocalStop();
103
+ }
104
+ #finishLocalStop() {
37
105
  this.#stopped = true;
38
106
  this.#abort.abort();
39
- await this.t
40
- .json({ method: "DELETE", path: `${this.base}/${encodeURIComponent(this.id)}`, ...options })
41
- .catch(() => undefined);
42
- await this.#following?.catch(() => undefined);
43
- this.options.onExit?.("stopped");
107
+ this.#notifyExit("stopped");
108
+ }
109
+ #notifyExit(reason) {
110
+ if (this.#exitNotified)
111
+ return;
112
+ this.#exitNotified = true;
113
+ this.options.onExit?.(reason);
44
114
  }
45
- async #follow() {
115
+ async #follow(abort) {
46
116
  for (;;) {
47
117
  let next = "exit";
48
118
  try {
@@ -50,32 +120,52 @@ export class WatchHandle {
50
120
  method: "GET",
51
121
  path: `${this.base}/${encodeURIComponent(this.id)}/events`,
52
122
  query: { cursor: this.cursor, follow: true },
53
- signal: this.#abort.signal,
123
+ signal: abort.signal,
54
124
  timeoutMs: 150_000,
55
125
  })) {
56
126
  this.cursor = event.cursor ?? this.cursor;
57
127
  if (event.k === "events") {
58
- if (this.options.onBatch)
59
- await this.options.onBatch(event.events);
60
- if (this.onEvent)
61
- for (const item of event.events)
62
- await this.onEvent(item);
128
+ this.#delivering++;
129
+ try {
130
+ if (this.options.onBatch)
131
+ await this.options.onBatch(event.events);
132
+ if (this.#stopped)
133
+ return;
134
+ if (this.onEvent)
135
+ for (const item of event.events) {
136
+ await this.onEvent(item);
137
+ if (this.#stopped)
138
+ return;
139
+ }
140
+ }
141
+ finally {
142
+ this.#delivering--;
143
+ }
63
144
  }
64
145
  else if (event.k === "continue")
65
146
  next = "continue";
66
147
  else if (event.k === "end" || event.k === "paused") {
148
+ // A paused callback may resume immediately. Retire this reader
149
+ // before invoking it, without letting its finalizer retire the new one.
150
+ this.#followingActive = false;
67
151
  if (!this.#stopped)
68
- this.options.onExit?.(event.k === "paused" ? "paused" : event.reason);
152
+ this.#notifyExit(event.k === "paused" ? "paused" : event.reason);
69
153
  return;
70
154
  }
71
155
  else if (event.k === "failure")
72
- throw new Error(`${event.code}: ${event.message}`);
156
+ throw new RuntimeError({
157
+ code: event.code,
158
+ status: event.status ?? 0,
159
+ message: event.message,
160
+ ...(event.hint ? { hint: event.hint } : {}),
161
+ ...(event.requestId ? { requestId: event.requestId } : {}),
162
+ });
73
163
  else
74
164
  this.options.onNotice?.(event);
75
165
  }
76
166
  }
77
167
  catch (error) {
78
- if (this.#stopped || this.#abort.signal.aborted)
168
+ if (this.#stopped || abort.signal.aborted)
79
169
  return;
80
170
  throw error;
81
171
  }
@@ -85,7 +175,15 @@ export class WatchHandle {
85
175
  }
86
176
  /** @internal */
87
177
  begin() {
88
- this.#following = this.#follow();
178
+ if (this.#followingActive)
179
+ throw new Error("This watch is already delivering");
180
+ this.#exitNotified = false;
181
+ this.#followingActive = true;
182
+ const abort = this.#abort;
183
+ this.#following = this.#follow(abort).finally(() => {
184
+ if (this.#abort === abort)
185
+ this.#followingActive = false;
186
+ });
89
187
  // An error surfaces through `done`; nothing is left unhandled meanwhile.
90
188
  this.#following.catch(() => undefined);
91
189
  return this;
@@ -126,6 +224,22 @@ export function sandboxWatches(t, sandboxId) {
126
224
  query: { cursor, ...(input.waitMs !== undefined ? { waitMs: input.waitMs } : {}) },
127
225
  ...options,
128
226
  }),
227
+ /** Starts a watch whose changes Runtime sends to the account's webhooks
228
+ * as `sandbox.files.changed` events, with nothing reading it here:
229
+ *
230
+ * await sbx.files.watches.webhook("/workspace/app", { recursive: true });
231
+ *
232
+ * Runs until stopped (timeoutMs 0) unless given a timeout. */
233
+ webhook: (path, input = {}) => {
234
+ const { idempotencyKey, signal, timeoutMs, ...rest } = input;
235
+ return t.json({
236
+ method: "POST",
237
+ path: base,
238
+ body: { path, ...rest, timeoutMs: timeoutMs ?? WEBHOOK_WATCH_TIMEOUT_MS, webhook: true },
239
+ ...(idempotencyKey ? { idempotencyKey } : {}),
240
+ ...(signal ? { signal } : {}),
241
+ });
242
+ },
129
243
  stop: (id, options) => t.json({
130
244
  method: "DELETE",
131
245
  path: `${base}/${encodeURIComponent(id)}`,
package/dist/proxy.js CHANGED
@@ -72,7 +72,9 @@ export function routeFor(target, env = processEnv()) {
72
72
  parsed = new URL(proxy);
73
73
  }
74
74
  catch {
75
- throw invalidProxy(found.name, found.value, "is not an address");
75
+ // A malformed address cannot be parsed reliably enough to remove its
76
+ // credentials. Keep the variable name, never its untrusted value.
77
+ throw invalidProxy(found.name, "invalid address", "is not an address");
76
78
  }
77
79
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
78
80
  throw invalidProxy(found.name, `${parsed.protocol}//${parsed.host}`, "is not an HTTP proxy");