@webhands/core 0.3.0 → 0.5.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.
package/README.md CHANGED
@@ -88,8 +88,10 @@ deliberately local and single-session by design.
88
88
  does NOT bypass authentication or solve CAPTCHAs programmatically, and it is not
89
89
  intended to.
90
90
  - **No fingerprint-spoofing / anti-detect tricks.** It leans on being a *real*
91
- browser/profile/IP rather than spoofing. There is no proxy rotation or
92
- anti-detect build here.
91
+ browser/profile/IP rather than spoofing. There is no proxy *rotation* or
92
+ anti-detect build here. (A single, user-chosen SOCKS proxy for traffic/DNS
93
+ control is available opt-in via `--proxy`; see *Optional: route traffic and
94
+ DNS through a SOCKS proxy* below.)
93
95
  - **Your own session only.** A replayed/stolen cookie does not work anyway
94
96
  (clearance is bound to the browser fingerprint and IP, not just the cookie);
95
97
  the design assumes the session is genuinely yours.
@@ -131,8 +133,18 @@ This is **off by default** — vanilla Playwright stays the default. To enable i
131
133
  Chrome with or without the Patchright path, and stealth with or without a
132
134
  system browser. Other channel names work too (e.g. `msedge`).
133
135
 
134
- Programmatic equivalent (the `--stealth` / `--use-system-browser` flags map onto
135
- these transport options):
136
+ 3. Optional extra hardening. `--no-viewport` lets the real browser window drive
137
+ its own size instead of Playwright's fixed 1280x720 emulated viewport (a
138
+ known headless tell). It is **defaulted ON under `--stealth`** (Patchright's
139
+ recommended recipe) and is overridable; pass `--viewport` to keep the fixed
140
+ viewport even under stealth. webhands deliberately does **not** override
141
+ `user-agent`, `locale`, `timezone`, or `headers`: a wrong UA is a bigger tell
142
+ than none.
143
+
144
+ Programmatic equivalent (the `--stealth` / `--use-system-browser` /
145
+ `--no-viewport` flags map onto these transport options; the constructor also
146
+ takes `extraLaunchArgs` and `ignoreDefaultArgs` escape hatches for additional
147
+ hardening flags, none of which touch the `OpenTarget` seam):
136
148
 
137
149
  ```ts
138
150
  import {PlaywrightLaunchTransport} from '@webhands/core';
@@ -140,7 +152,7 @@ import {PlaywrightLaunchTransport} from '@webhands/core';
140
152
  const transport = new PlaywrightLaunchTransport(
141
153
  {}, // profile location (omit for ~/.webhands)
142
154
  [], // extra hands
143
- {stealth: true, systemBrowser: 'chrome'},
155
+ {stealth: true, systemBrowser: 'chrome'}, // noViewport defaults to true here
144
156
  );
145
157
  // Stealth + headed + a real logged-in profile is the strongest recipe:
146
158
  const session = await transport.open({
@@ -156,12 +168,59 @@ It **never silently falls back** to vanilla Playwright, because that would put
156
168
  the tell back without telling you.
157
169
 
158
170
  **Honest caveat.** Stealth addresses ONLY the CDP `Runtime.enable` automation
159
- tell. It is **necessary-but-not-sufficient**: IP reputation and session/profile
171
+ tell, and the launch-hardening knobs (`--no-viewport`, `extraLaunchArgs`,
172
+ `ignoreDefaultArgs`) reduce but do **not** eliminate detection. They are
173
+ **necessary-but-not-sufficient**: IP reputation and session/profile
160
174
  reputation still matter. The realistic recipe is stealth +
161
175
  `systemBrowser: 'chrome'` + headed + a warmed, logged-in profile + a residential
162
176
  IP (see
163
177
  [`docs/adr/0002`](docs/adr/0002-real-session-over-fingerprint-spoofing.md)).
164
178
 
179
+ ## Optional: route traffic and DNS through a SOCKS proxy (opt-in, default OFF)
180
+
181
+ By default webhands connects directly on your own machine and IP. If you want
182
+ the browser to egress through a chosen SOCKS proxy (a VPN exit, an SSH/Tor SOCKS
183
+ endpoint, a residential proxy), pass `--proxy <socks-url>` to `serve` (or
184
+ `launch`). It routes **all** browser traffic AND DNS through that one proxy:
185
+
186
+ ```sh
187
+ # socks5h:// tunnels DNS through the proxy too (no DNS leak):
188
+ npx webhands serve --headed --proxy socks5h://127.0.0.1:1080
189
+
190
+ # with credentials:
191
+ npx webhands serve --proxy socks5h://user:pass@host:1080
192
+ ```
193
+
194
+ - **`socks5h://` means no DNS leak.** webhands adds Chromium's
195
+ `--host-resolver-rules` catch-all so even side channels (the DNS prefetcher)
196
+ cannot leak a raw local DNS query; only the proxy's own host is resolved
197
+ locally. This is the recommended form.
198
+ - **`socks5://` (or `socks://`) allows local DNS.** Use it when you deliberately
199
+ want split DNS. URL loads still resolve at the proxy, but Chromium may issue
200
+ some local DNS. Override either way with the programmatic `proxyNoLeak`
201
+ option.
202
+ - **A malformed `--proxy` value fails loudly** with a typed `InvalidProxyError`
203
+ (it never silently launches unproxied, which would leak the traffic you asked
204
+ to tunnel).
205
+
206
+ Programmatic equivalent:
207
+
208
+ ```ts
209
+ import {PlaywrightLaunchTransport} from '@webhands/core';
210
+
211
+ const transport = new PlaywrightLaunchTransport(
212
+ {}, // profile location
213
+ [], // extra hands
214
+ {proxy: 'socks5h://127.0.0.1:1080'}, // all traffic + DNS via the proxy, no leak
215
+ );
216
+ ```
217
+
218
+ **Honest caveat.** A proxy changes your IP and DNS path; it does **not** by
219
+ itself defeat bot detection, and a proxy/VPN/datacenter IP often reads WORSE
220
+ than a clean residential one. This is a deliberate, scoped opt-in deviation from
221
+ the "own IP" default (see
222
+ [`docs/adr/0009`](docs/adr/0009-opt-in-socks-proxy-all-traffic-and-dns.md)).
223
+
165
224
  ## Security note (the `serve` endpoint runs arbitrary code)
166
225
 
167
226
  The page verbs execute caller-supplied expressions: `eval` runs a JS expression
package/dist/errors.d.ts CHANGED
@@ -15,7 +15,7 @@
15
15
  * has to re-derive paths.
16
16
  */
17
17
  /** The closed set of identifiable `core` error conditions. */
