browserclaw 0.2.8 → 0.3.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/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as playwright_core from 'playwright-core';
2
+ import { lookup } from 'node:dns/promises';
2
3
 
3
4
  interface FrameEvalResult {
4
5
  frameUrl: string;
@@ -6,6 +7,18 @@ interface FrameEvalResult {
6
7
  result: unknown;
7
8
  }
8
9
 
10
+ /**
11
+ * Policy for controlling which URLs browser navigation is allowed to reach.
12
+ * By default all private/internal addresses are blocked to prevent SSRF attacks.
13
+ */
14
+ interface SsrfPolicy {
15
+ /** Allow navigation to private/internal network addresses. Default: `false` */
16
+ allowPrivateNetwork?: boolean;
17
+ /** Hostnames explicitly allowed even if they resolve to private addresses */
18
+ allowedHostnames?: string[];
19
+ /** Alias for allowedHostnames */
20
+ hostnameAllowlist?: string[];
21
+ }
9
22
  /** Supported browser types that can be detected and launched. */
10
23
  type ChromeKind = 'chrome' | 'brave' | 'edge' | 'chromium' | 'canary' | 'custom';
11
24
  /** A detected browser executable on the system. */
@@ -33,19 +46,27 @@ interface LaunchOptions {
33
46
  profileColor?: string;
34
47
  /** Additional Chrome command-line arguments (e.g. `['--start-maximized']`). */
35
48
  chromeArgs?: string[];
49
+ /**
50
+ * SSRF policy controlling which URLs navigation is allowed to reach.
51
+ * By default all private/internal addresses are blocked.
52
+ */
53
+ ssrfPolicy?: SsrfPolicy;
36
54
  /**
37
55
  * 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.
56
+ * @deprecated Use `ssrfPolicy: { allowPrivateNetwork: true }` instead.
40
57
  */
41
58
  allowInternal?: boolean;
42
59
  }
43
60
  /** Options for connecting to an existing browser instance. */
44
61
  interface ConnectOptions {
62
+ /**
63
+ * SSRF policy controlling which URLs navigation is allowed to reach.
64
+ * By default all private/internal addresses are blocked.
65
+ */
66
+ ssrfPolicy?: SsrfPolicy;
45
67
  /**
46
68
  * 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.
69
+ * @deprecated Use `ssrfPolicy: { allowPrivateNetwork: true }` instead.
49
70
  */
50
71
  allowInternal?: boolean;
51
72
  /**
@@ -176,7 +197,7 @@ interface ClickOptions {
176
197
  /** Mouse button to use */
177
198
  button?: 'left' | 'right' | 'middle';
178
199
  /** Modifier keys to hold during click */
179
- modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[];
200
+ modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[];
180
201
  /** Timeout in milliseconds. Default: `8000` */
181
202
  timeoutMs?: number;
182
203
  }
@@ -390,9 +411,9 @@ interface HttpCredentials {
390
411
  declare class CrawlPage {
391
412
  private readonly cdpUrl;
392
413
  private readonly targetId;
393
- private readonly allowInternal;
414
+ private readonly ssrfPolicy;
394
415
  /** @internal */
395
- constructor(cdpUrl: string, targetId: string, allowInternal?: boolean);
416
+ constructor(cdpUrl: string, targetId: string, ssrfPolicy?: SsrfPolicy);
396
417
  /** The CDP target ID for this page. Use this to identify the page in multi-tab scenarios. */
397
418
  get id(): string;
398
419
  /**
@@ -663,6 +684,8 @@ declare class CrawlPage {
663
684
  */
664
685
  evaluate(fn: string, opts?: {
665
686
  ref?: string;
687
+ timeoutMs?: number;
688
+ signal?: AbortSignal;
666
689
  }): Promise<unknown>;
667
690
  /**
668
691
  * Run JavaScript in ALL frames on the page (including cross-origin iframes).
@@ -987,7 +1010,7 @@ declare class CrawlPage {
987
1010
  */
988
1011
  declare class BrowserClaw {
989
1012
  private readonly cdpUrl;
990
- private readonly allowInternal;
1013
+ private readonly ssrfPolicy;
991
1014
  private chrome;
992
1015
  private constructor();
993
1016
  /**
@@ -1087,6 +1110,7 @@ declare class BrowserClaw {
1087
1110
  stop(): Promise<void>;
1088
1111
  }
1089
1112
 
1113
+ type LookupFn = typeof lookup;
1090
1114
  /**
1091
1115
  * Thrown when a navigation URL is blocked by SSRF policy.
1092
1116
  * Callers can catch this specifically to distinguish navigation blocks
@@ -1095,5 +1119,19 @@ declare class BrowserClaw {
1095
1119
  declare class InvalidBrowserNavigationUrlError extends Error {
1096
1120
  constructor(message: string);
1097
1121
  }
1122
+ /** Options for browser navigation SSRF policy. */
1123
+ type BrowserNavigationPolicyOptions = {
1124
+ ssrfPolicy?: SsrfPolicy;
1125
+ };
1126
+ /** Build a BrowserNavigationPolicyOptions from an SsrfPolicy. */
1127
+ declare function withBrowserNavigationPolicy(ssrfPolicy?: SsrfPolicy): BrowserNavigationPolicyOptions;
1128
+ /**
1129
+ * Assert that a URL is allowed for browser navigation under the given SSRF policy.
1130
+ * Throws `InvalidBrowserNavigationUrlError` if the URL is blocked.
1131
+ */
1132
+ declare function assertBrowserNavigationAllowed(opts: {
1133
+ url: string;
1134
+ lookupFn?: LookupFn;
1135
+ } & BrowserNavigationPolicyOptions): Promise<void>;
1098
1136
 
1099
- 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, InvalidBrowserNavigationUrlError, 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 };
1137
+ 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 LookupFn, 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, assertBrowserNavigationAllowed, withBrowserNavigationPolicy };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as playwright_core from 'playwright-core';
2
+ import { lookup } from 'node:dns/promises';
2
3
 
3
4
  interface FrameEvalResult {
4
5
  frameUrl: string;
@@ -6,6 +7,18 @@ interface FrameEvalResult {
6
7
  result: unknown;
7
8
  }
8
9
 
10
+ /**
11
+ * Policy for controlling which URLs browser navigation is allowed to reach.
12
+ * By default all private/internal addresses are blocked to prevent SSRF attacks.
13
+ */
14
+ interface SsrfPolicy {
15
+ /** Allow navigation to private/internal network addresses. Default: `false` */
16
+ allowPrivateNetwork?: boolean;
17
+ /** Hostnames explicitly allowed even if they resolve to private addresses */
18
+ allowedHostnames?: string[];
19
+ /** Alias for allowedHostnames */
20
+ hostnameAllowlist?: string[];
21
+ }
9
22
  /** Supported browser types that can be detected and launched. */
10
23
  type ChromeKind = 'chrome' | 'brave' | 'edge' | 'chromium' | 'canary' | 'custom';
11
24
  /** A detected browser executable on the system. */
@@ -33,19 +46,27 @@ interface LaunchOptions {
33
46
  profileColor?: string;
34
47
  /** Additional Chrome command-line arguments (e.g. `['--start-maximized']`). */
35
48
  chromeArgs?: string[];
49
+ /**
50
+ * SSRF policy controlling which URLs navigation is allowed to reach.
51
+ * By default all private/internal addresses are blocked.
52
+ */
53
+ ssrfPolicy?: SsrfPolicy;
36
54
  /**
37
55
  * 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.
56
+ * @deprecated Use `ssrfPolicy: { allowPrivateNetwork: true }` instead.
40
57
  */
41
58
  allowInternal?: boolean;
42
59
  }
43
60
  /** Options for connecting to an existing browser instance. */
44
61
  interface ConnectOptions {
62
+ /**
63
+ * SSRF policy controlling which URLs navigation is allowed to reach.
64
+ * By default all private/internal addresses are blocked.
65
+ */
66
+ ssrfPolicy?: SsrfPolicy;
45
67
  /**
46
68
  * 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.
69
+ * @deprecated Use `ssrfPolicy: { allowPrivateNetwork: true }` instead.
49
70
  */
50
71
  allowInternal?: boolean;
51
72
  /**
@@ -176,7 +197,7 @@ interface ClickOptions {
176
197
  /** Mouse button to use */
177
198
  button?: 'left' | 'right' | 'middle';
178
199
  /** Modifier keys to hold during click */
179
- modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[];
200
+ modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[];
180
201
  /** Timeout in milliseconds. Default: `8000` */
181
202
  timeoutMs?: number;
182
203
  }
@@ -390,9 +411,9 @@ interface HttpCredentials {
390
411
  declare class CrawlPage {
391
412
  private readonly cdpUrl;
392
413
  private readonly targetId;
393
- private readonly allowInternal;
414
+ private readonly ssrfPolicy;
394
415
  /** @internal */
395
- constructor(cdpUrl: string, targetId: string, allowInternal?: boolean);
416
+ constructor(cdpUrl: string, targetId: string, ssrfPolicy?: SsrfPolicy);
396
417
  /** The CDP target ID for this page. Use this to identify the page in multi-tab scenarios. */
397
418
  get id(): string;
398
419
  /**
@@ -663,6 +684,8 @@ declare class CrawlPage {
663
684
  */
664
685
  evaluate(fn: string, opts?: {
665
686
  ref?: string;
687
+ timeoutMs?: number;
688
+ signal?: AbortSignal;
666
689
  }): Promise<unknown>;
667
690
  /**
668
691
  * Run JavaScript in ALL frames on the page (including cross-origin iframes).
@@ -987,7 +1010,7 @@ declare class CrawlPage {
987
1010
  */
988
1011
  declare class BrowserClaw {
989
1012
  private readonly cdpUrl;
990
- private readonly allowInternal;
1013
+ private readonly ssrfPolicy;
991
1014
  private chrome;
992
1015
  private constructor();
993
1016
  /**
@@ -1087,6 +1110,7 @@ declare class BrowserClaw {
1087
1110
  stop(): Promise<void>;
1088
1111
  }
1089
1112
 
1113
+ type LookupFn = typeof lookup;
1090
1114
  /**
1091
1115
  * Thrown when a navigation URL is blocked by SSRF policy.
1092
1116
  * Callers can catch this specifically to distinguish navigation blocks
@@ -1095,5 +1119,19 @@ declare class BrowserClaw {
1095
1119
  declare class InvalidBrowserNavigationUrlError extends Error {
1096
1120
  constructor(message: string);
1097
1121
  }
1122
+ /** Options for browser navigation SSRF policy. */
1123
+ type BrowserNavigationPolicyOptions = {
1124
+ ssrfPolicy?: SsrfPolicy;
1125
+ };
1126
+ /** Build a BrowserNavigationPolicyOptions from an SsrfPolicy. */
1127
+ declare function withBrowserNavigationPolicy(ssrfPolicy?: SsrfPolicy): BrowserNavigationPolicyOptions;
1128
+ /**
1129
+ * Assert that a URL is allowed for browser navigation under the given SSRF policy.
1130
+ * Throws `InvalidBrowserNavigationUrlError` if the URL is blocked.
1131
+ */
1132
+ declare function assertBrowserNavigationAllowed(opts: {
1133
+ url: string;
1134
+ lookupFn?: LookupFn;
1135
+ } & BrowserNavigationPolicyOptions): Promise<void>;
1098
1136
 
1099
- 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, InvalidBrowserNavigationUrlError, 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 };
1137
+ 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 LookupFn, 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, assertBrowserNavigationAllowed, withBrowserNavigationPolicy };
package/dist/index.js CHANGED
@@ -1384,6 +1384,32 @@ var InvalidBrowserNavigationUrlError = class extends Error {
1384
1384
  this.name = "InvalidBrowserNavigationUrlError";
1385
1385
  }
1386
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, opts.lookupFn)) {
1408
+ throw new InvalidBrowserNavigationUrlError(
1409
+ `Navigation to internal/loopback address blocked: "${opts.url}". Use ssrfPolicy: { allowPrivateNetwork: true } if this is intentional.`
1410
+ );
1411
+ }
1412
+ }
1387
1413
  function assertSafeOutputPath(path2, allowedRoots) {
1388
1414
  if (!path2 || typeof path2 !== "string") {
1389
1415
  throw new Error("Output path is required.");
@@ -1506,7 +1532,7 @@ function isInternalUrl(url) {
1506
1532
  }
1507
1533
  return false;
1508
1534
  }
1509
- async function isInternalUrlResolved(url) {
1535
+ async function isInternalUrlResolved(url, lookupFn = lookup) {
1510
1536
  if (isInternalUrl(url)) return true;
1511
1537
  let parsed;
1512
1538
  try {
@@ -1515,7 +1541,7 @@ async function isInternalUrlResolved(url) {
1515
1541
  return true;
1516
1542
  }
1517
1543
  try {
1518
- const { address } = await lookup(parsed.hostname);
1544
+ const { address } = await lookupFn(parsed.hostname);
1519
1545
  if (isInternalIP(address)) return true;
1520
1546
  } catch {
1521
1547
  return true;
@@ -1527,9 +1553,8 @@ async function isInternalUrlResolved(url) {
1527
1553
  async function navigateViaPlaywright(opts) {
1528
1554
  const url = String(opts.url ?? "").trim();
1529
1555
  if (!url) throw new Error("url is required");
1530
- if (!opts.allowInternal && await isInternalUrlResolved(url)) {
1531
- throw new InvalidBrowserNavigationUrlError(`Navigation to internal/loopback address blocked: "${url}". Set allowInternal: true if this is intentional.`);
1532
- }
1556
+ const policy = opts.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts.ssrfPolicy;
1557
+ await assertBrowserNavigationAllowed({ url, ssrfPolicy: policy });
1533
1558
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1534
1559
  ensurePageState(page);
1535
1560
  await page.goto(url, { timeout: normalizeTimeoutMs(opts.timeoutMs, 2e4) });
@@ -1552,8 +1577,9 @@ async function listPagesViaPlaywright(opts) {
1552
1577
  }
1553
1578
  async function createPageViaPlaywright(opts) {
1554
1579
  const targetUrl = (opts.url ?? "").trim() || "about:blank";
1555
- if (targetUrl !== "about:blank" && !opts.allowInternal && await isInternalUrlResolved(targetUrl)) {
1556
- throw new InvalidBrowserNavigationUrlError(`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 });
1557
1583
  }
1558
1584
  const { browser } = await connectBrowser(opts.cdpUrl);
1559
1585
  const context = browser.contexts()[0] ?? await browser.newContext();
@@ -1673,6 +1699,7 @@ async function evaluateViaPlaywright(opts) {
1673
1699
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1674
1700
  ensurePageState(page);
1675
1701
  restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
1702
+ const timeout = opts.timeoutMs != null ? opts.timeoutMs : void 0;
1676
1703
  if (opts.ref) {
1677
1704
  const locator = refLocator(page, opts.ref);
1678
1705
  return await locator.evaluate(
@@ -1685,10 +1712,11 @@ async function evaluateViaPlaywright(opts) {
1685
1712
  throw new Error("Invalid evaluate function: " + (err instanceof Error ? err.message : String(err)));
1686
1713
  }
1687
1714
  },
1688
- fnText
1715
+ fnText,
1716
+ { timeout }
1689
1717
  );
1690
1718
  }
1691
- return await page.evaluate(
1719
+ const evalPromise = page.evaluate(
1692
1720
  // eslint-disable-next-line no-eval
1693
1721
  (fnBody) => {
1694
1722
  try {
@@ -1700,6 +1728,13 @@ async function evaluateViaPlaywright(opts) {
1700
1728
  },
1701
1729
  fnText
1702
1730
  );
1731
+ if (!opts.signal) return evalPromise;
1732
+ return Promise.race([
1733
+ evalPromise,
1734
+ new Promise((_, reject) => {
1735
+ opts.signal.addEventListener("abort", () => reject(new Error("Evaluate aborted")), { once: true });
1736
+ })
1737
+ ]);
1703
1738
  }
1704
1739
 
1705
1740
  // src/actions/download.ts
@@ -2061,12 +2096,12 @@ async function storageClearViaPlaywright(opts) {
2061
2096
  var CrawlPage = class {
2062
2097
  cdpUrl;
2063
2098
  targetId;
2064
- allowInternal;
2099
+ ssrfPolicy;
2065
2100
  /** @internal */
2066
- constructor(cdpUrl, targetId, allowInternal = false) {
2101
+ constructor(cdpUrl, targetId, ssrfPolicy) {
2067
2102
  this.cdpUrl = cdpUrl;
2068
2103
  this.targetId = targetId;
2069
- this.allowInternal = allowInternal;
2104
+ this.ssrfPolicy = ssrfPolicy;
2070
2105
  }
2071
2106
  /** The CDP target ID for this page. Use this to identify the page in multi-tab scenarios. */
2072
2107
  get id() {
@@ -2399,7 +2434,7 @@ var CrawlPage = class {
2399
2434
  targetId: this.targetId,
2400
2435
  url,
2401
2436
  timeoutMs: opts?.timeoutMs,
2402
- allowInternal: this.allowInternal
2437
+ ssrfPolicy: this.ssrfPolicy
2403
2438
  });
2404
2439
  }
2405
2440
  /**
@@ -2478,7 +2513,9 @@ var CrawlPage = class {
2478
2513
  cdpUrl: this.cdpUrl,
2479
2514
  targetId: this.targetId,
2480
2515
  fn,
2481
- ref: opts?.ref
2516
+ ref: opts?.ref,
2517
+ timeoutMs: opts?.timeoutMs,
2518
+ signal: opts?.signal
2482
2519
  });
2483
2520
  }
2484
2521
  /**
@@ -2929,12 +2966,12 @@ var CrawlPage = class {
2929
2966
  };
2930
2967
  var BrowserClaw = class _BrowserClaw {
2931
2968
  cdpUrl;
2932
- allowInternal;
2969
+ ssrfPolicy;
2933
2970
  chrome;
2934
- constructor(cdpUrl, chrome, allowInternal = false) {
2971
+ constructor(cdpUrl, chrome, ssrfPolicy) {
2935
2972
  this.cdpUrl = cdpUrl;
2936
2973
  this.chrome = chrome;
2937
- this.allowInternal = allowInternal;
2974
+ this.ssrfPolicy = ssrfPolicy;
2938
2975
  }
2939
2976
  /**
2940
2977
  * Launch a new Chrome instance and connect to it.
@@ -2962,7 +2999,8 @@ var BrowserClaw = class _BrowserClaw {
2962
2999
  static async launch(opts = {}) {
2963
3000
  const chrome = await launchChrome(opts);
2964
3001
  const cdpUrl = `http://127.0.0.1:${chrome.cdpPort}`;
2965
- return new _BrowserClaw(cdpUrl, chrome, opts.allowInternal);
3002
+ const ssrfPolicy = opts.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts.ssrfPolicy;
3003
+ return new _BrowserClaw(cdpUrl, chrome, ssrfPolicy);
2966
3004
  }
2967
3005
  /**
2968
3006
  * Connect to an already-running Chrome instance via its CDP endpoint.
@@ -2983,7 +3021,8 @@ var BrowserClaw = class _BrowserClaw {
2983
3021
  throw new Error(`Cannot connect to Chrome at ${cdpUrl}. Is Chrome running with --remote-debugging-port?`);
2984
3022
  }
2985
3023
  await connectBrowser(cdpUrl, opts?.authToken);
2986
- return new _BrowserClaw(cdpUrl, null, opts?.allowInternal);
3024
+ const ssrfPolicy = opts?.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts?.ssrfPolicy;
3025
+ return new _BrowserClaw(cdpUrl, null, ssrfPolicy);
2987
3026
  }
2988
3027
  /**
2989
3028
  * Open a URL in a new tab and return the page handle.
@@ -2998,8 +3037,8 @@ var BrowserClaw = class _BrowserClaw {
2998
3037
  * ```
2999
3038
  */
3000
3039
  async open(url) {
3001
- const tab = await createPageViaPlaywright({ cdpUrl: this.cdpUrl, url, allowInternal: this.allowInternal });
3002
- return new CrawlPage(this.cdpUrl, tab.targetId, this.allowInternal);
3040
+ const tab = await createPageViaPlaywright({ cdpUrl: this.cdpUrl, url, ssrfPolicy: this.ssrfPolicy });
3041
+ return new CrawlPage(this.cdpUrl, tab.targetId, this.ssrfPolicy);
3003
3042
  }
3004
3043
  /**
3005
3044
  * Get a CrawlPage handle for the currently active tab.
@@ -3012,7 +3051,7 @@ var BrowserClaw = class _BrowserClaw {
3012
3051
  if (!pages.length) throw new Error("No pages available. Use browser.open(url) to create a tab.");
3013
3052
  const tid = await pageTargetId(pages[0]).catch(() => null);
3014
3053
  if (!tid) throw new Error("Failed to get targetId for the current page.");
3015
- return new CrawlPage(this.cdpUrl, tid, this.allowInternal);
3054
+ return new CrawlPage(this.cdpUrl, tid, this.ssrfPolicy);
3016
3055
  }
3017
3056
  /**
3018
3057
  * List all open tabs.
@@ -3047,7 +3086,7 @@ var BrowserClaw = class _BrowserClaw {
3047
3086
  * @returns CrawlPage for the specified tab
3048
3087
  */
3049
3088
  page(targetId) {
3050
- return new CrawlPage(this.cdpUrl, targetId, this.allowInternal);
3089
+ return new CrawlPage(this.cdpUrl, targetId, this.ssrfPolicy);
3051
3090
  }
3052
3091
  /** The CDP endpoint URL for this browser connection. */
3053
3092
  get url() {
@@ -3069,6 +3108,6 @@ var BrowserClaw = class _BrowserClaw {
3069
3108
  }
3070
3109
  };
3071
3110
 
3072
- export { BrowserClaw, CrawlPage, InvalidBrowserNavigationUrlError };
3111
+ export { BrowserClaw, CrawlPage, InvalidBrowserNavigationUrlError, assertBrowserNavigationAllowed, withBrowserNavigationPolicy };
3073
3112
  //# sourceMappingURL=index.js.map
3074
3113
  //# sourceMappingURL=index.js.map