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 CHANGED
@@ -1387,6 +1387,38 @@ async function pressKeyViaPlaywright(opts) {
1387
1387
  ensurePageState(page);
1388
1388
  await page.keyboard.press(key, { delay: Math.max(0, Math.floor(opts.delayMs ?? 0)) });
1389
1389
  }
1390
+ var InvalidBrowserNavigationUrlError = class extends Error {
1391
+ constructor(message) {
1392
+ super(message);
1393
+ this.name = "InvalidBrowserNavigationUrlError";
1394
+ }
1395
+ };
1396
+ function withBrowserNavigationPolicy(ssrfPolicy) {
1397
+ return { ssrfPolicy };
1398
+ }
1399
+ async function assertBrowserNavigationAllowed(opts) {
1400
+ const policy = opts.ssrfPolicy;
1401
+ if (policy?.allowPrivateNetwork) return;
1402
+ const allowedHostnames = [
1403
+ ...policy?.allowedHostnames ?? [],
1404
+ ...policy?.hostnameAllowlist ?? []
1405
+ ];
1406
+ if (allowedHostnames.length) {
1407
+ let parsed;
1408
+ try {
1409
+ parsed = new URL(opts.url);
1410
+ } catch {
1411
+ throw new InvalidBrowserNavigationUrlError(`Invalid URL: "${opts.url}"`);
1412
+ }
1413
+ const hostname = parsed.hostname.toLowerCase();
1414
+ if (allowedHostnames.some((h) => h.toLowerCase() === hostname)) return;
1415
+ }
1416
+ if (await isInternalUrlResolved(opts.url)) {
1417
+ throw new InvalidBrowserNavigationUrlError(
1418
+ `Navigation to internal/loopback address blocked: "${opts.url}". Use ssrfPolicy: { allowPrivateNetwork: true } if this is intentional.`
1419
+ );
1420
+ }
1421
+ }
1390
1422
  function assertSafeOutputPath(path2, allowedRoots) {
1391
1423
  if (!path2 || typeof path2 !== "string") {
1392
1424
  throw new Error("Output path is required.");
@@ -1530,9 +1562,8 @@ async function isInternalUrlResolved(url) {
1530
1562
  async function navigateViaPlaywright(opts) {
1531
1563
  const url = String(opts.url ?? "").trim();
1532
1564
  if (!url) throw new Error("url is required");
1533
- if (!opts.allowInternal && await isInternalUrlResolved(url)) {
1534
- throw new Error(`Navigation to internal/loopback address blocked: "${url}". Set allowInternal: true if this is intentional.`);
1535
- }
1565
+ const policy = opts.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts.ssrfPolicy;
1566
+ await assertBrowserNavigationAllowed({ url, ssrfPolicy: policy });
1536
1567
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1537
1568
  ensurePageState(page);
1538
1569
  await page.goto(url, { timeout: normalizeTimeoutMs(opts.timeoutMs, 2e4) });
@@ -1555,8 +1586,9 @@ async function listPagesViaPlaywright(opts) {
1555
1586
  }
1556
1587
  async function createPageViaPlaywright(opts) {
1557
1588
  const targetUrl = (opts.url ?? "").trim() || "about:blank";
1558
- if (targetUrl !== "about:blank" && !opts.allowInternal && await isInternalUrlResolved(targetUrl)) {
1559
- throw new Error(`Navigation to internal/loopback address blocked: "${targetUrl}". Set allowInternal: true if this is intentional.`);
1589
+ if (targetUrl !== "about:blank") {
1590
+ const policy = opts.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts.ssrfPolicy;
1591
+ await assertBrowserNavigationAllowed({ url: targetUrl, ssrfPolicy: policy });
1560
1592
  }
1561
1593
  const { browser } = await connectBrowser(opts.cdpUrl);
1562
1594
  const context = browser.contexts()[0] ?? await browser.newContext();
@@ -2064,12 +2096,12 @@ async function storageClearViaPlaywright(opts) {
2064
2096
  var CrawlPage = class {
2065
2097
  cdpUrl;
2066
2098
  targetId;
2067
- allowInternal;
2099
+ ssrfPolicy;
2068
2100
  /** @internal */
2069
- constructor(cdpUrl, targetId, allowInternal = false) {
2101
+ constructor(cdpUrl, targetId, ssrfPolicy) {
2070
2102
  this.cdpUrl = cdpUrl;
2071
2103
  this.targetId = targetId;
2072
- this.allowInternal = allowInternal;
2104
+ this.ssrfPolicy = ssrfPolicy;
2073
2105
  }
2074
2106
  /** The CDP target ID for this page. Use this to identify the page in multi-tab scenarios. */
2075
2107
  get id() {
@@ -2402,7 +2434,7 @@ var CrawlPage = class {
2402
2434
  targetId: this.targetId,
2403
2435
  url,
2404
2436
  timeoutMs: opts?.timeoutMs,
2405
- allowInternal: this.allowInternal
2437
+ ssrfPolicy: this.ssrfPolicy
2406
2438
  });
2407
2439
  }
2408
2440
  /**
@@ -2932,12 +2964,12 @@ var CrawlPage = class {
2932
2964
  };
2933
2965
  var BrowserClaw = class _BrowserClaw {
2934
2966
  cdpUrl;
2935
- allowInternal;
2967
+ ssrfPolicy;
2936
2968
  chrome;
2937
- constructor(cdpUrl, chrome, allowInternal = false) {
2969
+ constructor(cdpUrl, chrome, ssrfPolicy) {
2938
2970
  this.cdpUrl = cdpUrl;
2939
2971
  this.chrome = chrome;
2940
- this.allowInternal = allowInternal;
2972
+ this.ssrfPolicy = ssrfPolicy;
2941
2973
  }
2942
2974
  /**
2943
2975
  * Launch a new Chrome instance and connect to it.
@@ -2965,7 +2997,8 @@ var BrowserClaw = class _BrowserClaw {
2965
2997
  static async launch(opts = {}) {
2966
2998
  const chrome = await launchChrome(opts);
2967
2999
  const cdpUrl = `http://127.0.0.1:${chrome.cdpPort}`;
2968
- return new _BrowserClaw(cdpUrl, chrome, opts.allowInternal);
3000
+ const ssrfPolicy = opts.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts.ssrfPolicy;
3001
+ return new _BrowserClaw(cdpUrl, chrome, ssrfPolicy);
2969
3002
  }
2970
3003
  /**
2971
3004
  * Connect to an already-running Chrome instance via its CDP endpoint.
@@ -2986,7 +3019,8 @@ var BrowserClaw = class _BrowserClaw {
2986
3019
  throw new Error(`Cannot connect to Chrome at ${cdpUrl}. Is Chrome running with --remote-debugging-port?`);
2987
3020
  }
2988
3021
  await connectBrowser(cdpUrl, opts?.authToken);
2989
- return new _BrowserClaw(cdpUrl, null, opts?.allowInternal);
3022
+ const ssrfPolicy = opts?.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts?.ssrfPolicy;
3023
+ return new _BrowserClaw(cdpUrl, null, ssrfPolicy);
2990
3024
  }
2991
3025
  /**
2992
3026
  * Open a URL in a new tab and return the page handle.
@@ -3001,8 +3035,8 @@ var BrowserClaw = class _BrowserClaw {
3001
3035
  * ```
3002
3036
  */
3003
3037
  async open(url) {
3004
- const tab = await createPageViaPlaywright({ cdpUrl: this.cdpUrl, url, allowInternal: this.allowInternal });
3005
- return new CrawlPage(this.cdpUrl, tab.targetId, this.allowInternal);
3038
+ const tab = await createPageViaPlaywright({ cdpUrl: this.cdpUrl, url, ssrfPolicy: this.ssrfPolicy });
3039
+ return new CrawlPage(this.cdpUrl, tab.targetId, this.ssrfPolicy);
3006
3040
  }
3007
3041
  /**
3008
3042
  * Get a CrawlPage handle for the currently active tab.
@@ -3015,7 +3049,7 @@ var BrowserClaw = class _BrowserClaw {
3015
3049
  if (!pages.length) throw new Error("No pages available. Use browser.open(url) to create a tab.");
3016
3050
  const tid = await pageTargetId(pages[0]).catch(() => null);
3017
3051
  if (!tid) throw new Error("Failed to get targetId for the current page.");
3018
- return new CrawlPage(this.cdpUrl, tid, this.allowInternal);
3052
+ return new CrawlPage(this.cdpUrl, tid, this.ssrfPolicy);
3019
3053
  }
3020
3054
  /**
3021
3055
  * List all open tabs.
@@ -3050,7 +3084,7 @@ var BrowserClaw = class _BrowserClaw {
3050
3084
  * @returns CrawlPage for the specified tab
3051
3085
  */
3052
3086
  page(targetId) {
3053
- return new CrawlPage(this.cdpUrl, targetId, this.allowInternal);
3087
+ return new CrawlPage(this.cdpUrl, targetId, this.ssrfPolicy);
3054
3088
  }
3055
3089
  /** The CDP endpoint URL for this browser connection. */
3056
3090
  get url() {
@@ -3074,5 +3108,7 @@ var BrowserClaw = class _BrowserClaw {
3074
3108
 
3075
3109
  exports.BrowserClaw = BrowserClaw;
3076
3110
  exports.CrawlPage = CrawlPage;
3111
+ exports.InvalidBrowserNavigationUrlError = InvalidBrowserNavigationUrlError;
3112
+ exports.withBrowserNavigationPolicy = withBrowserNavigationPolicy;
3077
3113
  //# sourceMappingURL=index.cjs.map
3078
3114
  //# sourceMappingURL=index.cjs.map