browserclaw 0.2.8 → 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
@@ -1393,6 +1393,32 @@ var InvalidBrowserNavigationUrlError = class extends Error {
1393
1393
  this.name = "InvalidBrowserNavigationUrlError";
1394
1394
  }
1395
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
+ }
1396
1422
  function assertSafeOutputPath(path2, allowedRoots) {
1397
1423
  if (!path2 || typeof path2 !== "string") {
1398
1424
  throw new Error("Output path is required.");
@@ -1536,9 +1562,8 @@ async function isInternalUrlResolved(url) {
1536
1562
  async function navigateViaPlaywright(opts) {
1537
1563
  const url = String(opts.url ?? "").trim();
1538
1564
  if (!url) throw new Error("url is required");
1539
- if (!opts.allowInternal && await isInternalUrlResolved(url)) {
1540
- throw new InvalidBrowserNavigationUrlError(`Navigation to internal/loopback address blocked: "${url}". Set allowInternal: true if this is intentional.`);
1541
- }
1565
+ const policy = opts.allowInternal ? { ...opts.ssrfPolicy, allowPrivateNetwork: true } : opts.ssrfPolicy;
1566
+ await assertBrowserNavigationAllowed({ url, ssrfPolicy: policy });
1542
1567
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1543
1568
  ensurePageState(page);
1544
1569
  await page.goto(url, { timeout: normalizeTimeoutMs(opts.timeoutMs, 2e4) });
@@ -1561,8 +1586,9 @@ async function listPagesViaPlaywright(opts) {
1561
1586
  }
1562
1587
  async function createPageViaPlaywright(opts) {
1563
1588
  const targetUrl = (opts.url ?? "").trim() || "about:blank";
1564
- if (targetUrl !== "about:blank" && !opts.allowInternal && await isInternalUrlResolved(targetUrl)) {
1565
- throw new InvalidBrowserNavigationUrlError(`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 });
1566
1592
  }
1567
1593
  const { browser } = await connectBrowser(opts.cdpUrl);
1568
1594
  const context = browser.contexts()[0] ?? await browser.newContext();
@@ -2070,12 +2096,12 @@ async function storageClearViaPlaywright(opts) {
2070
2096
  var CrawlPage = class {
2071
2097
  cdpUrl;
2072
2098
  targetId;
2073
- allowInternal;
2099
+ ssrfPolicy;
2074
2100
  /** @internal */
2075
- constructor(cdpUrl, targetId, allowInternal = false) {
2101
+ constructor(cdpUrl, targetId, ssrfPolicy) {
2076
2102
  this.cdpUrl = cdpUrl;
2077
2103
  this.targetId = targetId;
2078
- this.allowInternal = allowInternal;
2104
+ this.ssrfPolicy = ssrfPolicy;
2079
2105
  }
2080
2106
  /** The CDP target ID for this page. Use this to identify the page in multi-tab scenarios. */
2081
2107
  get id() {
@@ -2408,7 +2434,7 @@ var CrawlPage = class {
2408
2434
  targetId: this.targetId,
2409
2435
  url,
2410
2436
  timeoutMs: opts?.timeoutMs,
2411
- allowInternal: this.allowInternal
2437
+ ssrfPolicy: this.ssrfPolicy
2412
2438
  });
2413
2439
  }
2414
2440
  /**
@@ -2938,12 +2964,12 @@ var CrawlPage = class {
2938
2964
  };
2939
2965
  var BrowserClaw = class _BrowserClaw {
2940
2966
  cdpUrl;
2941
- allowInternal;
2967
+ ssrfPolicy;
2942
2968
  chrome;
2943
- constructor(cdpUrl, chrome, allowInternal = false) {
2969
+ constructor(cdpUrl, chrome, ssrfPolicy) {
2944
2970
  this.cdpUrl = cdpUrl;
2945
2971
  this.chrome = chrome;
2946
- this.allowInternal = allowInternal;
2972
+ this.ssrfPolicy = ssrfPolicy;
2947
2973
  }
2948
2974
  /**
2949
2975
  * Launch a new Chrome instance and connect to it.
@@ -2971,7 +2997,8 @@ var BrowserClaw = class _BrowserClaw {
2971
2997
  static async launch(opts = {}) {
2972
2998
  const chrome = await launchChrome(opts);
2973
2999
  const cdpUrl = `http://127.0.0.1:${chrome.cdpPort}`;
2974
- 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);
2975
3002
  }
2976
3003
  /**
2977
3004
  * Connect to an already-running Chrome instance via its CDP endpoint.
@@ -2992,7 +3019,8 @@ var BrowserClaw = class _BrowserClaw {
2992
3019
  throw new Error(`Cannot connect to Chrome at ${cdpUrl}. Is Chrome running with --remote-debugging-port?`);
2993
3020
  }
2994
3021
  await connectBrowser(cdpUrl, opts?.authToken);
2995
- 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);
2996
3024
  }
2997
3025
  /**
2998
3026
  * Open a URL in a new tab and return the page handle.
@@ -3007,8 +3035,8 @@ var BrowserClaw = class _BrowserClaw {
3007
3035
  * ```
3008
3036
  */
3009
3037
  async open(url) {
3010
- const tab = await createPageViaPlaywright({ cdpUrl: this.cdpUrl, url, allowInternal: this.allowInternal });
3011
- 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);
3012
3040
  }
3013
3041
  /**
3014
3042
  * Get a CrawlPage handle for the currently active tab.
@@ -3021,7 +3049,7 @@ var BrowserClaw = class _BrowserClaw {
3021
3049
  if (!pages.length) throw new Error("No pages available. Use browser.open(url) to create a tab.");
3022
3050
  const tid = await pageTargetId(pages[0]).catch(() => null);
3023
3051
  if (!tid) throw new Error("Failed to get targetId for the current page.");
3024
- return new CrawlPage(this.cdpUrl, tid, this.allowInternal);
3052
+ return new CrawlPage(this.cdpUrl, tid, this.ssrfPolicy);
3025
3053
  }
3026
3054
  /**
3027
3055
  * List all open tabs.
@@ -3056,7 +3084,7 @@ var BrowserClaw = class _BrowserClaw {
3056
3084
  * @returns CrawlPage for the specified tab
3057
3085
  */
3058
3086
  page(targetId) {
3059
- return new CrawlPage(this.cdpUrl, targetId, this.allowInternal);
3087
+ return new CrawlPage(this.cdpUrl, targetId, this.ssrfPolicy);
3060
3088
  }
3061
3089
  /** The CDP endpoint URL for this browser connection. */
3062
3090
  get url() {
@@ -3081,5 +3109,6 @@ var BrowserClaw = class _BrowserClaw {
3081
3109
  exports.BrowserClaw = BrowserClaw;
3082
3110
  exports.CrawlPage = CrawlPage;
3083
3111
  exports.InvalidBrowserNavigationUrlError = InvalidBrowserNavigationUrlError;
3112
+ exports.withBrowserNavigationPolicy = withBrowserNavigationPolicy;
3084
3113
  //# sourceMappingURL=index.cjs.map
3085
3114
  //# sourceMappingURL=index.cjs.map