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.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
- * Default: `false` internal URLs are blocked to prevent SSRF attacks.
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
- * Default: `false` internal URLs are blocked to prevent SSRF attacks.
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 allowInternal;
413
+ private readonly ssrfPolicy;
394
414
  /** @internal */
395
- constructor(cdpUrl: string, targetId: string, allowInternal?: boolean);
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 allowInternal;
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
- export { type AriaNode, type AriaSnapshotResult, BrowserClaw, 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, type LaunchOptions, type NetworkRequest, type PageError, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions };
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
- * Default: `false` internal URLs are blocked to prevent SSRF attacks.
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
- * Default: `false` internal URLs are blocked to prevent SSRF attacks.
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 allowInternal;
413
+ private readonly ssrfPolicy;
394
414
  /** @internal */
395
- constructor(cdpUrl: string, targetId: string, allowInternal?: boolean);
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 allowInternal;
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
- export { type AriaNode, type AriaSnapshotResult, BrowserClaw, 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, type LaunchOptions, type NetworkRequest, type PageError, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions };
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
- if (!opts.allowInternal && await isInternalUrlResolved(url)) {
1525
- throw new Error(`Navigation to internal/loopback address blocked: "${url}". Set allowInternal: true if this is intentional.`);
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" && !opts.allowInternal && await isInternalUrlResolved(targetUrl)) {
1550
- throw new Error(`Navigation to internal/loopback address blocked: "${targetUrl}". Set allowInternal: true if this is intentional.`);
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
- allowInternal;
2090
+ ssrfPolicy;
2059
2091
  /** @internal */
2060
- constructor(cdpUrl, targetId, allowInternal = false) {
2092
+ constructor(cdpUrl, targetId, ssrfPolicy) {
2061
2093
  this.cdpUrl = cdpUrl;
2062
2094
  this.targetId = targetId;
2063
- this.allowInternal = allowInternal;
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
- allowInternal: this.allowInternal
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
- allowInternal;
2958
+ ssrfPolicy;
2927
2959
  chrome;
2928
- constructor(cdpUrl, chrome, allowInternal = false) {
2960
+ constructor(cdpUrl, chrome, ssrfPolicy) {
2929
2961
  this.cdpUrl = cdpUrl;
2930
2962
  this.chrome = chrome;
2931
- this.allowInternal = allowInternal;
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
- return new _BrowserClaw(cdpUrl, chrome, opts.allowInternal);
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
- return new _BrowserClaw(cdpUrl, null, opts?.allowInternal);
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, allowInternal: this.allowInternal });
2996
- return new CrawlPage(this.cdpUrl, tab.targetId, this.allowInternal);
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.allowInternal);
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.allowInternal);
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