@anvia/browser 1.1.4 → 1.1.6

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/README.md CHANGED
@@ -217,7 +217,10 @@ The legacy `not_ready` and `human_controlled` codes remain in the public union.
217
217
  The tools use ARIA state and strict Playwright locators. They do not expose JavaScript evaluation, raw
218
218
  CDP, coordinate input, shell access, hidden action retries, or automatic reconnection. The navigation
219
219
  policy is installed across the default browser context, so top-level navigation from links, forms,
220
- redirects, popups, and direct navigation is checked consistently. It does not block third-party
220
+ redirects, popups, and direct navigation is checked consistently. Private and reserved IP literals
221
+ (loopback, link-local, RFC 1918, and the other IANA special-purpose ranges) are rejected in every
222
+ policy mode, including `origins` entries that list them. The guard matches the literal host, so a
223
+ public domain that resolves to a private address is not covered. The policy does not block third-party
221
224
  subresources; Docker bridge networking remains outside that policy.
222
225
 
223
226
  The image runs Chromium as a non-root user with Chromium sandboxing, the Playwright seccomp profile,
@@ -1,3 +1,7 @@
1
+ import {
2
+ enforceNavigationPolicy
3
+ } from "./chunk-XE5WKN5F.js";
4
+
1
5
  // src/automation-worker.ts
2
6
  import { randomUUID } from "crypto";
3
7
  import { chromium } from "playwright-core";
@@ -222,26 +226,6 @@ async function setNavigationPolicy(policy) {
222
226
  assertBrowser().contexts().map((context) => context.route("**/*", (route) => enforceNavigationPolicy(route, policy)))
223
227
  );
224
228
  }