18
- export type ControllerErrorCode = 'missing-browser-binary' | 'missing-stealth-dependency' | 'missing-profile' | 'attach-not-chromium' | 'attach-no-context' | 'no-live-server' | 'session-already-active';
18
+ export type ControllerErrorCode = 'missing-browser-binary' | 'missing-stealth-dependency' | 'invalid-proxy' | 'missing-profile' | 'attach-not-chromium' | 'attach-no-context' | 'no-live-server' | 'session-already-active';
19
19
  /**
20
20
  * Base class for every identifiable `core` error. Branch on {@link code}.
21
21
  *
@@ -68,6 +68,26 @@ export declare class MissingStealthDependencyError extends ControllerError {
68
68
  cause?: unknown;
69
69
  });
70
70
  }
71
+ /**
72
+ * The `--proxy` value (a SOCKS URL) could not be parsed into a usable proxy
73
+ * config. webhands routes ALL traffic and DNS through one SOCKS proxy, so the
74
+ * value must be a `socks5://` or `socks5h://` URL with a host and port (an
75
+ * optional `user:pass@` is allowed). We refuse a malformed value LOUDLY with
76
+ * this typed condition rather than silently launching with no proxy (which
77
+ * would leak the very traffic the user asked to tunnel). The CLI maps the
78
+ * {@link code} to a fix message showing the expected URL shape.
79
+ *
80
+ * Mirrors {@link MissingStealthDependencyError}: a stable typed error whose
81
+ * brittle detection is confined to one spot (the proxy parser).
82
+ */
83
+ export declare class InvalidProxyError extends ControllerError {
84
+ readonly code = "invalid-proxy";
85
+ /** The offending raw `--proxy` value, echoed back so the user can see it. */
86
+ readonly value: string;
87
+ constructor(value: string, message?: string, options?: {
88
+ cause?: unknown;
89
+ });
90
+ }
71
91
  /**
72
92
  * The named profile has not been set up yet: its dedicated profile directory
73
93
  * does not exist on disk. A profile is created by the headed `setup-profile`
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,8DAA8D;AAC9D,MAAM,MAAM,mBAAmB,GAC5B,wBAAwB,GACxB,4BAA4B,GAC5B,iBAAiB,GACjB,qBAAqB,GACrB,mBAAmB,GACnB,gBAAgB,GAChB,wBAAwB,CAAC;AAE5B;;;;;;GAMG;AACH,8BAAsB,eAAgB,SAAQ,KAAK;IAClD,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAC5C,8EAA8E;IAC9E,QAAQ,CAAC,iBAAiB,EAAG,IAAI,CAAU;gBAE/B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAMxD;AAED;;;;GAIG;AACH,qBAAa,yBAA0B,SAAQ,eAAe;IAC7D,QAAQ,CAAC,IAAI,4BAA4B;IACzC,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGxB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAA0D,EACnE,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,6BAA8B,SAAQ,eAAe;IACjE,QAAQ,CAAC,IAAI,gCAAgC;IAC7C,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAG3B,UAAU,SAAe,EACzB,OAAO,GAAE,MAA+Q,EACxR,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,eAAe;IACvD,QAAQ,CAAC,IAAI,qBAAqB;IAClC,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAG3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,MAA0F,EACnG,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAM5B;AAED;;;;;;GAMG;AACH,qBAAa,sBAAuB,SAAQ,eAAe;IAC1D,QAAQ,CAAC,IAAI,yBAAyB;IACtC,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGxB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAuJ,EAChK,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,eAAe;IACxD,QAAQ,CAAC,IAAI,uBAAuB;IACpC,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAGzB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,MAA+H,EACxI,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;;;GAOG;AACH,qBAAa,iBAAkB,SAAQ,eAAe;IACrD,QAAQ,CAAC,IAAI,oBAAoB;gBAGhC,OAAO,GAAE,MAAoF,EAC7F,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,eAAe;IAC7D,QAAQ,CAAC,IAAI,4BAA4B;gBAGxC,OAAO,GAAE,MAAmE,EAC5E,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAI5B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAO1E"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,8DAA8D;AAC9D,MAAM,MAAM,mBAAmB,GAC5B,wBAAwB,GACxB,4BAA4B,GAC5B,eAAe,GACf,iBAAiB,GACjB,qBAAqB,GACrB,mBAAmB,GACnB,gBAAgB,GAChB,wBAAwB,CAAC;AAE5B;;;;;;GAMG;AACH,8BAAsB,eAAgB,SAAQ,KAAK;IAClD,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAC5C,8EAA8E;IAC9E,QAAQ,CAAC,iBAAiB,EAAG,IAAI,CAAU;gBAE/B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAMxD;AAED;;;;GAIG;AACH,qBAAa,yBAA0B,SAAQ,eAAe;IAC7D,QAAQ,CAAC,IAAI,4BAA4B;IACzC,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGxB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAA0D,EACnE,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,6BAA8B,SAAQ,eAAe;IACjE,QAAQ,CAAC,IAAI,gCAAgC;IAC7C,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAG3B,UAAU,SAAe,EACzB,OAAO,GAAE,MAA+Q,EACxR,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAkB,SAAQ,eAAe;IACrD,QAAQ,CAAC,IAAI,mBAAmB;IAChC,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAGtB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,MAE8I,EACvJ,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,eAAe;IACvD,QAAQ,CAAC,IAAI,qBAAqB;IAClC,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAG3B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,MAA0F,EACnG,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAM5B;AAED;;;;;;GAMG;AACH,qBAAa,sBAAuB,SAAQ,eAAe;IAC1D,QAAQ,CAAC,IAAI,yBAAyB;IACtC,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGxB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAuJ,EAChK,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,eAAe;IACxD,QAAQ,CAAC,IAAI,uBAAuB;IACpC,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAGzB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,MAA+H,EACxI,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAK5B;AAED;;;;;;;GAOG;AACH,qBAAa,iBAAkB,SAAQ,eAAe;IACrD,QAAQ,CAAC,IAAI,oBAAoB;gBAGhC,OAAO,GAAE,MAAoF,EAC7F,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,eAAe;IAC7D,QAAQ,CAAC,IAAI,4BAA4B;gBAGxC,OAAO,GAAE,MAAmE,EAC5E,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAC;CAI5B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAO1E"}
package/dist/errors.js CHANGED
@@ -69,6 +69,27 @@ export class MissingStealthDependencyError extends ControllerError {
69
69
  this.dependency = dependency;
70
70
  }
71
71
  }
72
+ /**
73
+ * The `--proxy` value (a SOCKS URL) could not be parsed into a usable proxy
74
+ * config. webhands routes ALL traffic and DNS through one SOCKS proxy, so the
75
+ * value must be a `socks5://` or `socks5h://` URL with a host and port (an
76
+ * optional `user:pass@` is allowed). We refuse a malformed value LOUDLY with
77
+ * this typed condition rather than silently launching with no proxy (which
78
+ * would leak the very traffic the user asked to tunnel). The CLI maps the
79
+ * {@link code} to a fix message showing the expected URL shape.
80
+ *
81
+ * Mirrors {@link MissingStealthDependencyError}: a stable typed error whose
82
+ * brittle detection is confined to one spot (the proxy parser).
83
+ */
84
+ export class InvalidProxyError extends ControllerError {
85
+ code = 'invalid-proxy';
86
+ /** The offending raw `--proxy` value, echoed back so the user can see it. */
87
+ value;
88
+ constructor(value, message = `Invalid --proxy value ${JSON.stringify(value)}. Expected a SOCKS URL like socks5h://host:1080 or socks5://user:pass@host:1080 (socks5h tunnels DNS too; both route all traffic through the proxy).`, options) {
89
+ super(message, options);
90
+ this.value = value;
91
+ }
92
+ }
72
93
  /**
73
94
  * The named profile has not been set up yet: its dedicated profile directory
74
95
  * does not exist on disk. A profile is created by the headed `setup-profile`
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -5,9 +5,10 @@ export { StubTransport, type StubCall } from './stub-transport.js';
5
5
  export type { Hand, HandContext, HandContribution } from './hand-host.js';
6
6
  export { readHandsConfig, normalizeConfig, loadHands, HandLoadError, HANDS_CONFIG_FILENAME, type HandEntry, type HandsConfig, type LoadedHand, type LoadHandsOptions, } from './hand-loading.js';
7
7
  export { PlaywrightLaunchTransport, type PlaywrightLaunchTransportOptions, type StealthChromiumImporter, } from './playwright-launch-transport.js';
8
+ export { parseSocksProxy, hostResolverRulesArg, type ParsedSocksProxy, } from './socks-proxy.js';
8
9
  export { PlaywrightAttachTransport } from './playwright-attach-transport.js';
9
10
  export { setupProfile, buildPrompt, type PromptSink, type SetupProfileOptions, type SetupProfileResult, } from './setup-profile.js';
10
- export { ControllerError, MissingBrowserBinaryError, MissingStealthDependencyError, MissingProfileError, AttachNotChromiumError, AttachNoContextError, NoLiveServerError, SessionAlreadyActiveError, isControllerError, type ControllerErrorCode, } from './errors.js';
11
+ export { ControllerError, MissingBrowserBinaryError, MissingStealthDependencyError, InvalidProxyError, MissingProfileError, AttachNotChromiumError, AttachNoContextError, NoLiveServerError, SessionAlreadyActiveError, isControllerError, type ControllerErrorCode, } from './errors.js';
11
12
  export { resolveSessionEndpointPath, writeSessionEndpoint, readSessionEndpoint, clearSessionEndpoint, SESSION_ENDPOINT_FILENAME, type SessionEndpoint, } from './session-endpoint.js';
12
13
  export { startSessionServer, sessionAlreadyActive, type SessionServerOptions, type RunningSessionServer, } from './session-server.js';
13
14
  export { connectRemoteSession } from './remote-session.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACX,MAAM,EACN,MAAM,EACN,aAAa,EACb,UAAU,EACV,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,SAAS,EACT,aAAa,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAElC,OAAO,EACN,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,EACtB,KAAK,aAAa,GAClB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAC,aAAa,EAAE,KAAK,QAAQ,EAAC,MAAM,qBAAqB,CAAC;AAEjE,YAAY,EAAC,IAAI,EAAE,WAAW,EAAE,gBAAgB,EAAC,MAAM,gBAAgB,CAAC;AAExE,OAAO,EACN,eAAe,EACf,eAAe,EACf,SAAS,EACT,aAAa,EACb,qBAAqB,EACrB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,gBAAgB,GACrB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACN,yBAAyB,EACzB,KAAK,gCAAgC,EACrC,KAAK,uBAAuB,GAC5B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAC,yBAAyB,EAAC,MAAM,kCAAkC,CAAC;AAE3E,OAAO,EACN,YAAY,EACZ,WAAW,EACX,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACvB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACN,eAAe,EACf,yBAAyB,EACzB,6BAA6B,EAC7B,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,iBAAiB,EACjB,KAAK,mBAAmB,GACxB,MAAM,aAAa,CAAC;AAErB,OAAO,EACN,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,KAAK,eAAe,GACpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACN,kBAAkB,EAClB,oBAAoB,EACpB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAC,oBAAoB,EAAC,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EACN,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,GACvB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACN,eAAe,EACf,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,eAAe,EACpB,KAAK,sBAAsB,GAC3B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACN,kBAAkB,EAClB,KAAK,aAAa,GAClB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAC,aAAa,EAAC,MAAM,kCAAkC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACX,MAAM,EACN,MAAM,EACN,aAAa,EACb,UAAU,EACV,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,SAAS,EACT,aAAa,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAElC,OAAO,EACN,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,EACtB,KAAK,aAAa,GAClB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAC,aAAa,EAAE,KAAK,QAAQ,EAAC,MAAM,qBAAqB,CAAC;AAEjE,YAAY,EAAC,IAAI,EAAE,WAAW,EAAE,gBAAgB,EAAC,MAAM,gBAAgB,CAAC;AAExE,OAAO,EACN,eAAe,EACf,eAAe,EACf,SAAS,EACT,aAAa,EACb,qBAAqB,EACrB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,gBAAgB,GACrB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACN,yBAAyB,EACzB,KAAK,gCAAgC,EACrC,KAAK,uBAAuB,GAC5B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACN,eAAe,EACf,oBAAoB,EACpB,KAAK,gBAAgB,GACrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAC,yBAAyB,EAAC,MAAM,kCAAkC,CAAC;AAE3E,OAAO,EACN,YAAY,EACZ,WAAW,EACX,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACvB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACN,eAAe,EACf,yBAAyB,EACzB,6BAA6B,EAC7B,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,iBAAiB,EACjB,KAAK,mBAAmB,GACxB,MAAM,aAAa,CAAC;AAErB,OAAO,EACN,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,KAAK,eAAe,GACpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACN,kBAAkB,EAClB,oBAAoB,EACpB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAC,oBAAoB,EAAC,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EACN,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,GACvB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACN,eAAe,EACf,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,eAAe,EACpB,KAAK,sBAAsB,GAC3B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACN,kBAAkB,EAClB,KAAK,aAAa,GAClB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAC,aAAa,EAAC,MAAM,kCAAkC,CAAC"}
package/dist/index.js CHANGED
@@ -3,9 +3,10 @@ export { serializeCookies, deserializeCookies, COOKIES_EXPORT_VERSION, } from '.
3
3
  export { StubTransport } from './stub-transport.js';
4
4
  export { readHandsConfig, normalizeConfig, loadHands, HandLoadError, HANDS_CONFIG_FILENAME, } from './hand-loading.js';
5
5
  export { PlaywrightLaunchTransport, } from './playwright-launch-transport.js';
6
+ export { parseSocksProxy, hostResolverRulesArg, } from './socks-proxy.js';
6
7
  export { PlaywrightAttachTransport } from './playwright-attach-transport.js';
7
8
  export { setupProfile, buildPrompt, } from './setup-profile.js';
8
- export { ControllerError, MissingBrowserBinaryError, MissingStealthDependencyError, MissingProfileError, AttachNotChromiumError, AttachNoContextError, NoLiveServerError, SessionAlreadyActiveError, isControllerError, } from './errors.js';
9
+ export { ControllerError, MissingBrowserBinaryError, MissingStealthDependencyError, InvalidProxyError, MissingProfileError, AttachNotChromiumError, AttachNoContextError, NoLiveServerError, SessionAlreadyActiveError, isControllerError, } from './errors.js';
9
10
  export { resolveSessionEndpointPath, writeSessionEndpoint, readSessionEndpoint, clearSessionEndpoint, SESSION_ENDPOINT_FILENAME, } from './session-endpoint.js';
10
11
  export { startSessionServer, sessionAlreadyActive, } from './session-server.js';
11
12
  export { connectRemoteSession } from './remote-session.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAElC,OAAO,EACN,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,GAEtB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAC,aAAa,EAAgB,MAAM,qBAAqB,CAAC;AAIjE,OAAO,EACN,eAAe,EACf,eAAe,EACf,SAAS,EACT,aAAa,EACb,qBAAqB,GAKrB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACN,yBAAyB,GAGzB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAC,yBAAyB,EAAC,MAAM,kCAAkC,CAAC;AAE3E,OAAO,EACN,YAAY,EACZ,WAAW,GAIX,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACN,eAAe,EACf,yBAAyB,EACzB,6BAA6B,EAC7B,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,iBAAiB,GAEjB,MAAM,aAAa,CAAC;AAErB,OAAO,EACN,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,GAEzB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACN,kBAAkB,EAClB,oBAAoB,GAGpB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAC,oBAAoB,EAAC,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EACN,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,YAAY,GAKZ,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACN,eAAe,EACf,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,GAGhB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACN,kBAAkB,GAElB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAC,aAAa,EAAC,MAAM,kCAAkC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAElC,OAAO,EACN,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,GAEtB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAC,aAAa,EAAgB,MAAM,qBAAqB,CAAC;AAIjE,OAAO,EACN,eAAe,EACf,eAAe,EACf,SAAS,EACT,aAAa,EACb,qBAAqB,GAKrB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACN,yBAAyB,GAGzB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACN,eAAe,EACf,oBAAoB,GAEpB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAC,yBAAyB,EAAC,MAAM,kCAAkC,CAAC;AAE3E,OAAO,EACN,YAAY,EACZ,WAAW,GAIX,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACN,eAAe,EACf,yBAAyB,EACzB,6BAA6B,EAC7B,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,iBAAiB,GAEjB,MAAM,aAAa,CAAC;AAErB,OAAO,EACN,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,GAEzB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACN,kBAAkB,EAClB,oBAAoB,GAGpB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAC,oBAAoB,EAAC,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EACN,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,YAAY,GAKZ,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACN,eAAe,EACf,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,GAGhB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACN,kBAAkB,GAElB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAC,aAAa,EAAC,MAAM,kCAAkC,CAAC"}
@@ -56,6 +56,76 @@ export interface PlaywrightLaunchTransportOptions {
56
56
  * Playwright vocabulary out of the public surface).
57
57
  */
