browserclaw 0.2.7 → 0.2.9
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/dist/index.cjs +54 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -8
- package/dist/index.d.ts +43 -8
- package/dist/index.js +53 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -6,6 +6,18 @@ interface FrameEvalResult {
|
|
|
6
6
|
result: unknown;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Policy for controlling which URLs browser navigation is allowed to reach.
|
|
11
|
+
* By default all private/internal addresses are blocked to prevent SSRF attacks.
|
|
12
|
+
*/
|
|
13
|
+
interface SsrfPolicy {
|
|
14
|
+
/** Allow navigation to private/internal network addresses. Default: `false` */
|
|
15
|
+
allowPrivateNetwork?: boolean;
|
|
16
|
+
/** Hostnames explicitly allowed even if they resolve to private addresses */
|
|
17
|
+
allowedHostnames?: string[];
|
|
18
|
+
/** Alias for allowedHostnames */
|
|
19
|
+
hostnameAllowlist?: string[];
|
|
20
|
+
}
|
|
9
21
|
/** Supported browser types that can be detected and launched. */
|
|
10
22
|
type ChromeKind = 'chrome' | 'brave' | 'edge' | 'chromium' | 'canary' | 'custom';
|
|
11
23
|
/** A detected browser executable on the system. */
|
|
@@ -33,19 +45,27 @@ interface LaunchOptions {
|
|
|
33
45
|
profileColor?: string;
|
|
34
46
|
/** Additional Chrome command-line arguments (e.g. `['--start-maximized']`). */
|
|
35
47
|
chromeArgs?: string[];
|
|
48
|
+
/**
|
|
49
|
+
* SSRF policy controlling which URLs navigation is allowed to reach.
|
|
50
|
+
* By default all private/internal addresses are blocked.
|
|
51
|
+
*/
|
|
52
|
+
ssrfPolicy?: SsrfPolicy;
|
|
36
53
|
/**
|
|
37
54
|
* Allow navigation to internal/loopback addresses (localhost, 127.x, private IPs).
|
|
38
|
-
*
|
|
39
|
-
* Set to `true` if you need to access local development servers.
|
|
55
|
+
* @deprecated Use `ssrfPolicy: { allowPrivateNetwork: true }` instead.
|
|
40
56
|
*/
|
|
41
57
|
allowInternal?: boolean;
|
|
42
58
|
}
|
|
43
59
|
/** Options for connecting to an existing browser instance. */
|
|
44
60
|
interface ConnectOptions {
|
|
61
|
+
/**
|
|
62
|
+
* SSRF policy controlling which URLs navigation is allowed to reach.
|
|
63
|
+
* By default all private/internal addresses are blocked.
|
|
64
|
+
*/
|
|
65
|
+
ssrfPolicy?: SsrfPolicy;
|
|
45
66
|
/**
|
|
46
67
|
* Allow navigation to internal/loopback addresses (localhost, 127.x, private IPs).
|
|
47
|
-
*
|
|
48
|
-
* Set to `true` if you need to access local development servers.
|
|
68
|
+
* @deprecated Use `ssrfPolicy: { allowPrivateNetwork: true }` instead.
|
|
49
69
|
*/
|
|
50
70
|
allowInternal?: boolean;
|
|
51
71
|
/**
|
|
@@ -390,9 +410,9 @@ interface HttpCredentials {
|
|
|
390
410
|
declare class CrawlPage {
|
|
391
411
|
private readonly cdpUrl;
|
|
392
412
|
private readonly targetId;
|
|
393
|
-
private readonly
|
|
413
|
+
private readonly ssrfPolicy;
|
|
394
414
|
/** @internal */
|
|
395
|
-
constructor(cdpUrl: string, targetId: string,
|
|
415
|
+
constructor(cdpUrl: string, targetId: string, ssrfPolicy?: SsrfPolicy);
|
|
396
416
|
/** The CDP target ID for this page. Use this to identify the page in multi-tab scenarios. */
|
|
397
417
|
get id(): string;
|
|
398
418
|
/**
|
|
@@ -987,7 +1007,7 @@ declare class CrawlPage {
|
|
|
987
1007
|
*/
|
|
988
1008
|
declare class BrowserClaw {
|
|
989
1009
|
private readonly cdpUrl;
|
|
990
|
-
private readonly
|
|
1010
|
+
private readonly ssrfPolicy;
|
|
991
1011
|
private chrome;
|
|
992
1012
|
private constructor();
|
|
993
1013
|
/**
|
|
@@ -1087,4 +1107,19 @@ declare class BrowserClaw {
|
|
|
1087
1107
|
stop(): Promise<void>;
|
|
1088
1108
|
}
|
|
1089
1109
|
|
|
1090
|
-
|
|
1110
|
+
/**
|
|
1111
|
+
* Thrown when a navigation URL is blocked by SSRF policy.
|
|
1112
|
+
* Callers can catch this specifically to distinguish navigation blocks
|
|
1113
|
+
* from other errors.
|
|
1114
|
+
*/
|
|
1115
|
+
declare class InvalidBrowserNavigationUrlError extends Error {
|
|
1116
|
+
constructor(message: string);
|
|
1117
|
+
}
|
|
1118
|
+
/** Options for browser navigation SSRF policy. */
|
|
1119
|
+
type BrowserNavigationPolicyOptions = {
|
|
1120
|
+
ssrfPolicy?: SsrfPolicy;
|
|
1121
|
+
};
|
|
1122
|
+
/** Build a BrowserNavigationPolicyOptions from an SsrfPolicy. */
|
|
1123
|
+
declare function withBrowserNavigationPolicy(ssrfPolicy?: SsrfPolicy): BrowserNavigationPolicyOptions;
|
|
1124
|
+
|
|
1125
|
+
export { type AriaNode, type AriaSnapshotResult, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserTab, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type CookieData, CrawlPage, type DialogOptions, type DownloadResult, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type NetworkRequest, type PageError, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, withBrowserNavigationPolicy };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,18 @@ interface FrameEvalResult {
|
|
|
6
6
|
result: unknown;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Policy for controlling which URLs browser navigation is allowed to reach.
|
|
11
|
+
* By default all private/internal addresses are blocked to prevent SSRF attacks.
|
|
12
|
+
*/
|
|
13
|
+
interface SsrfPolicy {
|
|
14
|
+
/** Allow navigation to private/internal network addresses. Default: `false` */
|
|
15
|
+
allowPrivateNetwork?: boolean;
|
|
16
|
+
/** Hostnames explicitly allowed even if they resolve to private addresses */
|
|
17
|
+
allowedHostnames?: string[];
|
|
18
|
+
/** Alias for allowedHostnames */
|
|
19
|
+
hostnameAllowlist?: string[];
|
|
20
|
+
}
|
|
9
21
|
/** Supported browser types that can be detected and launched. */
|
|
10
22
|
type ChromeKind = 'chrome' | 'brave' | 'edge' | 'chromium' | 'canary' | 'custom';
|
|
11
23
|
/** A detected browser executable on the system. */
|
|
@@ -33,19 +45,27 @@ interface LaunchOptions {
|
|
|
33
45
|
profileColor?: string;
|
|
34
46
|
/** Additional Chrome command-line arguments (e.g. `['--start-maximized']`). */
|
|
35
47
|
chromeArgs?: string[];
|
|
48
|
+
/**
|
|
49
|
+
* SSRF policy controlling which URLs navigation is allowed to reach.
|
|
50
|
+
* By default all private/internal addresses are blocked.
|
|
51
|
+
*/
|
|
52
|
+
ssrfPolicy?: SsrfPolicy;
|
|
36
53
|
/**
|
|
37
54
|
* Allow navigation to internal/loopback addresses (localhost, 127.x, private IPs).
|
|
38
|
-
*
|
|
39
|
-
* Set to `true` if you need to access local development servers.
|
|
55
|
+
* @deprecated Use `ssrfPolicy: { allowPrivateNetwork: true }` instead.
|
|
40
56
|
*/
|
|
41
57
|
allowInternal?: boolean;
|
|
42
58
|
}
|
|
43
59
|
/** Options for connecting to an existing browser instance. */
|
|
44
60
|
interface ConnectOptions {
|
|
61
|
+
/**
|
|
62
|
+
* SSRF policy controlling which URLs navigation is allowed to reach.
|
|
63
|
+
* By default all private/internal addresses are blocked.
|
|
64
|
+
*/
|
|
65
|
+
ssrfPolicy?: SsrfPolicy;
|
|
45
66
|
/**
|
|
46
67
|
* Allow navigation to internal/loopback addresses (localhost, 127.x, private IPs).
|
|
47
|
-
*
|
|
48
|
-
* Set to `true` if you need to access local development servers.
|
|
68
|
+
* @deprecated Use `ssrfPolicy: { allowPrivateNetwork: true }` instead.
|
|
49
69
|
*/
|
|
50
70
|
allowInternal?: boolean;
|
|
51
71
|
/**
|
|
@@ -390,9 +410,9 @@ interface HttpCredentials {
|
|
|
390
410
|
declare class CrawlPage {
|
|
391
411
|
private readonly cdpUrl;
|
|
392
412
|
private readonly targetId;
|
|
393
|
-
private readonly
|
|
413
|
+
private readonly ssrfPolicy;
|
|
394
414
|
/** @internal */
|
|
395
|
-
constructor(cdpUrl: string, targetId: string,
|
|
415
|
+
constructor(cdpUrl: string, targetId: string, ssrfPolicy?: SsrfPolicy);
|
|
396
416
|
/** The CDP target ID for this page. Use this to identify the page in multi-tab scenarios. */
|
|
397
417
|
get id(): string;
|
|
398
418
|
/**
|
|
@@ -987,7 +1007,7 @@ declare class CrawlPage {
|
|
|
987
1007
|
*/
|
|
988
1008
|
declare class BrowserClaw {
|
|
989
1009
|
private readonly cdpUrl;
|
|
990
|
-
private readonly
|
|
1010
|
+
private readonly ssrfPolicy;
|
|
991
1011
|
private chrome;
|
|
992
1012
|
private constructor();
|
|
993
1013
|
/**
|
|
@@ -1087,4 +1107,19 @@ declare class BrowserClaw {
|
|
|
1087
1107
|
stop(): Promise<void>;
|
|
1088
1108
|
}
|
|
1089
1109
|
|
|
1090
|
-
|
|
1110
|
+
/**
|
|
1111
|
+
* Thrown when a navigation URL is blocked by SSRF policy.
|
|
1112
|
+
* Callers can catch this specifically to distinguish navigation blocks
|
|
1113
|
+
* from other errors.
|
|
1114
|
+
*/
|
|
1115
|
+
declare class InvalidBrowserNavigationUrlError extends Error {
|
|
1116
|
+
constructor(message: string);
|
|
1117
|
+
}
|
|
1118
|
+
/** Options for browser navigation SSRF policy. */
|
|
1119
|
+
type BrowserNavigationPolicyOptions = {
|
|
1120
|
+
ssrfPolicy?: SsrfPolicy;
|
|
1121
|
+
};
|
|
1122
|
+
/** Build a BrowserNavigationPolicyOptions from an SsrfPolicy. */
|
|
1123
|
+
declare function withBrowserNavigationPolicy(ssrfPolicy?: SsrfPolicy): BrowserNavigationPolicyOptions;
|
|
1124
|
+
|
|
1125
|
+
export { type AriaNode, type AriaSnapshotResult, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserTab, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type CookieData, CrawlPage, type DialogOptions, type DownloadResult, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type NetworkRequest, type PageError, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, withBrowserNavigationPolicy };
|
package/dist/index.js
CHANGED
|
@@ -1378,6 +1378,38 @@ async function pressKeyViaPlaywright(opts) {
|
|
|
1378
1378
|
ensurePageState(page);
|
|
1379
1379
|
await page.keyboard.press(key, { delay: Math.max(0, Math.floor(opts.delayMs ?? 0)) });
|
|
1380
1380
|
}
|
|
1381
|
+
var InvalidBrowserNavigationUrlError = class extends Error {
|
|
1382
|
+
constructor(message) {
|
|
1383
|
+
super(message);
|
|
1384
|
+
this.name = "InvalidBrowserNavigationUrlError";
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
function withBrowserNavigationPolicy(ssrfPolicy) {
|
|
1388
|
+
return { ssrfPolicy };
|
|
1389
|
+
}
|
|
1390
|
+
async function assertBrowserNavigationAllowed(opts) {
|
|
1391
|
+
const policy = opts.ssrfPolicy;
|
|
1392
|
+
if (policy?.allowPrivateNetwork) return;
|
|
1393
|
+
const allowedHostnames = [
|
|
1394
|
+
...policy?.allowedHostnames ?? [],
|
|
1395
|
+
...policy?.hostnameAllowlist ?? []
|
|
1396
|
+
];
|
|
1397
|
+
if (allowedHostnames.length) {
|
|
1398
|
+
let parsed;
|
|
1399
|
+
try {
|
|
1400
|
+
parsed = new URL(opts.url);
|
|
1401
|
+
} catch {
|
|
1402
|
+
throw new InvalidBrowserNavigationUrlError(`Invalid URL: "${opts.url}"`);
|
|
1403
|
+
}
|
|
1404
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
1405
|
+
if (allowedHostnames.some((h) => h.toLowerCase() === hostname)) return;
|
|
1406
|
+
}
|
|
1407
|
+
if (await isInternalUrlResolved(opts.url)) {
|
|
1408
|
+
throw new InvalidBrowserNavigationUrlError(
|
|
1409
|
+
`Navigation to internal/loopback address blocked: "${opts.url}". Use ssrfPolicy: { allowPrivateNetwork: true } if this is intentional.`
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1381
1413
|
function assertSafeOutputPath(path2, allowedRoots) {
|
|
1382
1414
|
if (!path2 || typeof path2 !== "string") {
|
|
1383
1415
|
throw new Error("Output path is required.");
|
|
@@ -1521,9 +1553,8 @@ async function isInternalUrlResolved(url) {
|
|
|
1521
1553
|
async function navigateViaPlaywright(opts) {
|
|
1522
1554
|
const url = String(opts.url ?? "").trim();
|
|
1523
1555
|
if (!url) throw new Error("url is required");
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
}
|
|
1556
|
+
const policy = opts.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts.ssrfPolicy;
|
|
1557
|
+
await assertBrowserNavigationAllowed({ url, ssrfPolicy: policy });
|
|
1527
1558
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
1528
1559
|
ensurePageState(page);
|
|
1529
1560
|
await page.goto(url, { timeout: normalizeTimeoutMs(opts.timeoutMs, 2e4) });
|
|
@@ -1546,8 +1577,9 @@ async function listPagesViaPlaywright(opts) {
|
|
|
1546
1577
|
}
|
|
1547
1578
|
async function createPageViaPlaywright(opts) {
|
|
1548
1579
|
const targetUrl = (opts.url ?? "").trim() || "about:blank";
|
|
1549
|
-
if (targetUrl !== "about:blank"
|
|
1550
|
-
|
|
1580
|
+
if (targetUrl !== "about:blank") {
|
|
1581
|
+
const policy = opts.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts.ssrfPolicy;
|
|
1582
|
+
await assertBrowserNavigationAllowed({ url: targetUrl, ssrfPolicy: policy });
|
|
1551
1583
|
}
|
|
1552
1584
|
const { browser } = await connectBrowser(opts.cdpUrl);
|
|
1553
1585
|
const context = browser.contexts()[0] ?? await browser.newContext();
|
|
@@ -2055,12 +2087,12 @@ async function storageClearViaPlaywright(opts) {
|
|
|
2055
2087
|
var CrawlPage = class {
|
|
2056
2088
|
cdpUrl;
|
|
2057
2089
|
targetId;
|
|
2058
|
-
|
|
2090
|
+
ssrfPolicy;
|
|
2059
2091
|
/** @internal */
|
|
2060
|
-
constructor(cdpUrl, targetId,
|
|
2092
|
+
constructor(cdpUrl, targetId, ssrfPolicy) {
|
|
2061
2093
|
this.cdpUrl = cdpUrl;
|
|
2062
2094
|
this.targetId = targetId;
|
|
2063
|
-
this.
|
|
2095
|
+
this.ssrfPolicy = ssrfPolicy;
|
|
2064
2096
|
}
|
|
2065
2097
|
/** The CDP target ID for this page. Use this to identify the page in multi-tab scenarios. */
|
|
2066
2098
|
get id() {
|
|
@@ -2393,7 +2425,7 @@ var CrawlPage = class {
|
|
|
2393
2425
|
targetId: this.targetId,
|
|
2394
2426
|
url,
|
|
2395
2427
|
timeoutMs: opts?.timeoutMs,
|
|
2396
|
-
|
|
2428
|
+
ssrfPolicy: this.ssrfPolicy
|
|
2397
2429
|
});
|
|
2398
2430
|
}
|
|
2399
2431
|
/**
|
|
@@ -2923,12 +2955,12 @@ var CrawlPage = class {
|
|
|
2923
2955
|
};
|
|
2924
2956
|
var BrowserClaw = class _BrowserClaw {
|
|
2925
2957
|
cdpUrl;
|
|
2926
|
-
|
|
2958
|
+
ssrfPolicy;
|
|
2927
2959
|
chrome;
|
|
2928
|
-
constructor(cdpUrl, chrome,
|
|
2960
|
+
constructor(cdpUrl, chrome, ssrfPolicy) {
|
|
2929
2961
|
this.cdpUrl = cdpUrl;
|
|
2930
2962
|
this.chrome = chrome;
|
|
2931
|
-
this.
|
|
2963
|
+
this.ssrfPolicy = ssrfPolicy;
|
|
2932
2964
|
}
|
|
2933
2965
|
/**
|
|
2934
2966
|
* Launch a new Chrome instance and connect to it.
|
|
@@ -2956,7 +2988,8 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
2956
2988
|
static async launch(opts = {}) {
|
|
2957
2989
|
const chrome = await launchChrome(opts);
|
|
2958
2990
|
const cdpUrl = `http://127.0.0.1:${chrome.cdpPort}`;
|
|
2959
|
-
|
|
2991
|
+
const ssrfPolicy = opts.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts.ssrfPolicy;
|
|
2992
|
+
return new _BrowserClaw(cdpUrl, chrome, ssrfPolicy);
|
|
2960
2993
|
}
|
|
2961
2994
|
/**
|
|
2962
2995
|
* Connect to an already-running Chrome instance via its CDP endpoint.
|
|
@@ -2977,7 +3010,8 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
2977
3010
|
throw new Error(`Cannot connect to Chrome at ${cdpUrl}. Is Chrome running with --remote-debugging-port?`);
|
|
2978
3011
|
}
|
|
2979
3012
|
await connectBrowser(cdpUrl, opts?.authToken);
|
|
2980
|
-
|
|
3013
|
+
const ssrfPolicy = opts?.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts?.ssrfPolicy;
|
|
3014
|
+
return new _BrowserClaw(cdpUrl, null, ssrfPolicy);
|
|
2981
3015
|
}
|
|
2982
3016
|
/**
|
|
2983
3017
|
* Open a URL in a new tab and return the page handle.
|
|
@@ -2992,8 +3026,8 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
2992
3026
|
* ```
|
|
2993
3027
|
*/
|
|
2994
3028
|
async open(url) {
|
|
2995
|
-
const tab = await createPageViaPlaywright({ cdpUrl: this.cdpUrl, url,
|
|
2996
|
-
return new CrawlPage(this.cdpUrl, tab.targetId, this.
|
|
3029
|
+
const tab = await createPageViaPlaywright({ cdpUrl: this.cdpUrl, url, ssrfPolicy: this.ssrfPolicy });
|
|
3030
|
+
return new CrawlPage(this.cdpUrl, tab.targetId, this.ssrfPolicy);
|
|
2997
3031
|
}
|
|
2998
3032
|
/**
|
|
2999
3033
|
* Get a CrawlPage handle for the currently active tab.
|
|
@@ -3006,7 +3040,7 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
3006
3040
|
if (!pages.length) throw new Error("No pages available. Use browser.open(url) to create a tab.");
|
|
3007
3041
|
const tid = await pageTargetId(pages[0]).catch(() => null);
|
|
3008
3042
|
if (!tid) throw new Error("Failed to get targetId for the current page.");
|
|
3009
|
-
return new CrawlPage(this.cdpUrl, tid, this.
|
|
3043
|
+
return new CrawlPage(this.cdpUrl, tid, this.ssrfPolicy);
|
|
3010
3044
|
}
|
|
3011
3045
|
/**
|
|
3012
3046
|
* List all open tabs.
|
|
@@ -3041,7 +3075,7 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
3041
3075
|
* @returns CrawlPage for the specified tab
|
|
3042
3076
|
*/
|
|
3043
3077
|
page(targetId) {
|
|
3044
|
-
return new CrawlPage(this.cdpUrl, targetId, this.
|
|
3078
|
+
return new CrawlPage(this.cdpUrl, targetId, this.ssrfPolicy);
|
|
3045
3079
|
}
|
|
3046
3080
|
/** The CDP endpoint URL for this browser connection. */
|
|
3047
3081
|
get url() {
|
|
@@ -3063,6 +3097,6 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
3063
3097
|
}
|
|
3064
3098
|
};
|
|
3065
3099
|
|
|
3066
|
-
export { BrowserClaw, CrawlPage };
|
|
3100
|
+
export { BrowserClaw, CrawlPage, InvalidBrowserNavigationUrlError, withBrowserNavigationPolicy };
|
|
3067
3101
|
//# sourceMappingURL=index.js.map
|
|
3068
3102
|
//# sourceMappingURL=index.js.map
|