@appium/execute-driver-plugin 6.0.1 → 6.0.2

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
@@ -14,7 +14,7 @@ Running a driver script in a child process adds a degree of parallelisation, whi
14
14
  faster test execution.
15
15
 
16
16
  > [!WARNING]
17
- > This plugin enables execution of arbitrary JavaScript code. We recommend only using this plugin in a controlled environment.
17
+ > This plugin enables execution of arbitrary JavaScript code. We recommend only using this plugin in a controlled environment. Scripts run in a Node.js `vm` context with a hardened view of the WebdriverIO driver (host-realm prototype metadata is not exposed), but `vm` is still not a full security boundary for untrusted code; treat `--allow-insecure=…:execute_driver_script` as highly privileged.
18
18
 
19
19
  ## Installation
20
20
 
@@ -1 +1 @@
1
- {"version":3,"file":"execute-child.d.ts","sourceRoot":"","sources":["../../lib/execute-child.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,eAAe,wCAAkC,CAAC;AAC/D,eAAO,MAAM,mBAAmB,YAAY,CAAC"}
1
+ {"version":3,"file":"execute-child.d.ts","sourceRoot":"","sources":["../../lib/execute-child.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,eAAe,wCAAkC,CAAC;AAC/D,eAAO,MAAM,mBAAmB,YAAY,CAAC"}
@@ -8,6 +8,7 @@ const lodash_1 = __importDefault(require("lodash"));
8
8
  const node_vm_1 = __importDefault(require("node:vm"));
9
9
  const node_util_1 = require("node:util");
10
10
  const support_1 = require("appium/support");
11
+ const vm_host_binding_1 = require("./vm-host-binding");
11
12
  const log = support_1.logger.getLogger('ExecuteDriver Child');
12
13
  let send;
13
14
  // duplicate defining these keys here so we don't need to re-load a huge appium
@@ -34,17 +35,27 @@ async function runScript(eventParams) {
34
35
  log: [],
35
36
  };
36
37
  const consoleFns = {
37
- error: (...logMsgs) => logs.error.push(...logMsgs),
38
- warn: (...logMsgs) => logs.warn.push(...logMsgs),
39
- log: (...logMsgs) => logs.log.push(...logMsgs),
38
+ error: (0, vm_host_binding_1.wrapHostBindingForVmContext)((...logMsgs) => logs.error.push(...logMsgs)),
39
+ warn: (0, vm_host_binding_1.wrapHostBindingForVmContext)((...logMsgs) => logs.warn.push(...logMsgs)),
40
+ log: (0, vm_host_binding_1.wrapHostBindingForVmContext)((...logMsgs) => logs.log.push(...logMsgs)),
40
41
  };
41
42
  const { attach } = await import('webdriverio');
42
43
  const driver = await attach(driverOpts);
44
+ const sandboxDriver = (0, vm_host_binding_1.wrapHostBindingForVmContext)(driver);
45
+ const sandboxConsole = (0, vm_host_binding_1.wrapHostBindingForVmContext)(consoleFns);
46
+ const sandboxSetTimeout = (0, vm_host_binding_1.wrapHostBindingForVmContext)(setTimeout);
47
+ const sandboxClearTimeout = (0, vm_host_binding_1.wrapHostBindingForVmContext)(clearTimeout);
43
48
  const fullScript = `(async () => {${script}})();`;
44
49
  log.info('Running driver script in Node vm');
45
50
  // run the driver script, giving user access to the driver object, a fake console logger,
46
- // and standard setTimeout/clearTimeout functions
47
- let result = await node_vm_1.default.runInNewContext(fullScript, { driver, console: consoleFns, setTimeout, clearTimeout }, { timeout: timeoutMs, breakOnSigint: true });
51
+ // and standard setTimeout/clearTimeout functions. Each host value is proxied so
52
+ // main-realm prototype metadata cannot be used to obtain the host `Function` (VM escape).
53
+ let result = await node_vm_1.default.runInNewContext(fullScript, {
54
+ driver: sandboxDriver,
55
+ console: sandboxConsole,
56
+ setTimeout: sandboxSetTimeout,
57
+ clearTimeout: sandboxClearTimeout,
58
+ }, { timeout: timeoutMs, breakOnSigint: true });
48
59
  result = coerceScriptResult(result);
49
60
  log.info('Successfully ensured driver script result is appropriate type for return');
50
61
  return { result, logs };
@@ -1 +1 @@
1
- {"version":3,"file":"execute-child.js","sourceRoot":"","sources":["../../lib/execute-child.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"execute-child.js","sourceRoot":"","sources":["../../lib/execute-child.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @fileoverview Bridges host-realm (child process) objects into Node's `vm.runInNewContext` without
3
+ * leaking main-realm prototype metadata that untrusted script could use to obtain the real
4
+ * `Function` constructor and escape the VM (RCE on the Appium host).
5
+ *
6
+ * ## Why this exists
7
+ *
8
+ * `vm` isolates *globals* and bytecode, but any **host object** you inject still carries the
9
+ * **main V8 realm's** prototypes.
10
+ *
11
+ * ## Strategy (high level)
12
+ *
13
+ * 1. **Deep `Proxy`**: Every host object/function exposed to the script is wrapped. Property
14
+ * reads (`get`), call results (`apply` / `construct`), and—where allowed—descriptor reflection
15
+ * (`getOwnPropertyDescriptor`) funnel return values through `wrapIfNeeded`, which recursively
16
+ * wraps objects and functions so nested references never surface as raw host callables.
17
+ *
18
+ * 2. **Prototype-adjacent keys**: `constructor` and `__proto__` are not read from the target;
19
+ * the `get` trap returns a frozen null-prototype sentinel, `getPrototypeOf` always reports that
20
+ * sentinel (not the real prototype chain), `has` hides those keys, and `setPrototypeOf` is
21
+ * rejected—closing the usual `…constructor.constructor` chains.
22
+ *
23
+ * 3. **Identity**: `targetToProxy` is a `WeakMap` from each host object/function to its single
24
+ * proxy, so repeated reads (e.g. `driver.m === driver.m`) stay stable and cycles do not recurse
25
+ * forever. `proxyToTarget` reverses the mapping so `Reflect.get`/`apply` can unwrap `this` and
26
+ * pass the real host receiver to accessors and methods that expect it.
27
+ *
28
+ * 4. **Promises**: A `Proxy` around a native `Promise` breaks V8's internal `then` branding
29
+ * (WebdriverIO breaks). Host promises are therefore surfaced as a **null-prototype thenable**
30
+ * that forwards to the real promise and runs `wrapIfNeeded` on fulfilled/rejected values before
31
+ * VM continuations run. One thenable object per promise lives in `promiseToThenableHost`.
32
+ *
33
+ * 5. **Descriptors**: For **configurable** own properties only, `getOwnPropertyDescriptor` returns
34
+ * descriptors whose `value` / `get` / `set` are wrapped—so descriptor-based extraction of a
35
+ * method still yields a sandbox proxy. **Non-configurable** properties must return the real
36
+ * descriptor unchanged (ECMAScript Proxy invariants); those paths can still expose raw host
37
+ * callables until the script uses normal property access, which remains wrapped.
38
+ *
39
+ * 6. **`defineProperty`**: Incoming descriptors from the VM may reference our proxies; fields are
40
+ * unwrapped before `Reflect.defineProperty` so the host object receives real functions/objects.
41
+ *
42
+ * This is defense in depth: Node documents that `vm` is not a full security boundary. Treat
43
+ * `--allow-insecure=…:execute_driver_script` as highly privileged regardless of this module.
44
+ */
45
+ /**
46
+ * Any host-realm callable (methods, timers, `console.log`, etc.) that may be wrapped
47
+ * and passed into the VM alongside plain objects.
48
+ */
49
+ type HostCallable = (...args: unknown[]) => unknown;
50
+ type HostTarget = object | HostCallable;
51
+ /**
52
+ * Entry point: wrap a host object or function for use as a global in `vm.runInNewContext`.
53
+ *
54
+ * Delegates to {@link wrapDeep}; see the file-level overview for behavior and limitations.
55
+ *
56
+ * @typeParam T - Host object or function type (returned value is proxied but typed as `T`).
57
+ * @param hostValue - Root binding injected into the VM (e.g. WebdriverIO `driver`, `console`).
58
+ * @returns A proxy whose transitive property/call/descriptor surfaces hide host `Function` leaks.
59
+ */
60
+ export declare function wrapHostBindingForVmContext<T extends HostTarget>(hostValue: T): T;
61
+ export {};
62
+ //# sourceMappingURL=vm-host-binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vm-host-binding.d.ts","sourceRoot":"","sources":["../../lib/vm-host-binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AASH;;;GAGG;AACH,KAAK,YAAY,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;AACpD,KAAK,UAAU,GAAG,MAAM,GAAG,YAAY,CAAC;AAexC;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,UAAU,EAAE,SAAS,EAAE,CAAC,GAAG,CAAC,CAEjF"}
@@ -0,0 +1,327 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Bridges host-realm (child process) objects into Node's `vm.runInNewContext` without
4
+ * leaking main-realm prototype metadata that untrusted script could use to obtain the real
5
+ * `Function` constructor and escape the VM (RCE on the Appium host).
6
+ *
7
+ * ## Why this exists
8
+ *
9
+ * `vm` isolates *globals* and bytecode, but any **host object** you inject still carries the
10
+ * **main V8 realm's** prototypes.
11
+ *
12
+ * ## Strategy (high level)
13
+ *
14
+ * 1. **Deep `Proxy`**: Every host object/function exposed to the script is wrapped. Property
15
+ * reads (`get`), call results (`apply` / `construct`), and—where allowed—descriptor reflection
16
+ * (`getOwnPropertyDescriptor`) funnel return values through `wrapIfNeeded`, which recursively
17
+ * wraps objects and functions so nested references never surface as raw host callables.
18
+ *
19
+ * 2. **Prototype-adjacent keys**: `constructor` and `__proto__` are not read from the target;
20
+ * the `get` trap returns a frozen null-prototype sentinel, `getPrototypeOf` always reports that
21
+ * sentinel (not the real prototype chain), `has` hides those keys, and `setPrototypeOf` is
22
+ * rejected—closing the usual `…constructor.constructor` chains.
23
+ *
24
+ * 3. **Identity**: `targetToProxy` is a `WeakMap` from each host object/function to its single
25
+ * proxy, so repeated reads (e.g. `driver.m === driver.m`) stay stable and cycles do not recurse
26
+ * forever. `proxyToTarget` reverses the mapping so `Reflect.get`/`apply` can unwrap `this` and
27
+ * pass the real host receiver to accessors and methods that expect it.
28
+ *
29
+ * 4. **Promises**: A `Proxy` around a native `Promise` breaks V8's internal `then` branding
30
+ * (WebdriverIO breaks). Host promises are therefore surfaced as a **null-prototype thenable**
31
+ * that forwards to the real promise and runs `wrapIfNeeded` on fulfilled/rejected values before
32
+ * VM continuations run. One thenable object per promise lives in `promiseToThenableHost`.
33
+ *
34
+ * 5. **Descriptors**: For **configurable** own properties only, `getOwnPropertyDescriptor` returns
35
+ * descriptors whose `value` / `get` / `set` are wrapped—so descriptor-based extraction of a
36
+ * method still yields a sandbox proxy. **Non-configurable** properties must return the real
37
+ * descriptor unchanged (ECMAScript Proxy invariants); those paths can still expose raw host
38
+ * callables until the script uses normal property access, which remains wrapped.
39
+ *
40
+ * 6. **`defineProperty`**: Incoming descriptors from the VM may reference our proxies; fields are
41
+ * unwrapped before `Reflect.defineProperty` so the host object receives real functions/objects.
42
+ *
43
+ * This is defense in depth: Node documents that `vm` is not a full security boundary. Treat
44
+ * `--allow-insecure=…:execute_driver_script` as highly privileged regardless of this module.
45
+ */
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.wrapHostBindingForVmContext = wrapHostBindingForVmContext;
48
+ /**
49
+ * Null-prototype object returned for prototype-adjacent property lookups on
50
+ * proxied host values. It must not inherit `Object.prototype` (which would
51
+ * reintroduce `constructor` / `__proto__` chains back to the host `Function`).
52
+ */
53
+ const SAFE_LOOKUP_TARGET = Object.freeze(Object.create(null));
54
+ /** Host target → single deep proxy (stable identity, cycle-safe). */
55
+ const targetToProxy = new WeakMap();
56
+ /** Deep proxy → underlying host target (unwrap `this` / receivers / defineProperty). */
57
+ const proxyToTarget = new WeakMap();
58
+ /**
59
+ * Host `Promise` → null-prototype thenable facade (one per promise; then wrapped by `wrapDeep`).
60
+ * Native promises cannot be proxied without breaking `Promise.prototype.then` receiver checks.
61
+ */
62
+ const promiseToThenableHost = new WeakMap();
63
+ /**
64
+ * Entry point: wrap a host object or function for use as a global in `vm.runInNewContext`.
65
+ *
66
+ * Delegates to {@link wrapDeep}; see the file-level overview for behavior and limitations.
67
+ *
68
+ * @typeParam T - Host object or function type (returned value is proxied but typed as `T`).
69
+ * @param hostValue - Root binding injected into the VM (e.g. WebdriverIO `driver`, `console`).
70
+ * @returns A proxy whose transitive property/call/descriptor surfaces hide host `Function` leaks.
71
+ */
72
+ function wrapHostBindingForVmContext(hostValue) {
73
+ return wrapDeep(hostValue);
74
+ }
75
+ /**
76
+ * @returns Whether `value` is a proxy created by this module (`proxyToTarget` has an entry).
77
+ * Includes both object proxies and function proxies.
78
+ */
79
+ function isOurProxy(value) {
80
+ return value !== null && (typeof value === 'object' || typeof value === 'function') && proxyToTarget.has(value);
81
+ }
82
+ /**
83
+ * Maps a value that may be our deep proxy back to the underlying host target for host APIs.
84
+ *
85
+ * @param value - Possibly a proxy from this module, or any other value.
86
+ * @returns The host target if `value` is our proxy; otherwise `value` unchanged.
87
+ */
88
+ function unwrapIfProxy(value) {
89
+ if (isOurProxy(value)) {
90
+ return proxyToTarget.get(value);
91
+ }
92
+ return value;
93
+ }
94
+ /** Property keys that must never resolve through the real target (VM escape vectors). */
95
+ function isBlockedPrototypeKey(prop) {
96
+ return prop === 'constructor' || prop === '__proto__';
97
+ }
98
+ /** Whether `value` is a native host Promise (handled via thenable facade, not `wrapDeep` alone). */
99
+ function isNativePromise(value) {
100
+ return value instanceof Promise;
101
+ }
102
+ /**
103
+ * Wraps a host Promise by exposing a null-prototype thenable that delegates to `p` and ensures
104
+ * fulfillment/rejection values are passed through {@link wrapIfNeeded} before VM callbacks run.
105
+ *
106
+ * @param p - Host promise returned from driver or other host APIs.
107
+ * @returns A deep-proxied thenable safe for `await` / `.then` from inside the VM.
108
+ */
109
+ function wrapPromiseAsThenable(p) {
110
+ const cached = promiseToThenableHost.get(p);
111
+ const hostObj = cached ??
112
+ (() => {
113
+ /* eslint-disable promise/prefer-await-to-then -- thenable facade over a host Promise */
114
+ const o = Object.assign(Object.create(null), {
115
+ then(onFulfilled, onRejected) {
116
+ const adaptFulfill = onFulfilled != null && typeof onFulfilled === 'function'
117
+ ? (v) => Reflect.apply(onFulfilled, undefined, [wrapIfNeeded(v)])
118
+ : (v) => wrapIfNeeded(v);
119
+ const adaptReject = onRejected != null && typeof onRejected === 'function'
120
+ ? (e) => Reflect.apply(onRejected, undefined, [wrapIfNeeded(e)])
121
+ : undefined;
122
+ return wrapIfNeeded(p.then(adaptFulfill, adaptReject));
123
+ },
124
+ catch(onRejected) {
125
+ return wrapIfNeeded(p.catch((e) => onRejected != null && typeof onRejected === 'function'
126
+ ? Reflect.apply(onRejected, undefined, [wrapIfNeeded(e)])
127
+ : Promise.reject(wrapIfNeeded(e))));
128
+ },
129
+ finally(onFinally) {
130
+ return wrapIfNeeded(p.finally(onFinally));
131
+ },
132
+ });
133
+ /* eslint-enable promise/prefer-await-to-then */
134
+ promiseToThenableHost.set(p, o);
135
+ return o;
136
+ })();
137
+ return wrapDeep(hostObj);
138
+ }
139
+ /**
140
+ * Clones a **configurable** property descriptor so `value` / `get` / `set` are wrapped for the VM.
141
+ *
142
+ * @param d - Descriptor from `Reflect.getOwnPropertyDescriptor` on the host target.
143
+ * @returns A new descriptor safe to return from the proxy `getOwnPropertyDescriptor` trap.
144
+ */
145
+ function mapDescriptorForSandbox(descriptor) {
146
+ if ('value' in descriptor) {
147
+ return {
148
+ configurable: descriptor.configurable,
149
+ enumerable: descriptor.enumerable,
150
+ writable: descriptor.writable,
151
+ value: wrapIfNeeded(descriptor.value),
152
+ };
153
+ }
154
+ return {
155
+ configurable: descriptor.configurable,
156
+ enumerable: descriptor.enumerable,
157
+ get: descriptor.get ? wrapIfNeeded(descriptor.get) : undefined,
158
+ set: descriptor.set ? wrapIfNeeded(descriptor.set) : undefined,
159
+ };
160
+ }
161
+ /**
162
+ * Prepares a property descriptor coming **from** the VM so `Reflect.defineProperty` on the host
163
+ * receives real targets, not our proxy objects.
164
+ *
165
+ * @param descriptor - Descriptor passed into the proxy `defineProperty` trap.
166
+ * @returns Descriptor with `value` / `get` / `set` unwrapped where they were our proxies.
167
+ */
168
+ function mapDescriptorForHost(descriptor) {
169
+ const mapped = {};
170
+ if ('configurable' in descriptor) {
171
+ mapped.configurable = descriptor.configurable;
172
+ }
173
+ if ('enumerable' in descriptor) {
174
+ mapped.enumerable = descriptor.enumerable;
175
+ }
176
+ if ('writable' in descriptor) {
177
+ mapped.writable = descriptor.writable;
178
+ }
179
+ if ('value' in descriptor) {
180
+ mapped.value = unwrapIfProxy(descriptor.value);
181
+ }
182
+ if ('get' in descriptor) {
183
+ mapped.get = unwrapIfProxy(descriptor.get);
184
+ }
185
+ if ('set' in descriptor) {
186
+ mapped.set = unwrapIfProxy(descriptor.set);
187
+ }
188
+ return mapped;
189
+ }
190
+ /**
191
+ * Unwraps the proxy when used as the `receiver` for `Reflect.get`, so host getters see real `this`.
192
+ */
193
+ function reflectReceiver(receiver) {
194
+ return unwrapIfProxy(receiver);
195
+ }
196
+ /**
197
+ * Unwraps constructor references passed to `Reflect.construct`.
198
+ *
199
+ * @param newTarget - Constructor received by the proxy `construct` trap.
200
+ * @returns Host constructor with this module's proxy removed when applicable.
201
+ */
202
+ function unwrapConstructor(newTarget) {
203
+ return unwrapIfProxy(newTarget);
204
+ }
205
+ /**
206
+ * Unwraps each argument before forwarding calls into host functions so VM-facing proxies
207
+ * round-trip back to their original host targets.
208
+ *
209
+ * @param argList - Arguments received by the proxy `apply` trap.
210
+ * @returns Arguments with this module's proxies replaced by underlying host targets.
211
+ */
212
+ function unwrapArgList(argList) {
213
+ return argList.map((arg) => unwrapIfProxy(arg));
214
+ }
215
+ /**
216
+ * Builds the shared `ProxyHandler` used for every deep wrap (objects and functions).
217
+ *
218
+ * Traps implement the file-level strategy: hide prototype edges, wrap outgoing values, unwrap
219
+ * incoming `this` / descriptors where required by invariants.
220
+ */
221
+ function createDeepHandler() {
222
+ return {
223
+ apply(proxyTarget, thisArg, argArray) {
224
+ const hostFn = proxyTarget;
225
+ const hostThis = unwrapIfProxy(thisArg);
226
+ const hostArgs = unwrapArgList(argArray);
227
+ const result = Reflect.apply(hostFn, hostThis, hostArgs);
228
+ return wrapIfNeeded(result);
229
+ },
230
+ construct(proxyTarget, argArray, newTarget) {
231
+ const hostCtor = proxyTarget;
232
+ const hostArgs = unwrapArgList(argArray);
233
+ const hostNewTarget = unwrapConstructor(newTarget);
234
+ const result = Reflect.construct(hostCtor, hostArgs, hostNewTarget);
235
+ return wrapIfNeeded(result);
236
+ },
237
+ defineProperty(target, prop, descriptor) {
238
+ return Reflect.defineProperty(target, prop, mapDescriptorForHost(descriptor));
239
+ },
240
+ get(target, prop, receiver) {
241
+ if (isBlockedPrototypeKey(prop)) {
242
+ return SAFE_LOOKUP_TARGET;
243
+ }
244
+ const value = Reflect.get(target, prop, reflectReceiver(receiver));
245
+ return wrapIfNeeded(value);
246
+ },
247
+ getOwnPropertyDescriptor(target, prop) {
248
+ if (isBlockedPrototypeKey(prop)) {
249
+ return {
250
+ configurable: true,
251
+ enumerable: false,
252
+ value: SAFE_LOOKUP_TARGET,
253
+ writable: false,
254
+ };
255
+ }
256
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
257
+ if (!descriptor) {
258
+ return undefined;
259
+ }
260
+ // Non-configurable properties must keep a descriptor compatible with the target
261
+ // (SameValue rules for getters / values); wrapping would violate Proxy invariants.
262
+ if (!descriptor.configurable) {
263
+ return descriptor;
264
+ }
265
+ return mapDescriptorForSandbox(descriptor);
266
+ },
267
+ getPrototypeOf(target) {
268
+ void target;
269
+ return SAFE_LOOKUP_TARGET;
270
+ },
271
+ has(target, prop) {
272
+ if (isBlockedPrototypeKey(prop)) {
273
+ return false;
274
+ }
275
+ return Reflect.has(target, prop);
276
+ },
277
+ setPrototypeOf(target, prototype) {
278
+ void target;
279
+ void prototype;
280
+ return false;
281
+ },
282
+ };
283
+ }
284
+ /**
285
+ * Returns the one deep proxy for this host target, creating and caching it on first use.
286
+ *
287
+ * @typeParam T - Host object or function type.
288
+ * @param hostValue - Raw host reference (not a primitive).
289
+ * @returns Cached or new proxy for `hostValue`.
290
+ */
291
+ function wrapDeep(hostValue) {
292
+ if (typeof hostValue !== 'object' && typeof hostValue !== 'function') {
293
+ return hostValue;
294
+ }
295
+ if (isOurProxy(hostValue)) {
296
+ return hostValue;
297
+ }
298
+ const cached = targetToProxy.get(hostValue);
299
+ if (cached) {
300
+ return cached;
301
+ }
302
+ const handler = createDeepHandler();
303
+ const proxy = new Proxy(hostValue, handler);
304
+ targetToProxy.set(hostValue, proxy);
305
+ proxyToTarget.set(proxy, hostValue);
306
+ return proxy;
307
+ }
308
+ /**
309
+ * Recursively wraps objects and functions; leaves primitives unchanged; routes Promises to the
310
+ * thenable facade.
311
+ *
312
+ * @param value - Result of a `get` / `apply` / descriptor field, or any nested host value.
313
+ * @returns Wrapped proxy, thenable-wrapped promise pipeline, or the original primitive.
314
+ */
315
+ function wrapIfNeeded(value) {
316
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
317
+ return value;
318
+ }
319
+ if (typeof value === 'function') {
320
+ return wrapDeep(value);
321
+ }
322
+ if (isNativePromise(value)) {
323
+ return wrapPromiseAsThenable(value);
324
+ }
325
+ return wrapDeep(value);
326
+ }
327
+ //# sourceMappingURL=vm-host-binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vm-host-binding.js","sourceRoot":"","sources":["../../lib/vm-host-binding.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ import vm from 'node:vm';
3
3
  import {promisify} from 'node:util';
4
4
  import {logger, util} from 'appium/support';
5
5
  import type {DriverScriptMessageEvent, ScriptResult, RunScriptResult} from './types';
6
+ import {wrapHostBindingForVmContext} from './vm-host-binding';
6
7
 
7
8
  const log = logger.getLogger('ExecuteDriver Child');
8
9
  let send: (res: ScriptResult) => Promise<void>;
@@ -33,24 +34,34 @@ async function runScript(eventParams: DriverScriptMessageEvent): Promise<RunScri
33
34
  log: [],
34
35
  };
35
36
  const consoleFns: {error: (...args: any[]) => void; warn: (...args: any[]) => void; log: (...args: any[]) => void} = {
36
- error: (...logMsgs) => logs.error.push(...logMsgs),
37
- warn: (...logMsgs) => logs.warn.push(...logMsgs),
38
- log: (...logMsgs) => logs.log.push(...logMsgs),
37
+ error: wrapHostBindingForVmContext((...logMsgs) => logs.error.push(...logMsgs)),
38
+ warn: wrapHostBindingForVmContext((...logMsgs) => logs.warn.push(...logMsgs)),
39
+ log: wrapHostBindingForVmContext((...logMsgs) => logs.log.push(...logMsgs)),
39
40
  };
40
41
 
41
42
  const {attach} = await import('webdriverio');
42
43
 
43
44
  const driver = await attach(driverOpts);
45
+ const sandboxDriver = wrapHostBindingForVmContext(driver);
46
+ const sandboxConsole = wrapHostBindingForVmContext(consoleFns);
47
+ const sandboxSetTimeout = wrapHostBindingForVmContext(setTimeout);
48
+ const sandboxClearTimeout = wrapHostBindingForVmContext(clearTimeout);
44
49
 
45
50
  const fullScript = `(async () => {${script}})();`;
46
51
 
47
52
  log.info('Running driver script in Node vm');
48
53
 
49
54
  // run the driver script, giving user access to the driver object, a fake console logger,
50
- // and standard setTimeout/clearTimeout functions
55
+ // and standard setTimeout/clearTimeout functions. Each host value is proxied so
56
+ // main-realm prototype metadata cannot be used to obtain the host `Function` (VM escape).
51
57
  let result = await vm.runInNewContext(
52
58
  fullScript,
53
- {driver, console: consoleFns, setTimeout, clearTimeout},
59
+ {
60
+ driver: sandboxDriver,
61
+ console: sandboxConsole,
62
+ setTimeout: sandboxSetTimeout,
63
+ clearTimeout: sandboxClearTimeout,
64
+ },
54
65
  {timeout: timeoutMs, breakOnSigint: true}
55
66
  );
56
67
 
@@ -0,0 +1,370 @@
1
+ /**
2
+ * @fileoverview Bridges host-realm (child process) objects into Node's `vm.runInNewContext` without
3
+ * leaking main-realm prototype metadata that untrusted script could use to obtain the real
4
+ * `Function` constructor and escape the VM (RCE on the Appium host).
5
+ *
6
+ * ## Why this exists
7
+ *
8
+ * `vm` isolates *globals* and bytecode, but any **host object** you inject still carries the
9
+ * **main V8 realm's** prototypes.
10
+ *
11
+ * ## Strategy (high level)
12
+ *
13
+ * 1. **Deep `Proxy`**: Every host object/function exposed to the script is wrapped. Property
14
+ * reads (`get`), call results (`apply` / `construct`), and—where allowed—descriptor reflection
15
+ * (`getOwnPropertyDescriptor`) funnel return values through `wrapIfNeeded`, which recursively
16
+ * wraps objects and functions so nested references never surface as raw host callables.
17
+ *
18
+ * 2. **Prototype-adjacent keys**: `constructor` and `__proto__` are not read from the target;
19
+ * the `get` trap returns a frozen null-prototype sentinel, `getPrototypeOf` always reports that
20
+ * sentinel (not the real prototype chain), `has` hides those keys, and `setPrototypeOf` is
21
+ * rejected—closing the usual `…constructor.constructor` chains.
22
+ *
23
+ * 3. **Identity**: `targetToProxy` is a `WeakMap` from each host object/function to its single
24
+ * proxy, so repeated reads (e.g. `driver.m === driver.m`) stay stable and cycles do not recurse
25
+ * forever. `proxyToTarget` reverses the mapping so `Reflect.get`/`apply` can unwrap `this` and
26
+ * pass the real host receiver to accessors and methods that expect it.
27
+ *
28
+ * 4. **Promises**: A `Proxy` around a native `Promise` breaks V8's internal `then` branding
29
+ * (WebdriverIO breaks). Host promises are therefore surfaced as a **null-prototype thenable**
30
+ * that forwards to the real promise and runs `wrapIfNeeded` on fulfilled/rejected values before
31
+ * VM continuations run. One thenable object per promise lives in `promiseToThenableHost`.
32
+ *
33
+ * 5. **Descriptors**: For **configurable** own properties only, `getOwnPropertyDescriptor` returns
34
+ * descriptors whose `value` / `get` / `set` are wrapped—so descriptor-based extraction of a
35
+ * method still yields a sandbox proxy. **Non-configurable** properties must return the real
36
+ * descriptor unchanged (ECMAScript Proxy invariants); those paths can still expose raw host
37
+ * callables until the script uses normal property access, which remains wrapped.
38
+ *
39
+ * 6. **`defineProperty`**: Incoming descriptors from the VM may reference our proxies; fields are
40
+ * unwrapped before `Reflect.defineProperty` so the host object receives real functions/objects.
41
+ *
42
+ * This is defense in depth: Node documents that `vm` is not a full security boundary. Treat
43
+ * `--allow-insecure=…:execute_driver_script` as highly privileged regardless of this module.
44
+ */
45
+
46
+ /**
47
+ * Null-prototype object returned for prototype-adjacent property lookups on
48
+ * proxied host values. It must not inherit `Object.prototype` (which would
49
+ * reintroduce `constructor` / `__proto__` chains back to the host `Function`).
50
+ */
51
+ const SAFE_LOOKUP_TARGET: object = Object.freeze(Object.create(null));
52
+
53
+ /**
54
+ * Any host-realm callable (methods, timers, `console.log`, etc.) that may be wrapped
55
+ * and passed into the VM alongside plain objects.
56
+ */
57
+ type HostCallable = (...args: unknown[]) => unknown;
58
+ type HostTarget = object | HostCallable;
59
+ type HostConstructor = new (...args: unknown[]) => object;
60
+
61
+ /** Host target → single deep proxy (stable identity, cycle-safe). */
62
+ const targetToProxy = new WeakMap<HostTarget, object>();
63
+
64
+ /** Deep proxy → underlying host target (unwrap `this` / receivers / defineProperty). */
65
+ const proxyToTarget = new WeakMap<object, HostTarget>();
66
+
67
+ /**
68
+ * Host `Promise` → null-prototype thenable facade (one per promise; then wrapped by `wrapDeep`).
69
+ * Native promises cannot be proxied without breaking `Promise.prototype.then` receiver checks.
70
+ */
71
+ const promiseToThenableHost = new WeakMap<Promise<unknown>, object>();
72
+
73
+ /**
74
+ * Entry point: wrap a host object or function for use as a global in `vm.runInNewContext`.
75
+ *
76
+ * Delegates to {@link wrapDeep}; see the file-level overview for behavior and limitations.
77
+ *
78
+ * @typeParam T - Host object or function type (returned value is proxied but typed as `T`).
79
+ * @param hostValue - Root binding injected into the VM (e.g. WebdriverIO `driver`, `console`).
80
+ * @returns A proxy whose transitive property/call/descriptor surfaces hide host `Function` leaks.
81
+ */
82
+ export function wrapHostBindingForVmContext<T extends HostTarget>(hostValue: T): T {
83
+ return wrapDeep(hostValue);
84
+ }
85
+
86
+ /**
87
+ * @returns Whether `value` is a proxy created by this module (`proxyToTarget` has an entry).
88
+ * Includes both object proxies and function proxies.
89
+ */
90
+ function isOurProxy(value: unknown): value is HostTarget {
91
+ return value !== null && (typeof value === 'object' || typeof value === 'function') && proxyToTarget.has(value);
92
+ }
93
+
94
+ /**
95
+ * Maps a value that may be our deep proxy back to the underlying host target for host APIs.
96
+ *
97
+ * @param value - Possibly a proxy from this module, or any other value.
98
+ * @returns The host target if `value` is our proxy; otherwise `value` unchanged.
99
+ */
100
+ function unwrapIfProxy<T>(value: T): T {
101
+ if (isOurProxy(value)) {
102
+ return proxyToTarget.get(value) as T;
103
+ }
104
+ return value;
105
+ }
106
+
107
+ /** Property keys that must never resolve through the real target (VM escape vectors). */
108
+ function isBlockedPrototypeKey(prop: string | symbol): boolean {
109
+ return prop === 'constructor' || prop === '__proto__';
110
+ }
111
+
112
+ /** Whether `value` is a native host Promise (handled via thenable facade, not `wrapDeep` alone). */
113
+ function isNativePromise(value: object): value is Promise<unknown> {
114
+ return value instanceof Promise;
115
+ }
116
+
117
+ /**
118
+ * Wraps a host Promise by exposing a null-prototype thenable that delegates to `p` and ensures
119
+ * fulfillment/rejection values are passed through {@link wrapIfNeeded} before VM callbacks run.
120
+ *
121
+ * @param p - Host promise returned from driver or other host APIs.
122
+ * @returns A deep-proxied thenable safe for `await` / `.then` from inside the VM.
123
+ */
124
+ function wrapPromiseAsThenable(p: Promise<unknown>): unknown {
125
+ const cached = promiseToThenableHost.get(p);
126
+ const hostObj: object =
127
+ cached ??
128
+ (() => {
129
+ /* eslint-disable promise/prefer-await-to-then -- thenable facade over a host Promise */
130
+ const o = Object.assign(Object.create(null), {
131
+ then(onFulfilled?: unknown, onRejected?: unknown) {
132
+ const adaptFulfill =
133
+ onFulfilled != null && typeof onFulfilled === 'function'
134
+ ? (v: unknown) => Reflect.apply(onFulfilled as HostCallable, undefined, [wrapIfNeeded(v)])
135
+ : (v: unknown) => wrapIfNeeded(v);
136
+ const adaptReject =
137
+ onRejected != null && typeof onRejected === 'function'
138
+ ? (e: unknown) => Reflect.apply(onRejected as HostCallable, undefined, [wrapIfNeeded(e)])
139
+ : undefined;
140
+ return wrapIfNeeded(p.then(adaptFulfill, adaptReject));
141
+ },
142
+ catch(onRejected?: unknown) {
143
+ return wrapIfNeeded(
144
+ p.catch((e: unknown) =>
145
+ onRejected != null && typeof onRejected === 'function'
146
+ ? Reflect.apply(onRejected as HostCallable, undefined, [wrapIfNeeded(e)])
147
+ : Promise.reject(wrapIfNeeded(e))
148
+ )
149
+ );
150
+ },
151
+ finally(onFinally?: unknown) {
152
+ return wrapIfNeeded(p.finally(onFinally as () => void | PromiseLike<void> | undefined));
153
+ },
154
+ });
155
+ /* eslint-enable promise/prefer-await-to-then */
156
+ promiseToThenableHost.set(p, o);
157
+ return o;
158
+ })();
159
+ return wrapDeep(hostObj);
160
+ }
161
+
162
+ /**
163
+ * Clones a **configurable** property descriptor so `value` / `get` / `set` are wrapped for the VM.
164
+ *
165
+ * @param d - Descriptor from `Reflect.getOwnPropertyDescriptor` on the host target.
166
+ * @returns A new descriptor safe to return from the proxy `getOwnPropertyDescriptor` trap.
167
+ */
168
+ function mapDescriptorForSandbox(descriptor: PropertyDescriptor): PropertyDescriptor {
169
+ if ('value' in descriptor) {
170
+ return {
171
+ configurable: descriptor.configurable,
172
+ enumerable: descriptor.enumerable,
173
+ writable: descriptor.writable,
174
+ value: wrapIfNeeded(descriptor.value),
175
+ };
176
+ }
177
+ return {
178
+ configurable: descriptor.configurable,
179
+ enumerable: descriptor.enumerable,
180
+ get: descriptor.get ? (wrapIfNeeded(descriptor.get) as () => unknown) : undefined,
181
+ set: descriptor.set ? (wrapIfNeeded(descriptor.set) as (v: unknown) => void) : undefined,
182
+ };
183
+ }
184
+
185
+ /**
186
+ * Prepares a property descriptor coming **from** the VM so `Reflect.defineProperty` on the host
187
+ * receives real targets, not our proxy objects.
188
+ *
189
+ * @param descriptor - Descriptor passed into the proxy `defineProperty` trap.
190
+ * @returns Descriptor with `value` / `get` / `set` unwrapped where they were our proxies.
191
+ */
192
+ function mapDescriptorForHost(descriptor: PropertyDescriptor): PropertyDescriptor {
193
+ const mapped: PropertyDescriptor = {};
194
+ if ('configurable' in descriptor) {
195
+ mapped.configurable = descriptor.configurable;
196
+ }
197
+ if ('enumerable' in descriptor) {
198
+ mapped.enumerable = descriptor.enumerable;
199
+ }
200
+ if ('writable' in descriptor) {
201
+ mapped.writable = descriptor.writable;
202
+ }
203
+ if ('value' in descriptor) {
204
+ mapped.value = unwrapIfProxy((descriptor as {value: unknown}).value);
205
+ }
206
+ if ('get' in descriptor) {
207
+ mapped.get = unwrapIfProxy(descriptor.get) as (() => unknown) | undefined;
208
+ }
209
+ if ('set' in descriptor) {
210
+ mapped.set = unwrapIfProxy(descriptor.set) as ((v: unknown) => void) | undefined;
211
+ }
212
+ return mapped;
213
+ }
214
+
215
+ /**
216
+ * Unwraps the proxy when used as the `receiver` for `Reflect.get`, so host getters see real `this`.
217
+ */
218
+ function reflectReceiver(receiver: unknown): unknown {
219
+ return unwrapIfProxy(receiver);
220
+ }
221
+
222
+ /**
223
+ * Unwraps constructor references passed to `Reflect.construct`.
224
+ *
225
+ * @param newTarget - Constructor received by the proxy `construct` trap.
226
+ * @returns Host constructor with this module's proxy removed when applicable.
227
+ */
228
+ function unwrapConstructor(newTarget: HostCallable): HostConstructor {
229
+ return unwrapIfProxy(newTarget) as unknown as HostConstructor;
230
+ }
231
+
232
+ /**
233
+ * Unwraps each argument before forwarding calls into host functions so VM-facing proxies
234
+ * round-trip back to their original host targets.
235
+ *
236
+ * @param argList - Arguments received by the proxy `apply` trap.
237
+ * @returns Arguments with this module's proxies replaced by underlying host targets.
238
+ */
239
+ function unwrapArgList(argList: readonly unknown[]): unknown[] {
240
+ return argList.map((arg) => unwrapIfProxy(arg));
241
+ }
242
+
243
+ /**
244
+ * Builds the shared `ProxyHandler` used for every deep wrap (objects and functions).
245
+ *
246
+ * Traps implement the file-level strategy: hide prototype edges, wrap outgoing values, unwrap
247
+ * incoming `this` / descriptors where required by invariants.
248
+ */
249
+ function createDeepHandler(): ProxyHandler<HostTarget> {
250
+ return {
251
+ apply(proxyTarget: HostTarget, thisArg: unknown, argArray: unknown[]): unknown {
252
+ const hostFn = proxyTarget as HostCallable;
253
+ const hostThis = unwrapIfProxy(thisArg);
254
+ const hostArgs = unwrapArgList(argArray);
255
+ const result = Reflect.apply(hostFn, hostThis, hostArgs);
256
+ return wrapIfNeeded(result);
257
+ },
258
+
259
+ construct(proxyTarget: HostTarget, argArray: unknown[], newTarget: unknown): object {
260
+ const hostCtor = proxyTarget as HostConstructor;
261
+ const hostArgs = unwrapArgList(argArray);
262
+ const hostNewTarget = unwrapConstructor(newTarget as HostCallable);
263
+ const result = Reflect.construct(hostCtor, hostArgs, hostNewTarget);
264
+ return wrapIfNeeded(result) as object;
265
+ },
266
+
267
+ defineProperty(
268
+ target: HostTarget,
269
+ prop: string | symbol,
270
+ descriptor: PropertyDescriptor
271
+ ): boolean {
272
+ return Reflect.defineProperty(target, prop, mapDescriptorForHost(descriptor));
273
+ },
274
+
275
+ get(target: HostTarget, prop: string | symbol, receiver: unknown): unknown {
276
+ if (isBlockedPrototypeKey(prop)) {
277
+ return SAFE_LOOKUP_TARGET;
278
+ }
279
+ const value = Reflect.get(target, prop, reflectReceiver(receiver));
280
+ return wrapIfNeeded(value);
281
+ },
282
+
283
+ getOwnPropertyDescriptor(
284
+ target: HostTarget,
285
+ prop: string | symbol
286
+ ): PropertyDescriptor | undefined {
287
+ if (isBlockedPrototypeKey(prop)) {
288
+ return {
289
+ configurable: true,
290
+ enumerable: false,
291
+ value: SAFE_LOOKUP_TARGET,
292
+ writable: false,
293
+ };
294
+ }
295
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
296
+ if (!descriptor) {
297
+ return undefined;
298
+ }
299
+ // Non-configurable properties must keep a descriptor compatible with the target
300
+ // (SameValue rules for getters / values); wrapping would violate Proxy invariants.
301
+ if (!descriptor.configurable) {
302
+ return descriptor;
303
+ }
304
+ return mapDescriptorForSandbox(descriptor);
305
+ },
306
+
307
+ getPrototypeOf(target: HostTarget): object | null {
308
+ void target;
309
+ return SAFE_LOOKUP_TARGET;
310
+ },
311
+
312
+ has(target: HostTarget, prop: string | symbol): boolean {
313
+ if (isBlockedPrototypeKey(prop)) {
314
+ return false;
315
+ }
316
+ return Reflect.has(target, prop);
317
+ },
318
+
319
+ setPrototypeOf(target: HostTarget, prototype: object | null): boolean {
320
+ void target;
321
+ void prototype;
322
+ return false;
323
+ },
324
+ };
325
+ }
326
+
327
+ /**
328
+ * Returns the one deep proxy for this host target, creating and caching it on first use.
329
+ *
330
+ * @typeParam T - Host object or function type.
331
+ * @param hostValue - Raw host reference (not a primitive).
332
+ * @returns Cached or new proxy for `hostValue`.
333
+ */
334
+ function wrapDeep<T extends HostTarget>(hostValue: T): T {
335
+ if (typeof hostValue !== 'object' && typeof hostValue !== 'function') {
336
+ return hostValue;
337
+ }
338
+ if (isOurProxy(hostValue)) {
339
+ return hostValue as T;
340
+ }
341
+ const cached = targetToProxy.get(hostValue);
342
+ if (cached) {
343
+ return cached as T;
344
+ }
345
+ const handler = createDeepHandler();
346
+ const proxy = new Proxy(hostValue, handler);
347
+ targetToProxy.set(hostValue, proxy);
348
+ proxyToTarget.set(proxy, hostValue);
349
+ return proxy as T;
350
+ }
351
+
352
+ /**
353
+ * Recursively wraps objects and functions; leaves primitives unchanged; routes Promises to the
354
+ * thenable facade.
355
+ *
356
+ * @param value - Result of a `get` / `apply` / descriptor field, or any nested host value.
357
+ * @returns Wrapped proxy, thenable-wrapped promise pipeline, or the original primitive.
358
+ */
359
+ function wrapIfNeeded(value: unknown): unknown {
360
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
361
+ return value;
362
+ }
363
+ if (typeof value === 'function') {
364
+ return wrapDeep(value as HostCallable);
365
+ }
366
+ if (isNativePromise(value as object)) {
367
+ return wrapPromiseAsThenable(value as Promise<unknown>);
368
+ }
369
+ return wrapDeep(value as HostTarget);
370
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appium/execute-driver-plugin",
3
- "version": "6.0.1",
3
+ "version": "6.0.2",
4
4
  "description": "Plugin for batching and executing Appium driver commands",
5
5
  "keywords": [
6
6
  "automation",
@@ -34,9 +34,9 @@
34
34
  ],
35
35
  "scripts": {
36
36
  "test": "npm run test:unit",
37
- "test:e2e": "mocha --exit -t 160s --slow 20s \"./test/e2e/**/*.spec.*ts\"",
37
+ "test:e2e": "mocha --exit -t 160s --slow 20s \"./test/e2e/**/*.spec.ts\"",
38
38
  "test:smoke": "node ./build/lib/index.js",
39
- "test:unit": "mocha \"./test/unit/**/*.spec.*ts\""
39
+ "test:unit": "mocha \"./test/unit/**/*.spec.ts\""
40
40
  },
41
41
  "dependencies": {
42
42
  "lodash": "4.18.1",
@@ -56,5 +56,5 @@
56
56
  "pluginName": "execute-driver",
57
57
  "mainClass": "ExecuteDriverPlugin"
58
58
  },
59
- "gitHead": "7a8965f5c30ffec2ad04ce75903b3960537cef06"
59
+ "gitHead": "17f84265d10944fec06ae7684ae8ad77d1224b50"
60
60
  }
package/tsconfig.json CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "compilerOptions": {
3
+ "rootDir": ".",
3
4
  "outDir": "build",
4
5
  "checkJs": true,
5
6
  "esModuleInterop": true,