58
58
  readonly systemBrowser?: string;
59
+ /**
60
+ * Don't impose a fixed emulated viewport: let the browser window drive its own
61
+ * size, exactly as a real user's browser does. Maps to Playwright's
62
+ * `viewport: null` on the persistent context.
63
+ *
64
+ * Why this matters for hardening: Playwright's DEFAULT is a fixed 1280x720
65
+ * emulated viewport that does NOT match the real OS window, a discrepancy
66
+ * (e.g. `window.outerWidth`/`innerWidth`/`screen` mismatches, no real resize
67
+ * behaviour) that fingerprinting scripts read as a headless/automation tell.
68
+ * Patchright's official recommended recipe sets `no_viewport=True` for this
69
+ * reason.
70
+ *
71
+ * Default: `undefined` leaves Playwright's behaviour as-is, EXCEPT that when
72
+ * {@link stealth} is enabled it defaults to `true` (the Patchright recipe).
73
+ * Pass an explicit `false` to keep the fixed emulated viewport even under
74
+ * stealth (e.g. when a caller deliberately wants a deterministic size). We pick
75
+ * the stealth-on default because shipping the stealth engine while leaving the
76
+ * tell it is meant to hide in place would be self-defeating; making it an
77
+ * explicit, overridable default keeps that honest and discoverable.
78
+ */
79
+ readonly noViewport?: boolean;
80
+ /**
81
+ * Extra command-line args appended to the browser launch (Playwright's
82
+ * `args`). An escape hatch for well-known hardening flags Patchright/Chromium
83
+ * users pass (e.g. `--disable-blink-features=AutomationControlled`) WITHOUT
84
+ * leaking a Playwright type across the seam: this is a plain `string[]`, kept
85
+ * confined to this transport-construction policy and deliberately NOT on
86
+ * {@link OpenTarget} (ADR-0003). Default: none.
87
+ *
88
+ * Caveat: args are passed THROUGH verbatim; a wrong or contradictory flag can
89
+ * itself become a tell or break the launch. Opt-in only.
90
+ */
91
+ readonly extraLaunchArgs?: readonly string[];
92
+ /**
93
+ * Passthrough for Playwright's `ignoreDefaultArgs`: either `true` to drop ALL
94
+ * of Playwright's default launch args, or a list of specific default args to
95
+ * drop, so a caller can strip more automation-flavoured defaults than the
96
+ * built-in stealth subset.
97
+ *
98
+ * When omitted, the stealth path still drops `--enable-automation` on its own
99
+ * (unchanged behaviour). When provided, this value REPLACES that built-in
100
+ * choice, so a caller opting in owns the full list (pass
101
+ * `['--enable-automation', ...]` to keep it). Like {@link extraLaunchArgs}
102
+ * this is a plain value confined to this module, never on {@link OpenTarget}.
103
+ * Default: none.
104
+ */
105
+ readonly ignoreDefaultArgs?: boolean | readonly string[];
106
+ /**
107
+ * Route ALL browser traffic AND DNS through a single SOCKS proxy, given as a
108
+ * SOCKS URL: `socks5h://host:1080` (or `socks5://host:1080`, optionally with a
109
+ * `user:pass@` userinfo). When set, the transport forwards the proxy to
110
+ * Playwright's `proxy` launch option AND adds Chromium's `--host-resolver-rules`
111
+ * catch-all so no DNS query escapes locally (see {@link proxyNoLeak}).
112
+ *
113
+ * Scheme convention: `socks5h://` means "resolve DNS at the proxy" (no leak),
114
+ * `socks5://` means "SOCKS5, local DNS allowed" (Chromium still resolves URL
115
+ * hostnames at the proxy, but its DNS prefetcher etc. may issue local DNS). Use
116
+ * {@link proxyNoLeak} to override the scheme's implied DNS behaviour. A
117
+ * malformed value throws the typed {@link InvalidProxyError} rather than
118
+ * launching unproxied. Default: no proxy.
119
+ */
120
+ readonly proxy?: string;
121
+ /**
122
+ * Override whether the {@link proxy} enforces NO local DNS. `true` forces the
123
+ * leak-free catch-all even for a plain `socks5://` URL; `false` allows local
124
+ * DNS even for a `socks5h://` URL. When omitted, the SCHEME decides
125
+ * (`socks5h` => no leak, `socks5`/`socks` => local DNS allowed). Ignored when
126
+ * {@link proxy} is unset.
127
+ */
128
+ readonly proxyNoLeak?: boolean;
59
129
  /**
60
130
  * INTERNAL test seam: override how the stealth chromium is imported. Omit in
61
131
  * production (defaults to `import('patchright')`). See
@@ -98,9 +168,11 @@ export declare class PlaywrightLaunchTransport implements Transport {
98
168
  * the operator's explicit config; the transport does NOT discover them. Omit
99
169
  * for the built-ins-only surface.
100
170
  * @param options transport-construction policy, notably the opt-in `stealth`
101
- * toggle and optional `systemBrowser` (see
171
+ * toggle, optional `systemBrowser`, and the launch-hardening knobs
172
+ * (`noViewport`, `extraLaunchArgs`, `ignoreDefaultArgs`; see
102
173
  * {@link PlaywrightLaunchTransportOptions}). Defaults to vanilla Playwright,
103
- * bundled Chromium, stealth OFF.
174
+ * bundled Chromium, stealth OFF. The hardening knobs are confined to this
175
+ * module and never reach {@link OpenTarget} (ADR-0003).
104
176
  */
