browserclaw 0.3.8 → 0.3.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 +28 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +28 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1232,7 +1232,8 @@ async function assertBrowserNavigationAllowed(opts) {
|
|
|
1232
1232
|
const hostname = parsed.hostname.toLowerCase();
|
|
1233
1233
|
if (allowedHostnames.some((h) => h.toLowerCase() === hostname)) return;
|
|
1234
1234
|
}
|
|
1235
|
-
|
|
1235
|
+
const ipOpts = { allowRfc2544BenchmarkRange: policy?.allowRfc2544BenchmarkRange };
|
|
1236
|
+
if (await isInternalUrlResolved(rawUrl, opts.lookupFn, ipOpts)) {
|
|
1236
1237
|
throw new InvalidBrowserNavigationUrlError(
|
|
1237
1238
|
`Navigation to internal/loopback address blocked: "${rawUrl}". ssrfPolicy.dangerouslyAllowPrivateNetwork is false (strict mode).`
|
|
1238
1239
|
);
|
|
@@ -1347,7 +1348,7 @@ function isUnsupportedIPv4Literal(ip) {
|
|
|
1347
1348
|
if (!parts.every(isStrictDecimalOctet)) return true;
|
|
1348
1349
|
return false;
|
|
1349
1350
|
}
|
|
1350
|
-
function isInternalIP(ip) {
|
|
1351
|
+
function isInternalIP(ip, opts) {
|
|
1351
1352
|
if (!ip.includes(":") && isUnsupportedIPv4Literal(ip)) return true;
|
|
1352
1353
|
if (/^127\./.test(ip)) return true;
|
|
1353
1354
|
if (/^10\./.test(ip)) return true;
|
|
@@ -1356,6 +1357,7 @@ function isInternalIP(ip) {
|
|
|
1356
1357
|
if (/^169\.254\./.test(ip)) return true;
|
|
1357
1358
|
if (/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(ip)) return true;
|
|
1358
1359
|
if (ip === "0.0.0.0") return true;
|
|
1360
|
+
if (!opts?.allowRfc2544BenchmarkRange && /^198\.1[89]\./.test(ip)) return true;
|
|
1359
1361
|
const lower = ip.toLowerCase();
|
|
1360
1362
|
if (lower === "::1") return true;
|
|
1361
1363
|
if (lower.startsWith("fe80:")) return true;
|
|
@@ -1364,11 +1366,11 @@ function isInternalIP(ip) {
|
|
|
1364
1366
|
const embedded = extractEmbeddedIPv4(lower);
|
|
1365
1367
|
if (embedded !== null) {
|
|
1366
1368
|
if (embedded === "") return true;
|
|
1367
|
-
return isInternalIP(embedded);
|
|
1369
|
+
return isInternalIP(embedded, opts);
|
|
1368
1370
|
}
|
|
1369
1371
|
return false;
|
|
1370
1372
|
}
|
|
1371
|
-
function isInternalUrl(url) {
|
|
1373
|
+
function isInternalUrl(url, opts) {
|
|
1372
1374
|
let parsed;
|
|
1373
1375
|
try {
|
|
1374
1376
|
parsed = new URL(url);
|
|
@@ -1377,7 +1379,7 @@ function isInternalUrl(url) {
|
|
|
1377
1379
|
}
|
|
1378
1380
|
const hostname = parsed.hostname.toLowerCase();
|
|
1379
1381
|
if (hostname === "localhost") return true;
|
|
1380
|
-
if (isInternalIP(hostname)) return true;
|
|
1382
|
+
if (isInternalIP(hostname, opts)) return true;
|
|
1381
1383
|
if (hostname.endsWith(".local") || hostname.endsWith(".internal") || hostname.endsWith(".localhost")) {
|
|
1382
1384
|
return true;
|
|
1383
1385
|
}
|
|
@@ -1399,8 +1401,8 @@ async function assertSafeUploadPaths(paths) {
|
|
|
1399
1401
|
}
|
|
1400
1402
|
}
|
|
1401
1403
|
}
|
|
1402
|
-
async function isInternalUrlResolved(url, lookupFn = promises.lookup) {
|
|
1403
|
-
if (isInternalUrl(url)) return true;
|
|
1404
|
+
async function isInternalUrlResolved(url, lookupFn = promises.lookup, opts) {
|
|
1405
|
+
if (isInternalUrl(url, opts)) return true;
|
|
1404
1406
|
let parsed;
|
|
1405
1407
|
try {
|
|
1406
1408
|
parsed = new URL(url);
|
|
@@ -1409,12 +1411,25 @@ async function isInternalUrlResolved(url, lookupFn = promises.lookup) {
|
|
|
1409
1411
|
}
|
|
1410
1412
|
try {
|
|
1411
1413
|
const { address } = await lookupFn(parsed.hostname);
|
|
1412
|
-
if (isInternalIP(address)) return true;
|
|
1414
|
+
if (isInternalIP(address, opts)) return true;
|
|
1413
1415
|
} catch {
|
|
1414
1416
|
return true;
|
|
1415
1417
|
}
|
|
1416
1418
|
return false;
|
|
1417
1419
|
}
|
|
1420
|
+
async function assertBrowserNavigationResultAllowed(opts) {
|
|
1421
|
+
const rawUrl = String(opts.url ?? "").trim();
|
|
1422
|
+
if (!rawUrl) return;
|
|
1423
|
+
let parsed;
|
|
1424
|
+
try {
|
|
1425
|
+
parsed = new URL(rawUrl);
|
|
1426
|
+
} catch {
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
if (NETWORK_NAVIGATION_PROTOCOLS.has(parsed.protocol) || SAFE_NON_NETWORK_URLS.has(parsed.href)) {
|
|
1430
|
+
await assertBrowserNavigationAllowed(opts);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1418
1433
|
|
|
1419
1434
|
// src/actions/interaction.ts
|
|
1420
1435
|
async function clickViaPlaywright(opts) {
|
|
@@ -1620,7 +1635,9 @@ async function navigateViaPlaywright(opts) {
|
|
|
1620
1635
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
1621
1636
|
ensurePageState(page);
|
|
1622
1637
|
await page.goto(url, { timeout: normalizeTimeoutMs(opts.timeoutMs, 2e4) });
|
|
1623
|
-
|
|
1638
|
+
const finalUrl = page.url();
|
|
1639
|
+
await assertBrowserNavigationResultAllowed({ url: finalUrl, ssrfPolicy: policy });
|
|
1640
|
+
return { url: finalUrl };
|
|
1624
1641
|
}
|
|
1625
1642
|
async function listPagesViaPlaywright(opts) {
|
|
1626
1643
|
const { browser } = await connectBrowser(opts.cdpUrl);
|
|
@@ -1639,8 +1656,8 @@ async function listPagesViaPlaywright(opts) {
|
|
|
1639
1656
|
}
|
|
1640
1657
|
async function createPageViaPlaywright(opts) {
|
|
1641
1658
|
const targetUrl = (opts.url ?? "").trim() || "about:blank";
|
|
1659
|
+
const policy = opts.allowInternal ? { ...opts.ssrfPolicy, dangerouslyAllowPrivateNetwork: true } : opts.ssrfPolicy;
|
|
1642
1660
|
if (targetUrl !== "about:blank") {
|
|
1643
|
-
const policy = opts.allowInternal ? { ...opts.ssrfPolicy, dangerouslyAllowPrivateNetwork: true } : opts.ssrfPolicy;
|
|
1644
1661
|
await assertBrowserNavigationAllowed({ url: targetUrl, ssrfPolicy: policy });
|
|
1645
1662
|
}
|
|
1646
1663
|
const { browser } = await connectBrowser(opts.cdpUrl);
|
|
@@ -1649,6 +1666,7 @@ async function createPageViaPlaywright(opts) {
|
|
|
1649
1666
|
ensurePageState(page);
|
|
1650
1667
|
if (targetUrl !== "about:blank") {
|
|
1651
1668
|
await page.goto(targetUrl, { timeout: normalizeTimeoutMs(void 0, 2e4) });
|
|
1669
|
+
await assertBrowserNavigationResultAllowed({ url: page.url(), ssrfPolicy: policy });
|
|
1652
1670
|
}
|
|
1653
1671
|
const tid = await pageTargetId(page).catch(() => null);
|
|
1654
1672
|
if (!tid) throw new Error("Failed to get targetId for new page");
|