@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 +65 -6
- package/dist/errors.d.ts +21 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/playwright-launch-transport.d.ts +74 -2
- package/dist/playwright-launch-transport.d.ts.map +1 -1
- package/dist/playwright-launch-transport.js +62 -3
- package/dist/playwright-launch-transport.js.map +1 -1
- package/dist/socks-proxy.d.ts +61 -0
- package/dist/socks-proxy.d.ts.map +1 -0
- package/dist/socks-proxy.js +84 -0
- package/dist/socks-proxy.js.map +1 -0
- package/package.json +1 -1
- package/src/errors.ts +30 -0
- package/src/index.ts +7 -0
- package/src/playwright-launch-transport.ts +133 -3
- package/src/socks-proxy.ts +127 -0
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
|
-
|
|
135
|
-
|
|
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
|
|
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`
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -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":"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"}
|
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`
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAaH;;;;;;GAMG;AACH,MAAM,OAAgB,eAAgB,SAAQ,KAAK;IAGlD,8EAA8E;IACrE,iBAAiB,GAAG,IAAa,CAAC;IAE3C,YAAY,OAAe,EAAE,OAA2B;QACvD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,sEAAsE;QACtE,yCAAyC;QACzC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;IAC7B,CAAC;CACD;AAED;;;;GAIG;AACH,MAAM,OAAO,yBAA0B,SAAQ,eAAe;IACpD,IAAI,GAAG,wBAAwB,CAAC;IACzC,6DAA6D;IACpD,OAAO,CAAS;IAEzB,YACC,OAAe,EACf,UAAkB,OAAO,OAAO,mCAAmC,EACnE,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;CACD;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,6BAA8B,SAAQ,eAAe;IACxD,IAAI,GAAG,4BAA4B,CAAC;IAC7C,kEAAkE;IACzD,UAAU,CAAS;IAE5B,YACC,UAAU,GAAG,YAAY,EACzB,UAAkB,+CAA+C,UAAU,6DAA6D,UAAU,aAAa,UAAU,+GAA+G,EACxR,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC9B,CAAC;CACD;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,iBAAkB,SAAQ,eAAe;IAC5C,IAAI,GAAG,eAAe,CAAC;IAChC,6EAA6E;IACpE,KAAK,CAAS;IAEvB,YACC,KAAa,EACb,UAAkB,yBAAyB,IAAI,CAAC,SAAS,CACxD,KAAK,CACL,sJAAsJ,EACvJ,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;CACD;AAED;;;;;GAKG;AACH,MAAM,OAAO,mBAAoB,SAAQ,eAAe;IAC9C,IAAI,GAAG,iBAAiB,CAAC;IAClC,kDAAkD;IACzC,OAAO,CAAS;IACzB,kEAAkE;IACzD,UAAU,CAAS;IAE5B,YACC,OAAe,EACf,UAAkB,EAClB,UAAkB,QAAQ,OAAO,oDAAoD,UAAU,IAAI,EACnG,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC9B,CAAC;CACD;AAED;;;;;;GAMG;AACH,MAAM,OAAO,sBAAuB,SAAQ,eAAe;IACjD,IAAI,GAAG,qBAAqB,CAAC;IACtC,4EAA4E;IACnE,OAAO,CAAS;IAEzB,YACC,OAAe,EACf,UAAkB,oDAAoD,OAAO,mFAAmF,EAChK,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;CACD;AAED;;;;;GAKG;AACH,MAAM,OAAO,oBAAqB,SAAQ,eAAe;IAC/C,IAAI,GAAG,mBAAmB,CAAC;IACpC,qDAAqD;IAC5C,QAAQ,CAAS;IAE1B,YACC,QAAgB,EAChB,UAAkB,+CAA+C,QAAQ,+DAA+D,EACxI,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;CACD;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAkB,SAAQ,eAAe;IAC5C,IAAI,GAAG,gBAAgB,CAAC;IAEjC,YACC,UAAkB,2EAA2E,EAC7F,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACzB,CAAC;CACD;AAED;;;;;GAKG;AACH,MAAM,OAAO,yBAA0B,SAAQ,eAAe;IACpD,IAAI,GAAG,wBAAwB,CAAC;IAEzC,YACC,UAAkB,0DAA0D,EAC5E,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACzB,CAAC;CACD;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC/C,OAAO,CACN,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACb,KAAuC,CAAC,iBAAiB,KAAK,IAAI;QACnE,OAAQ,KAA0B,CAAC,IAAI,KAAK,QAAQ,CACpD,CAAC;AACH,CAAC"}
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
|
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;
|
|
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
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAC;AACtC,OAAO,EAAC,QAAQ,EAAiC,MAAM,YAAY,CAAC;AACpE,OAAO,EACN,yBAAyB,EACzB,mBAAmB,EACnB,6BAA6B,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAC,gBAAgB,EAA8B,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EACN,sBAAsB,GAEtB,MAAM,uBAAuB,CAAC;
|
|
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
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
|
|
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
|
-
|
|
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
|
+
}
|