105
177
  constructor(location?: ProfileLocationOptions, hands?: readonly Hand[], options?: PlaywrightLaunchTransportOptions);
106
178
  open(target: OpenTarget): Promise<Session>;
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-launch-transport.d.ts","sourceRoot":"","sources":["../src/playwright-launch-transport.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAiC,MAAM,YAAY,CAAC;AAMpE,OAAO,EAAmB,KAAK,IAAI,EAAmB,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAEN,KAAK,sBAAsB,EAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAC,MAAM,WAAW,CAAC;AAE9D;;;;;;;;GAQG;AACH,KAAK,gBAAgB,GAAG,IAAI,CAAC,OAAO,QAAQ,EAAE,yBAAyB,CAAC,CAAC;AAEzE,oEAAoE;AACpE,UAAU,aAAa;IACtB,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,uBAAuB,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,WAAW,gCAAgC;IAChD;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,uBAAuB,CAAC;CACzD;AAmBD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,yBAA0B,YAAW,SAAS;;IAO1D;;;;;;;;;;;;OAYG;gBAEF,QAAQ,GAAE,sBAA2B,EACrC,KAAK,GAAE,SAAS,IAAI,EAAO,EAC3B,OAAO,GAAE,gCAAqC;IAUzC,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;CA8FhD"}
1
+ {"version":3,"file":"playwright-launch-transport.d.ts","sourceRoot":"","sources":["../src/playwright-launch-transport.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAiC,MAAM,YAAY,CAAC;AAMpE,OAAO,EAAmB,KAAK,IAAI,EAAmB,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAEN,KAAK,sBAAsB,EAC3B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAC,MAAM,WAAW,CAAC;AAE9D;;;;;;;;GAQG;AACH,KAAK,gBAAgB,GAAG,IAAI,CAAC,OAAO,QAAQ,EAAE,yBAAyB,CAAC,CAAC;AAEzE,oEAAoE;AACpE,UAAU,aAAa;IACtB,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,uBAAuB,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,WAAW,gCAAgC;IAChD;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,GAAG,SAAS,MAAM,EAAE,CAAC;IACzD;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,uBAAuB,CAAC;CACzD;AAmBD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,yBAA0B,YAAW,SAAS;;IAY1D;;;;;;;;;;;;;;OAcG;gBAEF,QAAQ,GAAE,sBAA2B,EACrC,KAAK,GAAE,SAAS,IAAI,EAAO,EAC3B,OAAO,GAAE,gCAAqC;IAezC,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;CA6IhD"}
@@ -3,6 +3,7 @@ import { chromium } from 'playwright';
3
3
  import { MissingBrowserBinaryError, MissingProfileError, MissingStealthDependencyError, } from './errors.js';
4
4
  import { composeWithHands } from './hand-host.js';
5
5
  import { resolveProfileLocation, } from './profile-location.js';
6
+ import { hostResolverRulesArg, parseSocksProxy } from './socks-proxy.js';
6
7
  /**
7
8
  * The package name of the optional stealth dependency. Kept as a runtime value
8
9
  * (not an `import('patchright')` literal) so TypeScript does NOT try to resolve
@@ -47,6 +48,11 @@ export class PlaywrightLaunchTransport {
47
48
  #hands;
48
49
  #stealth;
49
50
  #systemBrowser;
51
+ #noViewport;
52
+ #extraLaunchArgs;
53
+ #ignoreDefaultArgs;
54
+ #proxy;
55
+ #proxyNoLeak;
50
56
  #importStealthChromium;
51
57
  /**
52
58
  * @param location overrides for where profiles live (a `root` dir and/or an
@@ -57,15 +63,22 @@ export class PlaywrightLaunchTransport {
57
63
  * the operator's explicit config; the transport does NOT discover them. Omit
58
64
  * for the built-ins-only surface.
59
65
  * @param options transport-construction policy, notably the opt-in `stealth`
60
- * toggle and optional `systemBrowser` (see
66
+ * toggle, optional `systemBrowser`, and the launch-hardening knobs
67
+ * (`noViewport`, `extraLaunchArgs`, `ignoreDefaultArgs`; see
61
68
  * {@link PlaywrightLaunchTransportOptions}). Defaults to vanilla Playwright,
62
- * bundled Chromium, stealth OFF.
69
+ * bundled Chromium, stealth OFF. The hardening knobs are confined to this
70
+ * module and never reach {@link OpenTarget} (ADR-0003).
63
71
  */
64
72
  constructor(location = {}, hands = [], options = {}) {
65
73
  this.#location = location;
66
74
  this.#hands = hands;
67
75
  this.#stealth = options.stealth === true;
68
76
  this.#systemBrowser = options.systemBrowser;
77
+ this.#noViewport = options.noViewport;
78
+ this.#extraLaunchArgs = options.extraLaunchArgs;
79
+ this.#ignoreDefaultArgs = options.ignoreDefaultArgs;
80
+ this.#proxy = options.proxy;
81
+ this.#proxyNoLeak = options.proxyNoLeak;
69
82
  this.#importStealthChromium =
70
83
  options.importStealthChromium ?? defaultStealthImporter;
71
84
  }
@@ -100,9 +113,55 @@ export class PlaywrightLaunchTransport {
100
113
  if (this.#systemBrowser !== undefined) {
101
114
  launchOptions.channel = this.#systemBrowser;
102
115
  }
103
- if (this.#stealth) {
116
+ // no_viewport: explicit caller choice wins; otherwise default to TRUE under
117
+ // stealth (Patchright's recommended recipe), and leave Playwright's default
118
+ // fixed viewport in place when stealth is off. `viewport: null` is how
119
+ // Playwright expresses "let the real window drive the size".
120
+ const noViewport = this.#noViewport ?? this.#stealth;
121
+ if (noViewport) {
122
+ launchOptions.viewport = null;
123
+ }
124
+ // ignoreDefaultArgs: an explicit passthrough REPLACES the built-in stealth
125
+ // choice (the caller then owns the full list). With no passthrough, the
126
+ // stealth path keeps dropping just `--enable-automation` so it cannot re-add
127
+ // the fingerprint Patchright just removed.
128
+ if (this.#ignoreDefaultArgs !== undefined) {
129
+ launchOptions.ignoreDefaultArgs =
130
+ typeof this.#ignoreDefaultArgs === 'boolean'
131
+ ? this.#ignoreDefaultArgs
132
+ : [...this.#ignoreDefaultArgs];
133
+ }
134
+ else if (this.#stealth) {
104
135
  launchOptions.ignoreDefaultArgs = ['--enable-automation'];
105
136
  }
137
+ // Proxy: route ALL traffic + DNS through one SOCKS proxy. We parse the URL
138
+ // HERE (a malformed value is the typed InvalidProxyError, never a silent
139
+ // unproxied launch), forward it to Playwright's `proxy` option, and when
140
+ // no-leak is in effect add Chromium's --host-resolver-rules catch-all so even
141
+ // the DNS prefetcher cannot leak a raw local DNS query.
142
+ const hardeningArgs = [];
143
+ if (this.#proxy !== undefined && this.#proxy.trim() !== '') {
144
+ const parsed = parseSocksProxy(this.#proxy, this.#proxyNoLeak);
145
+ launchOptions.proxy = {
146
+ server: parsed.server,
147
+ ...(parsed.username !== undefined ? { username: parsed.username } : {}),
148
+ ...(parsed.password !== undefined ? { password: parsed.password } : {}),
149
+ };
150
+ if (parsed.noLeak) {
151
+ hardeningArgs.push(hostResolverRulesArg(parsed.host));
152
+ }
153
+ }
154
+ // Extra launch args (the hardening escape hatch) are appended verbatim. We do
155
+ // NOT set user-agent/locale/timezone/headers here: a wrong UA is a bigger
156
+ // tell than none (Patchright warns against overriding them), so those stay
157
+ // untouched by default. The proxy's no-leak DNS arg (if any) rides alongside.
158
+ if (this.#extraLaunchArgs !== undefined &&
159
+ this.#extraLaunchArgs.length > 0) {
160
+ hardeningArgs.push(...this.#extraLaunchArgs);
161
+ }
162
+ if (hardeningArgs.length > 0) {
163
+ launchOptions.args = hardeningArgs;
164
+ }
106
165
  let context;
107
166
  try {
108
167
  context = await launcher.launchPersistentContext(loc.profileDir, launchOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-launch-transport.js","sourceRoot":"","sources":["../src/playwright-launch-transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"playwright-launch-transport.js","sourceRoot":"","sources":["../src/playwright-launch-transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * A parsed SOCKS proxy ready to hand to Playwright/Chromium.
3
+ *
4
+ * This is the SINGLE place webhands turns a user-facing `--proxy` SOCKS URL into
5
+ * the concrete launch inputs Chromium needs: the `proxy.server`/credentials
6
+ * Playwright forwards, plus the extra command-line arg that forces DNS through
7
+ * the proxy (no DNS leak). Keeping the brittle parsing + Chromium-flag knowledge
8
+ * in one module mirrors how the launch transport confines its other
9
+ * Playwright/Chromium details.
10
+ */
11
+ export interface ParsedSocksProxy {
12
+ /**
13
+ * The Playwright `proxy.server` value, always normalized to `socks5://host:port`.
14
+ *
15
+ * Chromium's `--proxy-server` understands `socks5://` but NOT the `socks5h://`
16
+ * convention, so we normalize the scheme here and carry the "resolve DNS at
17
+ * the proxy / block local DNS" intent separately in {@link noLeak} instead of
18
+ * in the scheme string.
19
+ */
20
+ readonly server: string;
21
+ /** Optional proxy username (from a `user:pass@` userinfo component). */
22
+ readonly username?: string;
23
+ /** Optional proxy password (from a `user:pass@` userinfo component). */
24
+ readonly password?: string;
25
+ /** The proxy host, used to build the DNS catch-all EXCLUDE rule. */
26
+ readonly host: string;
27
+ /**
28
+ * Whether to enforce NO local DNS (force every hostname to resolve at the
29
+ * proxy). When `true`, the transport adds a `--host-resolver-rules` catch-all
30
+ * so even Chromium components that bypass `--proxy-server` (DNS prefetcher,
31
+ * etc.) cannot leak a raw DNS query (see {@link hostResolverRulesArg}).
32
+ */
33
+ readonly noLeak: boolean;
34
+ }
35
+ /**
36
+ * Parse a user-facing `--proxy` SOCKS URL into a {@link ParsedSocksProxy}.
37
+ *
38
+ * Accepts `socks5h://`, `socks5://`, or `socks://` with a host and port and an
39
+ * optional `user:pass@` userinfo. Anything else (missing host/port, an http(s)
40
+ * proxy, a bare host with no scheme) is a typed {@link InvalidProxyError} so the
41
+ * caller refuses LOUDLY rather than launching unproxied.
42
+ *
43
+ * `forceNoLeak`, when set, overrides the scheme's implied DNS behaviour: pass
44
+ * `true` to enforce no local DNS even for a plain `socks5://` URL, or `false` to
45
+ * allow local DNS even for `socks5h://`. When omitted, the SCHEME decides
46
+ * (`socks5h` => no-leak, `socks5`/`socks` => local DNS allowed).
47
+ */
48
+ export declare function parseSocksProxy(value: string, forceNoLeak?: boolean): ParsedSocksProxy;
49
+ /**
50
+ * Build the Chromium `--host-resolver-rules` argument that prevents ANY local
51
+ * DNS resolution, the catch-all the Chromium SOCKS design doc prescribes for a
52
+ * leak-free SOCKS proxy.
53
+ *
54
+ * `MAP * ~NOTFOUND` maps every hostname to an invalid address so Chromium never
55
+ * issues a real local DNS query; `EXCLUDE <host>` lets Chromium still resolve
56
+ * the proxy server's own address (otherwise every request fails with
57
+ * PROXY_CONNECTION_FAILED). URL loads themselves resolve at the proxy via
58
+ * `--proxy-server`; this arg closes the side channels (DNS prefetcher, etc.).
59
+ */
60
+ export declare function hostResolverRulesArg(host: string): string;
61
+ //# sourceMappingURL=socks-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socks-proxy.d.ts","sourceRoot":"","sources":["../src/socks-proxy.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CACzB;AAqBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC9B,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,OAAO,GACnB,gBAAgB,CAuClB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD"}
@@ -0,0 +1,84 @@
1
+ import { InvalidProxyError } from './errors.js';
2
+ /**
3
+ * The SOCKS schemes we accept on a `--proxy` value.
4
+ *
5
+ * - `socks5h://` is the widely-used convention meaning "resolve DNS at the
6
+ * proxy" (no local DNS, no leak). We map it to {@link ParsedSocksProxy.noLeak}
7
+ * `true`.
8
+ * - `socks5://` means "SOCKS5, local DNS allowed" by the same convention. NOTE:
9
+ * Chromium ALREADY resolves URL hostnames at the proxy under `--proxy-server`,
10
+ * but other components (the DNS prefetcher) can still issue raw local DNS, so
11
+ * plain `socks5://` does NOT by itself guarantee no leak.
12
+ *
13
+ * `socks://` is accepted as an alias for `socks5://` (some tools emit it).
14
+ */
15
+ const SCHEME_NO_LEAK = {
16
+ 'socks5h:': true,
17
+ 'socks5:': false,
18
+ 'socks:': false,
19
+ };
20
+ /**
21
+ * Parse a user-facing `--proxy` SOCKS URL into a {@link ParsedSocksProxy}.
22
+ *
23
+ * Accepts `socks5h://`, `socks5://`, or `socks://` with a host and port and an
24
+ * optional `user:pass@` userinfo. Anything else (missing host/port, an http(s)
25
+ * proxy, a bare host with no scheme) is a typed {@link InvalidProxyError} so the
26
+ * caller refuses LOUDLY rather than launching unproxied.
27
+ *
28
+ * `forceNoLeak`, when set, overrides the scheme's implied DNS behaviour: pass
29
+ * `true` to enforce no local DNS even for a plain `socks5://` URL, or `false` to
30
+ * allow local DNS even for `socks5h://`. When omitted, the SCHEME decides
31
+ * (`socks5h` => no-leak, `socks5`/`socks` => local DNS allowed).
32
+ */
33
+ export function parseSocksProxy(value, forceNoLeak) {
34
+ const trimmed = value.trim();
35
+ if (trimmed === '') {
36
+ throw new InvalidProxyError(value);
37
+ }
38
+ let url;
39
+ try {
40
+ url = new URL(trimmed);
41
+ }
42
+ catch (cause) {
43
+ throw new InvalidProxyError(value, undefined, { cause });
44
+ }
45
+ const schemeNoLeak = SCHEME_NO_LEAK[url.protocol];
46
+ if (schemeNoLeak === undefined) {
47
+ // Not a SOCKS scheme (e.g. http://, https://, socks4://, or no scheme).
48
+ throw new InvalidProxyError(value);
49
+ }
50
+ if (url.hostname === '' || url.port === '') {
51
+ // A host AND an explicit port are both required: Chromium's --proxy-server
52
+ // needs the port, and we will not guess a default.
53
+ throw new InvalidProxyError(value);
54
+ }
55
+ const noLeak = forceNoLeak ?? schemeNoLeak;
56
+ const server = `socks5://${url.hostname}:${url.port}`;
57
+ const proxy = {
58
+ server,
59
+ host: url.hostname,
60
+ noLeak,
61
+ ...(url.username !== ''
62
+ ? { username: decodeURIComponent(url.username) }
63
+ : {}),
64
+ ...(url.password !== ''
65
+ ? { password: decodeURIComponent(url.password) }
66
+ : {}),
67
+ };
68
+ return proxy;
69
+ }
70
+ /**
71
+ * Build the Chromium `--host-resolver-rules` argument that prevents ANY local
72
+ * DNS resolution, the catch-all the Chromium SOCKS design doc prescribes for a
73
+ * leak-free SOCKS proxy.
74
+ *
75
+ * `MAP * ~NOTFOUND` maps every hostname to an invalid address so Chromium never
76
+ * issues a real local DNS query; `EXCLUDE <host>` lets Chromium still resolve
77
+ * the proxy server's own address (otherwise every request fails with
78
+ * PROXY_CONNECTION_FAILED). URL loads themselves resolve at the proxy via
79
+ * `--proxy-server`; this arg closes the side channels (DNS prefetcher, etc.).
80
+ */
81
+ export function hostResolverRulesArg(host) {
82
+ return `--host-resolver-rules=MAP * ~NOTFOUND , EXCLUDE ${host}`;
83
+ }
84
+ //# sourceMappingURL=socks-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socks-proxy.js","sourceRoot":"","sources":["../src/socks-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,iBAAiB,EAAC,MAAM,aAAa,CAAC;AAqC9C;;;;;;;;;;;;GAYG;AACH,MAAM,cAAc,GAAsC;IACzD,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,KAAK;IAChB,QAAQ,EAAE,KAAK;CACf,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,eAAe,CAC9B,KAAa,EACb,WAAqB;IAErB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QACpB,MAAM,IAAI,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACJ,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,EAAC,KAAK,EAAC,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,YAAY,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAClD,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAChC,wEAAwE;QACxE,MAAM,IAAI,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE,IAAI,GAAG,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC;QAC5C,2EAA2E;QAC3E,mDAAmD;QACnD,MAAM,IAAI,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,IAAI,YAAY,CAAC;IAC3C,MAAM,MAAM,GAAG,YAAY,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;IAEtD,MAAM,KAAK,GAAqB;QAC/B,MAAM;QACN,IAAI,EAAE,GAAG,CAAC,QAAQ;QAClB,MAAM;QACN,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,EAAE;YACtB,CAAC,CAAC,EAAC,QAAQ,EAAE,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAC;YAC9C,CAAC,CAAC,EAAE,CAAC;QACN,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,EAAE;YACtB,CAAC,CAAC,EAAC,QAAQ,EAAE,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAC;YAC9C,CAAC,CAAC,EAAE,CAAC;KACN,CAAC;IACF,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAChD,OAAO,mDAAmD,IAAI,EAAE,CAAC;AAClE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webhands/core",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Core library for webhands: drives a real, persistent browser via Playwright.",
5
5
  "keywords": [
6
6
  "browser",
package/src/errors.ts CHANGED
@@ -19,6 +19,7 @@
19
19
  export type ControllerErrorCode =
20
20
  | 'missing-browser-binary'
21
21
  | 'missing-stealth-dependency'
22
+ | 'invalid-proxy'
22
23
  | 'missing-profile'
23
24
  | 'attach-not-chromium'
24
25
  | 'attach-no-context'
@@ -96,6 +97,35 @@ export class MissingStealthDependencyError extends ControllerError {
96
97
  }
97
98
  }
98
99
 
100
+ /**
101
+ * The `--proxy` value (a SOCKS URL) could not be parsed into a usable proxy
102
+ * config. webhands routes ALL traffic and DNS through one SOCKS proxy, so the
103
+ * value must be a `socks5://` or `socks5h://` URL with a host and port (an
104
+ * optional `user:pass@` is allowed). We refuse a malformed value LOUDLY with
105
+ * this typed condition rather than silently launching with no proxy (which
106
+ * would leak the very traffic the user asked to tunnel). The CLI maps the
107
+ * {@link code} to a fix message showing the expected URL shape.
108
+ *
109
+ * Mirrors {@link MissingStealthDependencyError}: a stable typed error whose
110
+ * brittle detection is confined to one spot (the proxy parser).
111
+ */
112
+ export class InvalidProxyError extends ControllerError {
113
+ readonly code = 'invalid-proxy';
114
+ /** The offending raw `--proxy` value, echoed back so the user can see it. */
115
+ readonly value: string;
116
+
117
+ constructor(
118
+ value: string,
119
+ message: string = `Invalid --proxy value ${JSON.stringify(
120
+ value,
121
+ )}. Expected a SOCKS URL like socks5h://host:1080 or socks5://user:pass@host:1080 (socks5h tunnels DNS too; both route all traffic through the proxy).`,
122
+ options?: {cause?: unknown},
123
+ ) {
124
+ super(message, options);
125
+ this.value = value;
126
+ }
127
+ }
128
+
99
129
  /**
100
130
  * The named profile has not been set up yet: its dedicated profile directory
101
131
  * does not exist on disk. A profile is created by the headed `setup-profile`
package/src/index.ts CHANGED
@@ -42,6 +42,12 @@ export {
42
42
  type StealthChromiumImporter,
43
43
  } from './playwright-launch-transport.js';
44
44
 
45
+ export {
46
+ parseSocksProxy,
47
+ hostResolverRulesArg,
48
+ type ParsedSocksProxy,
49
+ } from './socks-proxy.js';
50
+
45
51
  export {PlaywrightAttachTransport} from './playwright-attach-transport.js';
46
52
 
47
53
  export {
@@ -56,6 +62,7 @@ export {
56
62
  ControllerError,
57
63
  MissingBrowserBinaryError,
58
64
  MissingStealthDependencyError,
65
+ InvalidProxyError,
59
66
  MissingProfileError,
60
67
  AttachNotChromiumError,
61
68
  AttachNoContextError,
@@ -10,6 +10,7 @@ import {
10
10
  resolveProfileLocation,
11
11
  type ProfileLocationOptions,
12
12
  } from './profile-location.js';
13
+ import {hostResolverRulesArg, parseSocksProxy} from './socks-proxy.js';
13
14
  import type {OpenTarget, Session, Transport} from './seam.js';
14
15
 
15
16
  /**
@@ -69,6 +70,76 @@ export interface PlaywrightLaunchTransportOptions {
69
70
  * Playwright vocabulary out of the public surface).
70
71
  */
71
72
  readonly systemBrowser?: string;
73
+ /**
74
+ * Don't impose a fixed emulated viewport: let the browser window drive its own
75
+ * size, exactly as a real user's browser does. Maps to Playwright's
76
+ * `viewport: null` on the persistent context.
77
+ *
78
+ * Why this matters for hardening: Playwright's DEFAULT is a fixed 1280x720
79
+ * emulated viewport that does NOT match the real OS window, a discrepancy
80
+ * (e.g. `window.outerWidth`/`innerWidth`/`screen` mismatches, no real resize
81
+ * behaviour) that fingerprinting scripts read as a headless/automation tell.
82
+ * Patchright's official recommended recipe sets `no_viewport=True` for this
83
+ * reason.
84
+ *
85
+ * Default: `undefined` leaves Playwright's behaviour as-is, EXCEPT that when
86
+ * {@link stealth} is enabled it defaults to `true` (the Patchright recipe).
87
+ * Pass an explicit `false` to keep the fixed emulated viewport even under
88
+ * stealth (e.g. when a caller deliberately wants a deterministic size). We pick
89
+ * the stealth-on default because shipping the stealth engine while leaving the
90
+ * tell it is meant to hide in place would be self-defeating; making it an
91
+ * explicit, overridable default keeps that honest and discoverable.
92
+ */
93
+ readonly noViewport?: boolean;
94
+ /**
95
+ * Extra command-line args appended to the browser launch (Playwright's
96
+ * `args`). An escape hatch for well-known hardening flags Patchright/Chromium
97
+ * users pass (e.g. `--disable-blink-features=AutomationControlled`) WITHOUT
98
+ * leaking a Playwright type across the seam: this is a plain `string[]`, kept
99
+ * confined to this transport-construction policy and deliberately NOT on
100
+ * {@link OpenTarget} (ADR-0003). Default: none.
101
+ *
102
+ * Caveat: args are passed THROUGH verbatim; a wrong or contradictory flag can
103
+ * itself become a tell or break the launch. Opt-in only.
104
+ */
105
+ readonly extraLaunchArgs?: readonly string[];
106
+ /**
107
+ * Passthrough for Playwright's `ignoreDefaultArgs`: either `true` to drop ALL
108
+ * of Playwright's default launch args, or a list of specific default args to
109
+ * drop, so a caller can strip more automation-flavoured defaults than the
110
+ * built-in stealth subset.
111
+ *
112
+ * When omitted, the stealth path still drops `--enable-automation` on its own
113
+ * (unchanged behaviour). When provided, this value REPLACES that built-in
114
+ * choice, so a caller opting in owns the full list (pass
115
+ * `['--enable-automation', ...]` to keep it). Like {@link extraLaunchArgs}
116
+ * this is a plain value confined to this module, never on {@link OpenTarget}.
117
+ * Default: none.
118
+ */
119
+ readonly ignoreDefaultArgs?: boolean | readonly string[];
120
+ /**
121
+ * Route ALL browser traffic AND DNS through a single SOCKS proxy, given as a
122
+ * SOCKS URL: `socks5h://host:1080` (or `socks5://host:1080`, optionally with a
123
+ * `user:pass@` userinfo). When set, the transport forwards the proxy to
124
+ * Playwright's `proxy` launch option AND adds Chromium's `--host-resolver-rules`
125
+ * catch-all so no DNS query escapes locally (see {@link proxyNoLeak}).
126
+ *
127
+ * Scheme convention: `socks5h://` means "resolve DNS at the proxy" (no leak),
128
+ * `socks5://` means "SOCKS5, local DNS allowed" (Chromium still resolves URL
129
+ * hostnames at the proxy, but its DNS prefetcher etc. may issue local DNS). Use
130
+ * {@link proxyNoLeak} to override the scheme's implied DNS behaviour. A
131
+ * malformed value throws the typed {@link InvalidProxyError} rather than
132
+ * launching unproxied. Default: no proxy.
133
+ */
134
+ readonly proxy?: string;
135
+ /**
136
+ * Override whether the {@link proxy} enforces NO local DNS. `true` forces the
137
+ * leak-free catch-all even for a plain `socks5://` URL; `false` allows local
138
+ * DNS even for a `socks5h://` URL. When omitted, the SCHEME decides
139
+ * (`socks5h` => no leak, `socks5`/`socks` => local DNS allowed). Ignored when
140
+ * {@link proxy} is unset.
141
+ */
142
+ readonly proxyNoLeak?: boolean;
72
143
  /**
73
144
  * INTERNAL test seam: override how the stealth chromium is imported. Omit in
74
145
  * production (defaults to `import('patchright')`). See
@@ -123,6 +194,11 @@ export class PlaywrightLaunchTransport implements Transport {
123
194
  readonly #hands: readonly Hand[];
124
195
  readonly #stealth: boolean;
125
196
  readonly #systemBrowser: string | undefined;
197
+ readonly #noViewport: boolean | undefined;
198
+ readonly #extraLaunchArgs: readonly string[] | undefined;
199
+ readonly #ignoreDefaultArgs: boolean | readonly string[] | undefined;
200
+ readonly #proxy: string | undefined;
201
+ readonly #proxyNoLeak: boolean | undefined;
126
202
  readonly #importStealthChromium: StealthChromiumImporter;
127
203
 
128
204
  /**
@@ -134,9 +210,11 @@ export class PlaywrightLaunchTransport implements Transport {
134
210
  * the operator's explicit config; the transport does NOT discover them. Omit
135
211
  * for the built-ins-only surface.
136
212
  * @param options transport-construction policy, notably the opt-in `stealth`
137
- * toggle and optional `systemBrowser` (see
213
+ * toggle, optional `systemBrowser`, and the launch-hardening knobs
214
+ * (`noViewport`, `extraLaunchArgs`, `ignoreDefaultArgs`; see
138
215
  * {@link PlaywrightLaunchTransportOptions}). Defaults to vanilla Playwright,
139
- * bundled Chromium, stealth OFF.
216
+ * bundled Chromium, stealth OFF. The hardening knobs are confined to this
217
+ * module and never reach {@link OpenTarget} (ADR-0003).
140
218
  */
141
219
  constructor(
142
220
  location: ProfileLocationOptions = {},
@@ -147,6 +225,11 @@ export class PlaywrightLaunchTransport implements Transport {
147
225
  this.#hands = hands;
148
226
  this.#stealth = options.stealth === true;
149
227
  this.#systemBrowser = options.systemBrowser;
228
+ this.#noViewport = options.noViewport;
229
+ this.#extraLaunchArgs = options.extraLaunchArgs;
230
+ this.#ignoreDefaultArgs = options.ignoreDefaultArgs;
231
+ this.#proxy = options.proxy;
232
+ this.#proxyNoLeak = options.proxyNoLeak;
150
233
  this.#importStealthChromium =
151
234
  options.importStealthChromium ?? defaultStealthImporter;
152
235
  }
@@ -191,9 +274,56 @@ export class PlaywrightLaunchTransport implements Transport {
191
274
  if (this.#systemBrowser !== undefined) {
192
275
  launchOptions.channel = this.#systemBrowser;
193
276
  }
194
- if (this.#stealth) {
277
+ // no_viewport: explicit caller choice wins; otherwise default to TRUE under
278
+ // stealth (Patchright's recommended recipe), and leave Playwright's default
279
+ // fixed viewport in place when stealth is off. `viewport: null` is how
280
+ // Playwright expresses "let the real window drive the size".
281
+ const noViewport = this.#noViewport ?? this.#stealth;
282
+ if (noViewport) {
283
+ launchOptions.viewport = null;
284
+ }
285
+ // ignoreDefaultArgs: an explicit passthrough REPLACES the built-in stealth
286
+ // choice (the caller then owns the full list). With no passthrough, the
287
+ // stealth path keeps dropping just `--enable-automation` so it cannot re-add
288
+ // the fingerprint Patchright just removed.
289
+ if (this.#ignoreDefaultArgs !== undefined) {
290
+ launchOptions.ignoreDefaultArgs =
291
+ typeof this.#ignoreDefaultArgs === 'boolean'
292
+ ? this.#ignoreDefaultArgs
293
+ : [...this.#ignoreDefaultArgs];
294
+ } else if (this.#stealth) {
195
295
  launchOptions.ignoreDefaultArgs = ['--enable-automation'];
196
296
  }
297
+ // Proxy: route ALL traffic + DNS through one SOCKS proxy. We parse the URL
298
+ // HERE (a malformed value is the typed InvalidProxyError, never a silent
299
+ // unproxied launch), forward it to Playwright's `proxy` option, and when
300
+ // no-leak is in effect add Chromium's --host-resolver-rules catch-all so even
301
+ // the DNS prefetcher cannot leak a raw local DNS query.
302
+ const hardeningArgs: string[] = [];
303
+ if (this.#proxy !== undefined && this.#proxy.trim() !== '') {
304
+ const parsed = parseSocksProxy(this.#proxy, this.#proxyNoLeak);
305
+ launchOptions.proxy = {
306
+ server: parsed.server,
307
+ ...(parsed.username !== undefined ? {username: parsed.username} : {}),
308
+ ...(parsed.password !== undefined ? {password: parsed.password} : {}),
309
+ };
310
+ if (parsed.noLeak) {
311
+ hardeningArgs.push(hostResolverRulesArg(parsed.host));
312
+ }
313
+ }
314
+ // Extra launch args (the hardening escape hatch) are appended verbatim. We do
315
+ // NOT set user-agent/locale/timezone/headers here: a wrong UA is a bigger
316
+ // tell than none (Patchright warns against overriding them), so those stay
317
+ // untouched by default. The proxy's no-leak DNS arg (if any) rides alongside.
318
+ if (
319
+ this.#extraLaunchArgs !== undefined &&
320
+ this.#extraLaunchArgs.length > 0
321
+ ) {
322
+ hardeningArgs.push(...this.#extraLaunchArgs);
323
+ }
324
+ if (hardeningArgs.length > 0) {
325
+ launchOptions.args = hardeningArgs;
326
+ }
197
327
 
198
328
  let context: BrowserContext;
199
329
  try {
@@ -0,0 +1,127 @@
1
+ import {InvalidProxyError} from './errors.js';
2
+
3
+ /**
4
+ * A parsed SOCKS proxy ready to hand to Playwright/Chromium.
5
+ *
6
+ * This is the SINGLE place webhands turns a user-facing `--proxy` SOCKS URL into
7
+ * the concrete launch inputs Chromium needs: the `proxy.server`/credentials
8
+ * Playwright forwards, plus the extra command-line arg that forces DNS through
9
+ * the proxy (no DNS leak). Keeping the brittle parsing + Chromium-flag knowledge
10
+ * in one module mirrors how the launch transport confines its other
11
+ * Playwright/Chromium details.
12
+ */
13
+ export interface ParsedSocksProxy {
14
+ /**
15
+ * The Playwright `proxy.server` value, always normalized to `socks5://host:port`.
16
+ *
17
+ * Chromium's `--proxy-server` understands `socks5://` but NOT the `socks5h://`
18
+ * convention, so we normalize the scheme here and carry the "resolve DNS at
19
+ * the proxy / block local DNS" intent separately in {@link noLeak} instead of
20
+ * in the scheme string.
21
+ */
22
+ readonly server: string;
23
+ /** Optional proxy username (from a `user:pass@` userinfo component). */
24
+ readonly username?: string;
25
+ /** Optional proxy password (from a `user:pass@` userinfo component). */
26
+ readonly password?: string;
27
+ /** The proxy host, used to build the DNS catch-all EXCLUDE rule. */
28
+ readonly host: string;
29
+ /**
30
+ * Whether to enforce NO local DNS (force every hostname to resolve at the
31
+ * proxy). When `true`, the transport adds a `--host-resolver-rules` catch-all
32
+ * so even Chromium components that bypass `--proxy-server` (DNS prefetcher,
33
+ * etc.) cannot leak a raw DNS query (see {@link hostResolverRulesArg}).
34
+ */
35
+ readonly noLeak: boolean;
36
+ }
37
+
38
+ /**
39
+ * The SOCKS schemes we accept on a `--proxy` value.
40
+ *
41
+ * - `socks5h://` is the widely-used convention meaning "resolve DNS at the
42
+ * proxy" (no local DNS, no leak). We map it to {@link ParsedSocksProxy.noLeak}
43
+ * `true`.
44
+ * - `socks5://` means "SOCKS5, local DNS allowed" by the same convention. NOTE:
45
+ * Chromium ALREADY resolves URL hostnames at the proxy under `--proxy-server`,
46
+ * but other components (the DNS prefetcher) can still issue raw local DNS, so
47
+ * plain `socks5://` does NOT by itself guarantee no leak.
48
+ *
49
+ * `socks://` is accepted as an alias for `socks5://` (some tools emit it).
50
+ */
51
+ const SCHEME_NO_LEAK: Readonly<Record<string, boolean>> = {
52
+ 'socks5h:': true,
53
+ 'socks5:': false,
54
+ 'socks:': false,
55
+ };
56
+
57
+ /**
58
+ * Parse a user-facing `--proxy` SOCKS URL into a {@link ParsedSocksProxy}.
59
+ *
60
+ * Accepts `socks5h://`, `socks5://`, or `socks://` with a host and port and an
61
+ * optional `user:pass@` userinfo. Anything else (missing host/port, an http(s)
62
+ * proxy, a bare host with no scheme) is a typed {@link InvalidProxyError} so the
63
+ * caller refuses LOUDLY rather than launching unproxied.
64
+ *
65
+ * `forceNoLeak`, when set, overrides the scheme's implied DNS behaviour: pass
66
+ * `true` to enforce no local DNS even for a plain `socks5://` URL, or `false` to
67
+ * allow local DNS even for `socks5h://`. When omitted, the SCHEME decides
68
+ * (`socks5h` => no-leak, `socks5`/`socks` => local DNS allowed).
69
+ */
70
+ export function parseSocksProxy(
71
+ value: string,
72
+ forceNoLeak?: boolean,
73
+ ): ParsedSocksProxy {
74
+ const trimmed = value.trim();
75
+ if (trimmed === '') {
76
+ throw new InvalidProxyError(value);
77
+ }
78
+
79
+ let url: URL;
80
+ try {
81
+ url = new URL(trimmed);
82
+ } catch (cause) {
83
+ throw new InvalidProxyError(value, undefined, {cause});
84
+ }
85
+
86
+ const schemeNoLeak = SCHEME_NO_LEAK[url.protocol];
87
+ if (schemeNoLeak === undefined) {
88
+ // Not a SOCKS scheme (e.g. http://, https://, socks4://, or no scheme).
89
+ throw new InvalidProxyError(value);
90
+ }
91
+ if (url.hostname === '' || url.port === '') {
92
+ // A host AND an explicit port are both required: Chromium's --proxy-server
93
+ // needs the port, and we will not guess a default.
94
+ throw new InvalidProxyError(value);
95
+ }
96
+
97
+ const noLeak = forceNoLeak ?? schemeNoLeak;
98
+ const server = `socks5://${url.hostname}:${url.port}`;
99
+
100
+ const proxy: ParsedSocksProxy = {
101
+ server,
102
+ host: url.hostname,
103
+ noLeak,
104
+ ...(url.username !== ''
105
+ ? {username: decodeURIComponent(url.username)}
106
+ : {}),
107
+ ...(url.password !== ''
108
+ ? {password: decodeURIComponent(url.password)}
109
+ : {}),
110
+ };
111
+ return proxy;
112
+ }
113
+
114
+ /**
115
+ * Build the Chromium `--host-resolver-rules` argument that prevents ANY local
116
+ * DNS resolution, the catch-all the Chromium SOCKS design doc prescribes for a
117
+ * leak-free SOCKS proxy.
118
+ *
119
+ * `MAP * ~NOTFOUND` maps every hostname to an invalid address so Chromium never
120
+ * issues a real local DNS query; `EXCLUDE <host>` lets Chromium still resolve
121
+ * the proxy server's own address (otherwise every request fails with
122
+ * PROXY_CONNECTION_FAILED). URL loads themselves resolve at the proxy via
123
+ * `--proxy-server`; this arg closes the side channels (DNS prefetcher, etc.).
124
+ */
125
+ export function hostResolverRulesArg(host: string): string {
126
+ return `--host-resolver-rules=MAP * ~NOTFOUND , EXCLUDE ${host}`;
127
+ }