225
- async function enforceNavigationPolicy(route, policy) {
226
- const request = route.request();
227
- const frame = request.frame();
228
- if (request.isNavigationRequest() && frame === frame.page().mainFrame() && !isNavigationAllowed(request.url(), policy)) {
229
- await route.abort("blockedbyclient");
230
- return;
231
- }
232
- await route.continue();
233
- }
234
- function isNavigationAllowed(value, policy) {
235
- let url;
236
- try {
237
- url = new URL(value);
238
- } catch {
239
- return false;
240
- }
241
- if (url.protocol !== "http:" && url.protocol !== "https:") return false;
242
- if (url.username.length > 0 || url.password.length > 0) return false;
243
- return policy.mode === "allow-all-http" || policy.origins.includes(url.origin);
244
- }
245
229
  function locatorFor(page, target) {
246
230
  switch (target.by) {
247
231
  case "role": {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/automation-worker.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { Browser, Locator, Page, Route } from \"playwright-core\";\nimport { chromium } from \"playwright-core\";\nimport type {\n AutomationCancel,\n AutomationCommand,\n AutomationRequest,\n AutomationResponse,\n BrowserTarget,\n SerializedError,\n} from \"./automation-protocol\";\nimport type { BrowserNavigationPolicy, BrowserTab } from \"./types\";\n\ntype ActiveOperation = {\n cancelled: boolean;\n page?: Page;\n completed: Promise<void>;\n complete: () => void;\n};\n\nlet browser: Browser | undefined;\nlet selected: Page | undefined;\nconst tabIds = new WeakMap<Page, string>();\nconst pagesById = new Map<string, Page>();\nconst operations = new Map<number, ActiveOperation>();\nlet navigationPolicyKey: string | undefined;\n\nprocess.once(\"disconnect\", () => {\n void closeAfterParentExit();\n});\n\nprocess.on(\"message\", (message: AutomationRequest | AutomationCancel) => {\n if (message.kind === \"cancel\") {\n void cancelOperation(message);\n return;\n }\n void runRequest(message);\n});\n\nasync function runRequest(request: AutomationRequest): Promise<void> {\n let complete: (() => void) | undefined;\n const operation: ActiveOperation = {\n cancelled: false,\n completed: new Promise<void>((resolve) => {\n complete = resolve;\n }),\n complete: () => complete?.(),\n };\n operations.set(request.id, operation);\n try {\n const value = await execute(request, operation);\n send({ kind: \"response\", id: request.id, ok: true, value });\n } catch (error) {\n send({ kind: \"response\", id: request.id, ok: false, error: serializeError(error) });\n } finally {\n operation.complete();\n operations.delete(request.id);\n }\n}\n\nasync function cancelOperation(request: AutomationCancel): Promise<void> {\n const operation = operations.get(request.id);\n if (operation !== undefined) {\n operation.cancelled = true;\n await operation.page?.close().catch(() => undefined);\n await operation.completed;\n }\n send({ kind: \"cancelled\", id: request.id });\n}\n\nasync function execute(request: AutomationRequest, operation: ActiveOperation): Promise<unknown> {\n const command: AutomationCommand = request;\n switch (command.method) {\n case \"connect\": {\n if (browser !== undefined) throw new Error(\"Automation worker is already connected.\");\n const connected = await chromium.connectOverCDP(command.params.endpointUrl, {\n timeout: command.params.timeoutMs,\n });\n try {\n if (operation.cancelled) throw new Error(\"Connection attempt was cancelled.\");\n const context = connected.contexts()[0];\n if (context === undefined) {\n throw new Error(\"CDP connection did not expose a browser context.\");\n }\n const session = await connected.newBrowserCDPSession();\n try {\n await session.send(\"Browser.getVersion\");\n } finally {\n await session.detach().catch(() => undefined);\n }\n if (operation.cancelled) throw new Error(\"Connection attempt was cancelled.\");\n } catch (error) {\n await connected.close().catch(() => undefined);\n throw error;\n }\n browser = connected;\n selected = pages()[0];\n for (const page of pages()) idFor(page);\n connected.once(\"disconnected\", () => {\n browser = undefined;\n send({ kind: \"event\", event: \"disconnected\" });\n });\n return undefined;\n }\n case \"disconnect\": {\n const connected = browser;\n browser = undefined;\n selected = undefined;\n if (connected !== undefined) await connected.close();\n return undefined;\n }\n case \"listTabs\":\n return tabSummaries();\n case \"setNavigationPolicy\":\n await setNavigationPolicy(command.params.policy);\n return undefined;\n case \"openTab\": {\n const context = assertBrowser().contexts()[0];\n if (context === undefined) throw new Error(\"Browser has no CDP context.\");\n const page = await context.newPage();\n operation.page = page;\n if (operation.cancelled) {\n await page.close().catch(() => undefined);\n throw new Error(\"Open-tab operation was cancelled.\");\n }\n selected = page;\n return tabResult(page);\n }\n case \"selectTab\": {\n const page = pageFor(command.params.tabId);\n selected = page;\n return tabResult(page);\n }\n case \"closeTab\": {\n const page = pageFor(command.params.tabId);\n operation.page = page;\n await page.close();\n if (page === selected) selected = pages()[0];\n return { closedTabId: command.params.tabId, tabs: await tabSummaries() };\n }\n case \"navigate\": {\n const page = targetPage(command.params.tabId, operation);\n await page.goto(command.params.url, {\n timeout: command.params.timeoutMs,\n waitUntil: command.params.waitUntil,\n });\n return tabResult(page);\n }\n case \"snapshot\": {\n const page = targetPage(command.params.tabId, operation);\n const snapshot = await page.locator(\"body\").ariaSnapshot({\n timeout: command.params.timeoutMs,\n });\n const truncated = snapshot.length > command.params.maxChars;\n return {\n ...(await tabResult(page)),\n snapshot: truncated ? snapshot.slice(0, command.params.maxChars) : snapshot,\n truncated,\n };\n }\n case \"click\": {\n const page = targetPage(command.params.tabId, operation);\n await locatorFor(page, command.params.target).click({ timeout: command.params.timeoutMs });\n return tabResult(page);\n }\n case \"type\": {\n const page = targetPage(command.params.tabId, operation);\n await locatorFor(page, command.params.target).fill(command.params.text, {\n timeout: command.params.timeoutMs,\n });\n return tabResult(page);\n }\n case \"pressKey\": {\n const page = targetPage(command.params.tabId, operation);\n await page.keyboard.press(command.params.key);\n return tabResult(page);\n }\n case \"screenshot\": {\n const page = targetPage(command.params.tabId, operation);\n const png = await page.screenshot({\n type: \"png\",\n fullPage: false,\n timeout: command.params.timeoutMs,\n });\n return { metadata: await tabResult(page), pngBase64: png.toString(\"base64\") };\n }\n }\n}\n\nfunction assertBrowser(): Browser {\n if (browser === undefined || !browser.isConnected())\n throw new Error(\"Browser connection is closed.\");\n return browser;\n}\n\nfunction pages(): Page[] {\n return browser?.contexts().flatMap((context) => context.pages()) ?? [];\n}\n\nfunction idFor(page: Page): string {\n const existing = tabIds.get(page);\n if (existing !== undefined) return existing;\n const id = randomUUID();\n tabIds.set(page, id);\n pagesById.set(id, page);\n page.once(\"close\", () => pagesById.delete(id));\n return id;\n}\n\nfunction pageFor(id: string): Page {\n assertBrowser();\n const page = pagesById.get(id);\n if (page === undefined || page.isClosed() || !pages().includes(page)) {\n throw new Error(`Browser tab does not exist: ${id}`);\n }\n return page;\n}\n\nfunction targetPage(id: string, operation: ActiveOperation): Page {\n const page = pageFor(id);\n operation.page = page;\n return page;\n}\n\nasync function tabSummaries(): Promise<readonly BrowserTab[]> {\n assertBrowser();\n if (selected?.isClosed()) selected = pages()[0];\n return Promise.all(\n pages().map(async (page) => ({\n id: idFor(page),\n title: await page.title(),\n url: page.url(),\n selected: page === selected,\n })),\n );\n}\n\nasync function tabResult(page: Page) {\n return { tabId: idFor(page), title: await page.title(), url: page.url() };\n}\n\nasync function setNavigationPolicy(policy: BrowserNavigationPolicy): Promise<void> {\n const key = JSON.stringify(policy);\n if (navigationPolicyKey !== undefined) {\n if (navigationPolicyKey !== key) {\n throw new TypeError(\"Browser connection already has a different navigation policy.\");\n }\n return;\n }\n navigationPolicyKey = key;\n await Promise.all(\n assertBrowser()\n .contexts()\n .map((context) => context.route(\"**/*\", (route) => enforceNavigationPolicy(route, policy))),\n );\n}\n\nasync function enforceNavigationPolicy(\n route: Route,\n policy: BrowserNavigationPolicy,\n): Promise<void> {\n const request = route.request();\n const frame = request.frame();\n if (\n request.isNavigationRequest() &&\n frame === frame.page().mainFrame() &&\n !isNavigationAllowed(request.url(), policy)\n ) {\n await route.abort(\"blockedbyclient\");\n return;\n }\n await route.continue();\n}\n\nfunction isNavigationAllowed(value: string, policy: BrowserNavigationPolicy): boolean {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n if (url.username.length > 0 || url.password.length > 0) return false;\n return policy.mode === \"allow-all-http\" || policy.origins.includes(url.origin);\n}\n\nfunction locatorFor(page: Page, target: BrowserTarget): Locator {\n switch (target.by) {\n case \"role\": {\n const options: { name?: string; exact?: boolean } = {};\n if (target.name !== undefined) options.name = target.name;\n if (target.exact !== undefined) options.exact = target.exact;\n return page.getByRole(target.role as never, options);\n }\n case \"text\": {\n const options: { exact?: boolean } = {};\n if (target.exact !== undefined) options.exact = target.exact;\n return page.getByText(target.text, options);\n }\n case \"label\": {\n const options: { exact?: boolean } = {};\n if (target.exact !== undefined) options.exact = target.exact;\n return page.getByLabel(target.label, options);\n }\n case \"placeholder\": {\n const options: { exact?: boolean } = {};\n if (target.exact !== undefined) options.exact = target.exact;\n return page.getByPlaceholder(target.placeholder, options);\n }\n case \"test-id\":\n return page.getByTestId(target.testId);\n case \"css\":\n return page.locator(target.selector);\n }\n}\n\nfunction serializeError(error: unknown): SerializedError {\n if (error instanceof Error) {\n const value: { name: string; message: string; stack?: string; code?: string } = {\n name: error.name,\n message: error.message,\n };\n if (error.stack !== undefined) value.stack = error.stack;\n if (\"code\" in error && typeof error.code === \"string\") value.code = error.code;\n return value;\n }\n return { name: \"Error\", message: String(error) };\n}\n\nfunction send(message: AutomationResponse): void {\n if (!process.connected || process.send === undefined) return;\n process.send(message, () => undefined);\n}\n\nasync function closeAfterParentExit(): Promise<void> {\n const forceExit = setTimeout(() => process.exit(1), 2_000);\n const connected = browser;\n browser = undefined;\n await connected?.close().catch(() => undefined);\n clearTimeout(forceExit);\n process.exit(0);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/automation-worker.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { Browser, Locator, Page } from \"playwright-core\";\nimport { chromium } from \"playwright-core\";\nimport type {\n AutomationCancel,\n AutomationCommand,\n AutomationRequest,\n AutomationResponse,\n BrowserTarget,\n SerializedError,\n} from \"./automation-protocol\";\nimport { enforceNavigationPolicy } from \"./internal/navigation-policy\";\nimport type { BrowserNavigationPolicy, BrowserTab } from \"./types\";\n\ntype ActiveOperation = {\n cancelled: boolean;\n page?: Page;\n completed: Promise<void>;\n complete: () => void;\n};\n\nlet browser: Browser | undefined;\nlet selected: Page | undefined;\nconst tabIds = new WeakMap<Page, string>();\nconst pagesById = new Map<string, Page>();\nconst operations = new Map<number, ActiveOperation>();\nlet navigationPolicyKey: string | undefined;\n\nprocess.once(\"disconnect\", () => {\n void closeAfterParentExit();\n});\n\nprocess.on(\"message\", (message: AutomationRequest | AutomationCancel) => {\n if (message.kind === \"cancel\") {\n void cancelOperation(message);\n return;\n }\n void runRequest(message);\n});\n\nasync function runRequest(request: AutomationRequest): Promise<void> {\n let complete: (() => void) | undefined;\n const operation: ActiveOperation = {\n cancelled: false,\n completed: new Promise<void>((resolve) => {\n complete = resolve;\n }),\n complete: () => complete?.(),\n };\n operations.set(request.id, operation);\n try {\n const value = await execute(request, operation);\n send({ kind: \"response\", id: request.id, ok: true, value });\n } catch (error) {\n send({ kind: \"response\", id: request.id, ok: false, error: serializeError(error) });\n } finally {\n operation.complete();\n operations.delete(request.id);\n }\n}\n\nasync function cancelOperation(request: AutomationCancel): Promise<void> {\n const operation = operations.get(request.id);\n if (operation !== undefined) {\n operation.cancelled = true;\n await operation.page?.close().catch(() => undefined);\n await operation.completed;\n }\n send({ kind: \"cancelled\", id: request.id });\n}\n\nasync function execute(request: AutomationRequest, operation: ActiveOperation): Promise<unknown> {\n const command: AutomationCommand = request;\n switch (command.method) {\n case \"connect\": {\n if (browser !== undefined) throw new Error(\"Automation worker is already connected.\");\n const connected = await chromium.connectOverCDP(command.params.endpointUrl, {\n timeout: command.params.timeoutMs,\n });\n try {\n if (operation.cancelled) throw new Error(\"Connection attempt was cancelled.\");\n const context = connected.contexts()[0];\n if (context === undefined) {\n throw new Error(\"CDP connection did not expose a browser context.\");\n }\n const session = await connected.newBrowserCDPSession();\n try {\n await session.send(\"Browser.getVersion\");\n } finally {\n await session.detach().catch(() => undefined);\n }\n if (operation.cancelled) throw new Error(\"Connection attempt was cancelled.\");\n } catch (error) {\n await connected.close().catch(() => undefined);\n throw error;\n }\n browser = connected;\n selected = pages()[0];\n for (const page of pages()) idFor(page);\n connected.once(\"disconnected\", () => {\n browser = undefined;\n send({ kind: \"event\", event: \"disconnected\" });\n });\n return undefined;\n }\n case \"disconnect\": {\n const connected = browser;\n browser = undefined;\n selected = undefined;\n if (connected !== undefined) await connected.close();\n return undefined;\n }\n case \"listTabs\":\n return tabSummaries();\n case \"setNavigationPolicy\":\n await setNavigationPolicy(command.params.policy);\n return undefined;\n case \"openTab\": {\n const context = assertBrowser().contexts()[0];\n if (context === undefined) throw new Error(\"Browser has no CDP context.\");\n const page = await context.newPage();\n operation.page = page;\n if (operation.cancelled) {\n await page.close().catch(() => undefined);\n throw new Error(\"Open-tab operation was cancelled.\");\n }\n selected = page;\n return tabResult(page);\n }\n case \"selectTab\": {\n const page = pageFor(command.params.tabId);\n selected = page;\n return tabResult(page);\n }\n case \"closeTab\": {\n const page = pageFor(command.params.tabId);\n operation.page = page;\n await page.close();\n if (page === selected) selected = pages()[0];\n return { closedTabId: command.params.tabId, tabs: await tabSummaries() };\n }\n case \"navigate\": {\n const page = targetPage(command.params.tabId, operation);\n await page.goto(command.params.url, {\n timeout: command.params.timeoutMs,\n waitUntil: command.params.waitUntil,\n });\n return tabResult(page);\n }\n case \"snapshot\": {\n const page = targetPage(command.params.tabId, operation);\n const snapshot = await page.locator(\"body\").ariaSnapshot({\n timeout: command.params.timeoutMs,\n });\n const truncated = snapshot.length > command.params.maxChars;\n return {\n ...(await tabResult(page)),\n snapshot: truncated ? snapshot.slice(0, command.params.maxChars) : snapshot,\n truncated,\n };\n }\n case \"click\": {\n const page = targetPage(command.params.tabId, operation);\n await locatorFor(page, command.params.target).click({ timeout: command.params.timeoutMs });\n return tabResult(page);\n }\n case \"type\": {\n const page = targetPage(command.params.tabId, operation);\n await locatorFor(page, command.params.target).fill(command.params.text, {\n timeout: command.params.timeoutMs,\n });\n return tabResult(page);\n }\n case \"pressKey\": {\n const page = targetPage(command.params.tabId, operation);\n await page.keyboard.press(command.params.key);\n return tabResult(page);\n }\n case \"screenshot\": {\n const page = targetPage(command.params.tabId, operation);\n const png = await page.screenshot({\n type: \"png\",\n fullPage: false,\n timeout: command.params.timeoutMs,\n });\n return { metadata: await tabResult(page), pngBase64: png.toString(\"base64\") };\n }\n }\n}\n\nfunction assertBrowser(): Browser {\n if (browser === undefined || !browser.isConnected())\n throw new Error(\"Browser connection is closed.\");\n return browser;\n}\n\nfunction pages(): Page[] {\n return browser?.contexts().flatMap((context) => context.pages()) ?? [];\n}\n\nfunction idFor(page: Page): string {\n const existing = tabIds.get(page);\n if (existing !== undefined) return existing;\n const id = randomUUID();\n tabIds.set(page, id);\n pagesById.set(id, page);\n page.once(\"close\", () => pagesById.delete(id));\n return id;\n}\n\nfunction pageFor(id: string): Page {\n assertBrowser();\n const page = pagesById.get(id);\n if (page === undefined || page.isClosed() || !pages().includes(page)) {\n throw new Error(`Browser tab does not exist: ${id}`);\n }\n return page;\n}\n\nfunction targetPage(id: string, operation: ActiveOperation): Page {\n const page = pageFor(id);\n operation.page = page;\n return page;\n}\n\nasync function tabSummaries(): Promise<readonly BrowserTab[]> {\n assertBrowser();\n if (selected?.isClosed()) selected = pages()[0];\n return Promise.all(\n pages().map(async (page) => ({\n id: idFor(page),\n title: await page.title(),\n url: page.url(),\n selected: page === selected,\n })),\n );\n}\n\nasync function tabResult(page: Page) {\n return { tabId: idFor(page), title: await page.title(), url: page.url() };\n}\n\nasync function setNavigationPolicy(policy: BrowserNavigationPolicy): Promise<void> {\n const key = JSON.stringify(policy);\n if (navigationPolicyKey !== undefined) {\n if (navigationPolicyKey !== key) {\n throw new TypeError(\"Browser connection already has a different navigation policy.\");\n }\n return;\n }\n navigationPolicyKey = key;\n await Promise.all(\n assertBrowser()\n .contexts()\n .map((context) => context.route(\"**/*\", (route) => enforceNavigationPolicy(route, policy))),\n );\n}\n\nfunction locatorFor(page: Page, target: BrowserTarget): Locator {\n switch (target.by) {\n case \"role\": {\n const options: { name?: string; exact?: boolean } = {};\n if (target.name !== undefined) options.name = target.name;\n if (target.exact !== undefined) options.exact = target.exact;\n return page.getByRole(target.role as never, options);\n }\n case \"text\": {\n const options: { exact?: boolean } = {};\n if (target.exact !== undefined) options.exact = target.exact;\n return page.getByText(target.text, options);\n }\n case \"label\": {\n const options: { exact?: boolean } = {};\n if (target.exact !== undefined) options.exact = target.exact;\n return page.getByLabel(target.label, options);\n }\n case \"placeholder\": {\n const options: { exact?: boolean } = {};\n if (target.exact !== undefined) options.exact = target.exact;\n return page.getByPlaceholder(target.placeholder, options);\n }\n case \"test-id\":\n return page.getByTestId(target.testId);\n case \"css\":\n return page.locator(target.selector);\n }\n}\n\nfunction serializeError(error: unknown): SerializedError {\n if (error instanceof Error) {\n const value: { name: string; message: string; stack?: string; code?: string } = {\n name: error.name,\n message: error.message,\n };\n if (error.stack !== undefined) value.stack = error.stack;\n if (\"code\" in error && typeof error.code === \"string\") value.code = error.code;\n return value;\n }\n return { name: \"Error\", message: String(error) };\n}\n\nfunction send(message: AutomationResponse): void {\n if (!process.connected || process.send === undefined) return;\n process.send(message, () => undefined);\n}\n\nasync function closeAfterParentExit(): Promise<void> {\n const forceExit = setTimeout(() => process.exit(1), 2_000);\n const connected = browser;\n browser = undefined;\n await connected?.close().catch(() => undefined);\n clearTimeout(forceExit);\n process.exit(0);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,195 @@
1
+ // src/internal/ip-check.ts
2
+ var BLOCKED_IPV4 = [
3
+ ["0.0.0.0", 8],
4
+ // "this network"
5
+ ["10.0.0.0", 8],
6
+ // private network
7
+ ["100.64.0.0", 10],
8
+ // shared address space (carrier-grade NAT)
9
+ ["127.0.0.0", 8],
10
+ // loopback
11
+ ["169.254.0.0", 16],
12
+ // link-local, includes cloud metadata at 169.254.169.254
13
+ ["172.16.0.0", 12],
14
+ // private network
15
+ ["192.0.0.0", 24],
16
+ // IETF protocol assignments
17
+ ["192.0.2.0", 24],
18
+ // documentation (TEST-NET-1)
19
+ ["192.88.99.0", 24],
20
+ // 6to4 relay anycast (deprecated)
21
+ ["192.168.0.0", 16],
22
+ // private network
23
+ ["198.18.0.0", 15],
24
+ // benchmarking
25
+ ["198.51.100.0", 24],
26
+ // documentation (TEST-NET-2)
27
+ ["203.0.113.0", 24],
28
+ // documentation (TEST-NET-3)
29
+ ["224.0.0.0", 4],
30
+ // multicast
31
+ ["240.0.0.0", 4]
32
+ // reserved, includes 255.255.255.255
33
+ ];
34
+ var BLOCKED_IPV6 = [
35
+ ["::", 96],
36
+ // unspecified, loopback, and deprecated IPv4-compatible addresses
37
+ ["::ffff:0:0", 96],
38
+ // IPv4-mapped (alternate spelling of an IPv4 destination)
39
+ ["64:ff9b:1::", 48],
40
+ // NAT64 local-use prefix
41
+ ["100::", 64],
42
+ // discard-only
43
+ ["100:0:0:1::", 64],
44
+ // dummy IPv6 prefix
45
+ ["2001::", 32],
46
+ // Teredo
47
+ ["2001:2::", 48],
48
+ // benchmarking
49
+ ["2001:10::", 28],
50
+ // ORCHID (deprecated)
51
+ ["2001:db8::", 32],
52
+ // documentation
53
+ ["2002::", 16],
54
+ // 6to4
55
+ ["3fff::", 20],
56
+ // documentation
57
+ ["5f00::", 16],
58
+ // segment routing SIDs
59
+ ["fc00::", 7],
60
+ // unique local
61
+ ["fe80::", 10],
62
+ // link-local
63
+ ["fec0::", 10],
64
+ // site-local (deprecated)
65
+ ["ff00::", 8]
66
+ // multicast
67
+ ];
68
+ var GLOBALLY_REACHABLE_IPV4 = /* @__PURE__ */ new Set([3221225481, 3221225482]);
69
+ var BLOCKED_IPV4_RANGES = compileIpv4Ranges(BLOCKED_IPV4);
70
+ var BLOCKED_IPV6_RANGES = compileIpv6Ranges(BLOCKED_IPV6);
71
+ function isPrivateOrReservedHost(hostname) {
72
+ const host = stripBrackets(hostname).toLowerCase().replace(/\.+$/, "");
73
+ if (host === "localhost" || host.endsWith(".localhost")) return true;
74
+ const ipv4 = parseIpv4(host);
75
+ if (ipv4 !== null) return isBlockedIpv4(ipv4);
76
+ const ipv6 = parseIpv6(host);
77
+ if (ipv6 !== null) return isBlockedIpv6(ipv6);
78
+ return false;
79
+ }
80
+ function stripBrackets(hostname) {
81
+ return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
82
+ }
83
+ function isBlockedIpv4(value) {
84
+ if (GLOBALLY_REACHABLE_IPV4.has(value)) return false;
85
+ for (const [network, prefix] of BLOCKED_IPV4_RANGES) {
86
+ if (isInIpv4Range(value, network, prefix)) return true;
87
+ }
88
+ return false;
89
+ }
90
+ function isBlockedIpv6(value) {
91
+ for (const [network, prefix] of BLOCKED_IPV6_RANGES) {
92
+ if (isInIpv6Range(value, network, prefix)) return true;
93
+ }
94
+ return false;
95
+ }
96
+ function isInIpv4Range(value, network, prefix) {
97
+ if (prefix === 0) return true;
98
+ const shift = 32 - prefix;
99
+ return value >>> shift === network >>> shift;
100
+ }
101
+ function isInIpv6Range(value, network, prefix) {
102
+ if (prefix === 0) return true;
103
+ const shift = BigInt(128 - prefix);
104
+ return value >> shift === network >> shift;
105
+ }
106
+ function parseIpv4(value) {
107
+ const parts = value.split(".");
108
+ if (parts.length !== 4) return null;
109
+ let result = 0;
110
+ for (const part of parts) {
111
+ if (!/^\d{1,3}$/.test(part)) return null;
112
+ const octet = Number(part);
113
+ if (octet > 255) return null;
114
+ result = result * 256 + octet;
115
+ }
116
+ return result;
117
+ }
118
+ function parseIpv6(value) {
119
+ const zone = value.indexOf("%");
120
+ const address = zone === -1 ? value : value.slice(0, zone);
121
+ let text = address;
122
+ const embedded = /(\d{1,3}(?:\.\d{1,3}){3})$/.exec(address);
123
+ if (embedded !== null) {
124
+ const ipv4 = parseIpv4(embedded[1] ?? "");
125
+ if (ipv4 === null) return null;
126
+ const groups = `${(ipv4 >>> 16).toString(16)}:${(ipv4 & 65535).toString(16)}`;
127
+ text = `${address.slice(0, embedded.index)}${groups}`;
128
+ }
129
+ const halves = text.split("::");
130
+ if (halves.length > 2) return null;
131
+ if (halves.length === 1) {
132
+ const groups = text.split(":");
133
+ return groups.length === 8 ? foldHexGroups(groups) : null;
134
+ }
135
+ const [head = "", tail = ""] = halves;
136
+ const leading = head === "" ? [] : head.split(":");
137
+ const trailing = tail === "" ? [] : tail.split(":");
138
+ const missing = 8 - leading.length - trailing.length;
139
+ if (missing < 1) return null;
140
+ const foldedHead = foldHexGroups(leading);
141
+ const foldedTail = foldHexGroups(trailing);
142
+ if (foldedHead === null || foldedTail === null) return null;
143
+ return foldedHead << BigInt(16 * (missing + trailing.length)) | foldedTail;
144
+ }
145
+ function foldHexGroups(groups) {
146
+ let value = 0n;
147
+ for (const group of groups) {
148
+ if (!/^[0-9a-f]{1,4}$/.test(group)) return null;
149
+ value = value << 16n | BigInt(parseInt(group, 16));
150
+ }
151
+ return value;
152
+ }
153
+ function compileIpv4Ranges(ranges) {
154
+ return ranges.map(([network, prefix]) => {
155
+ const value = parseIpv4(network);
156
+ if (value === null) throw new TypeError(`Invalid IPv4 range: ${network}/${prefix}`);
157
+ return [value, prefix];
158
+ });
159
+ }
160
+ function compileIpv6Ranges(ranges) {
161
+ return ranges.map(([network, prefix]) => {
162
+ const value = parseIpv6(network);
163
+ if (value === null) throw new TypeError(`Invalid IPv6 range: ${network}/${prefix}`);
164
+ return [value, prefix];
165
+ });
166
+ }
167
+
168
+ // src/internal/navigation-policy.ts
169
+ function isNavigationAllowed(value, policy) {
170
+ let url;
171
+ try {
172
+ url = new URL(value);
173
+ } catch {
174
+ return false;
175
+ }
176
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
177
+ if (url.username.length > 0 || url.password.length > 0) return false;
178
+ if (isPrivateOrReservedHost(url.hostname)) return false;
179
+ return policy.mode === "allow-all-http" || policy.origins.includes(url.origin);
180
+ }
181
+ async function enforceNavigationPolicy(route, policy) {
182
+ const request = route.request();
183
+ const frame = request.frame();
184
+ if (request.isNavigationRequest() && frame === frame.page().mainFrame() && !isNavigationAllowed(request.url(), policy)) {
185
+ await route.abort("blockedbyclient");
186
+ return;
187
+ }
188
+ await route.continue();
189
+ }
190
+
191
+ export {
192
+ isNavigationAllowed,
193
+ enforceNavigationPolicy
194
+ };
195
+ //# sourceMappingURL=chunk-XE5WKN5F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/ip-check.ts","../src/internal/navigation-policy.ts"],"sourcesContent":["/**\n * Shared SSRF guard: classify a hostname as private, reserved, or public.\n *\n * Called from the navigation policy checks in ./navigation-policy.ts, which run both\n * before the tool dispatches a navigation and inside the worker's route handler for\n * every top-level navigation request, including redirects.\n *\n * Blocked ranges follow the IANA IPv4/IPv6 special-purpose address registries.\n * Addresses that IANA marks globally reachable inside otherwise special-purpose space\n * stay reachable.\n *\n * IPv4 spellings are normalized by the WHATWG URL parser before this guard runs\n * (`127.1`, `2130706433`, `0x7f.0.0.1`, and `0177.0.0.1` all arrive as `127.0.0.1`),\n * and hosts that are not valid literals are left to DNS.\n */\n\ntype AddressRange = readonly [network: string, prefix: number];\n\n/** Non-global IPv4 ranges plus transition mechanisms that can encode another address. */\nconst BLOCKED_IPV4: readonly AddressRange[] = [\n [\"0.0.0.0\", 8], // \"this network\"\n [\"10.0.0.0\", 8], // private network\n [\"100.64.0.0\", 10], // shared address space (carrier-grade NAT)\n [\"127.0.0.0\", 8], // loopback\n [\"169.254.0.0\", 16], // link-local, includes cloud metadata at 169.254.169.254\n [\"172.16.0.0\", 12], // private network\n [\"192.0.0.0\", 24], // IETF protocol assignments\n [\"192.0.2.0\", 24], // documentation (TEST-NET-1)\n [\"192.88.99.0\", 24], // 6to4 relay anycast (deprecated)\n [\"192.168.0.0\", 16], // private network\n [\"198.18.0.0\", 15], // benchmarking\n [\"198.51.100.0\", 24], // documentation (TEST-NET-2)\n [\"203.0.113.0\", 24], // documentation (TEST-NET-3)\n [\"224.0.0.0\", 4], // multicast\n [\"240.0.0.0\", 4], // reserved, includes 255.255.255.255\n];\n\n/** Non-global IPv6 ranges plus transition mechanisms that can encode another address. */\nconst BLOCKED_IPV6: readonly AddressRange[] = [\n [\"::\", 96], // unspecified, loopback, and deprecated IPv4-compatible addresses\n [\"::ffff:0:0\", 96], // IPv4-mapped (alternate spelling of an IPv4 destination)\n [\"64:ff9b:1::\", 48], // NAT64 local-use prefix\n [\"100::\", 64], // discard-only\n [\"100:0:0:1::\", 64], // dummy IPv6 prefix\n [\"2001::\", 32], // Teredo\n [\"2001:2::\", 48], // benchmarking\n [\"2001:10::\", 28], // ORCHID (deprecated)\n [\"2001:db8::\", 32], // documentation\n [\"2002::\", 16], // 6to4\n [\"3fff::\", 20], // documentation\n [\"5f00::\", 16], // segment routing SIDs\n [\"fc00::\", 7], // unique local\n [\"fe80::\", 10], // link-local\n [\"fec0::\", 10], // site-local (deprecated)\n [\"ff00::\", 8], // multicast\n];\n\n/** IANA marks these two /32s globally reachable although 192.0.0.0/24 is not. */\nconst GLOBALLY_REACHABLE_IPV4: ReadonlySet<number> = new Set([0xc0000009, 0xc000000a]);\n\nconst BLOCKED_IPV4_RANGES = compileIpv4Ranges(BLOCKED_IPV4);\nconst BLOCKED_IPV6_RANGES = compileIpv6Ranges(BLOCKED_IPV6);\n\nexport function isPrivateOrReservedHost(hostname: string): boolean {\n const host = stripBrackets(hostname).toLowerCase().replace(/\\.+$/, \"\");\n\n if (host === \"localhost\" || host.endsWith(\".localhost\")) return true;\n\n const ipv4 = parseIpv4(host);\n if (ipv4 !== null) return isBlockedIpv4(ipv4);\n\n const ipv6 = parseIpv6(host);\n if (ipv6 !== null) return isBlockedIpv6(ipv6);\n\n return false;\n}\n\nfunction stripBrackets(hostname: string): string {\n return hostname.startsWith(\"[\") && hostname.endsWith(\"]\") ? hostname.slice(1, -1) : hostname;\n}\n\nfunction isBlockedIpv4(value: number): boolean {\n if (GLOBALLY_REACHABLE_IPV4.has(value)) return false;\n for (const [network, prefix] of BLOCKED_IPV4_RANGES) {\n if (isInIpv4Range(value, network, prefix)) return true;\n }\n return false;\n}\n\nfunction isBlockedIpv6(value: bigint): boolean {\n for (const [network, prefix] of BLOCKED_IPV6_RANGES) {\n if (isInIpv6Range(value, network, prefix)) return true;\n }\n return false;\n}\n\nfunction isInIpv4Range(value: number, network: number, prefix: number): boolean {\n if (prefix === 0) return true;\n const shift = 32 - prefix;\n return value >>> shift === network >>> shift;\n}\n\nfunction isInIpv6Range(value: bigint, network: bigint, prefix: number): boolean {\n if (prefix === 0) return true;\n const shift = BigInt(128 - prefix);\n return value >> shift === network >> shift;\n}\n\n/** Parse a dotted quad into its unsigned 32-bit value, or null when it is not one. */\nfunction parseIpv4(value: string): number | null {\n const parts = value.split(\".\");\n if (parts.length !== 4) return null;\n let result = 0;\n for (const part of parts) {\n if (!/^\\d{1,3}$/.test(part)) return null;\n const octet = Number(part);\n if (octet > 255) return null;\n result = result * 256 + octet;\n }\n return result;\n}\n\n/** Parse an IPv6 address (with optional embedded IPv4) into its 128-bit value. */\nfunction parseIpv6(value: string): bigint | null {\n // Zone identifiers (`fe80::1%eth0`) are invalid inside URLs; strip the zone so a\n // caller passing one directly still gets the address classified.\n const zone = value.indexOf(\"%\");\n const address = zone === -1 ? value : value.slice(0, zone);\n\n let text = address;\n const embedded = /(\\d{1,3}(?:\\.\\d{1,3}){3})$/.exec(address);\n if (embedded !== null) {\n const ipv4 = parseIpv4(embedded[1] ?? \"\");\n if (ipv4 === null) return null;\n const groups = `${(ipv4 >>> 16).toString(16)}:${(ipv4 & 0xffff).toString(16)}`;\n text = `${address.slice(0, embedded.index)}${groups}`;\n }\n\n const halves = text.split(\"::\");\n if (halves.length > 2) return null;\n if (halves.length === 1) {\n const groups = text.split(\":\");\n return groups.length === 8 ? foldHexGroups(groups) : null;\n }\n\n const [head = \"\", tail = \"\"] = halves;\n const leading = head === \"\" ? [] : head.split(\":\");\n const trailing = tail === \"\" ? [] : tail.split(\":\");\n const missing = 8 - leading.length - trailing.length;\n if (missing < 1) return null;\n const foldedHead = foldHexGroups(leading);\n const foldedTail = foldHexGroups(trailing);\n if (foldedHead === null || foldedTail === null) return null;\n return (foldedHead << BigInt(16 * (missing + trailing.length))) | foldedTail;\n}\n\nfunction foldHexGroups(groups: readonly string[]): bigint | null {\n let value = 0n;\n for (const group of groups) {\n if (!/^[0-9a-f]{1,4}$/.test(group)) return null;\n value = (value << 16n) | BigInt(parseInt(group, 16));\n }\n return value;\n}\n\nfunction compileIpv4Ranges(\n ranges: readonly AddressRange[],\n): ReadonlyArray<readonly [number, number]> {\n return ranges.map(([network, prefix]) => {\n const value = parseIpv4(network);\n if (value === null) throw new TypeError(`Invalid IPv4 range: ${network}/${prefix}`);\n return [value, prefix] as const;\n });\n}\n\nfunction compileIpv6Ranges(\n ranges: readonly AddressRange[],\n): ReadonlyArray<readonly [bigint, number]> {\n return ranges.map(([network, prefix]) => {\n const value = parseIpv6(network);\n if (value === null) throw new TypeError(`Invalid IPv6 range: ${network}/${prefix}`);\n return [value, prefix] as const;\n });\n}\n","/**\n * Shared navigation policy checks.\n *\n * Used by the tool layer (tools.ts) to reject a navigation before it is dispatched and\n * by the worker's route handler (automation-worker.ts) to re-check every top-level\n * navigation request, so a redirect cannot reach a destination the policy forbids.\n */\nimport type { Route } from \"playwright-core\";\nimport type { BrowserNavigationPolicy } from \"../types\";\nimport { isPrivateOrReservedHost } from \"./ip-check\";\n\nexport function isNavigationAllowed(value: string, policy: BrowserNavigationPolicy): boolean {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n if (url.username.length > 0 || url.password.length > 0) return false;\n\n // Private and reserved literal destinations stay blocked under every policy mode.\n if (isPrivateOrReservedHost(url.hostname)) return false;\n\n return policy.mode === \"allow-all-http\" || policy.origins.includes(url.origin);\n}\n\n/**\n * Abort top-level navigation requests that the policy rejects, including each redirect\n * hop Chromium follows after an allowed initial navigation.\n */\nexport async function enforceNavigationPolicy(\n route: Route,\n policy: BrowserNavigationPolicy,\n): Promise<void> {\n const request = route.request();\n const frame = request.frame();\n if (\n request.isNavigationRequest() &&\n frame === frame.page().mainFrame() &&\n !isNavigationAllowed(request.url(), policy)\n ) {\n await route.abort(\"blockedbyclient\");\n return;\n }\n await route.continue();\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ isNavigationAllowed
3
+ } from "./chunk-XE5WKN5F.js";
4
+
1
5
  // src/docker-browser-client.ts
2
6
  import { fileURLToPath } from "url";
3
7
 
@@ -2439,15 +2443,6 @@ function assertNavigationAllowed(value, policy) {
2439
2443
  throw new BrowserError(`Browser navigation is blocked: ${value}`, "navigation_blocked");
2440
2444
  }
2441
2445
  }
2442
- function isNavigationAllowed(value, policy) {
2443
- let url;
2444
- try {
2445
- url = parseHttpUrl(value);
2446
- } catch {
2447
- return false;
2448
- }
2449
- return policy.mode === "allow-all-http" || policy.origins.includes(url.origin);
2450
- }
2451
2446
  function parseHttpUrl(value) {
2452
2447
  const url = new URL(value);
2453
2448
  if (url.protocol !== "http:" && url.protocol !== "https:") {