@@ -14,6 +14,7 @@ import { SsrFBlockedError, type SsrFPolicy } from "../infra/net/ssrf.js";
1414import { withNoProxyForCdpUrl } from "./cdp-proxy-bypass.js" ;
1515import {
1616 appendCdpPath ,
17+ assertCdpEndpointAllowed ,
1718 fetchJson ,
1819 getHeadersWithAuth ,
1920 normalizeCdpHttpBaseForJsonEndpoints ,
@@ -424,12 +425,15 @@ function observeBrowser(browser: Browser) {
424425 }
425426}
426427
427- async function connectBrowser ( cdpUrl : string ) : Promise < ConnectedBrowser > {
428+ async function connectBrowser ( cdpUrl : string , ssrfPolicy ?: SsrFPolicy ) : Promise < ConnectedBrowser > {
428429 const normalized = normalizeCdpUrl ( cdpUrl ) ;
429430 const cached = cachedByCdpUrl . get ( normalized ) ;
430431 if ( cached ) {
431432 return cached ;
432433 }
434+ // Run SSRF policy check only on cache miss so transient DNS failures
435+ // do not break active sessions that already hold a live CDP connection.
436+ await assertCdpEndpointAllowed ( normalized , ssrfPolicy ) ;
433437 const connecting = connectingByCdpUrl . get ( normalized ) ;
434438 if ( connecting ) {
435439 return await connecting ;
@@ -440,7 +444,9 @@ async function connectBrowser(cdpUrl: string): Promise<ConnectedBrowser> {
440444 for ( let attempt = 0 ; attempt < 3 ; attempt += 1 ) {
441445 try {
442446 const timeout = 5000 + attempt * 2000 ;
443- const wsUrl = await getChromeWebSocketUrl ( normalized , timeout ) . catch ( ( ) => null ) ;
447+ const wsUrl = await getChromeWebSocketUrl ( normalized , timeout , ssrfPolicy ) . catch (
448+ ( ) => null ,
449+ ) ;
444450 const endpoint = wsUrl ?? normalized ;
445451 const headers = getHeadersWithAuth ( endpoint ) ;
446452 // Bypass proxy for loopback CDP connections (#31219)
@@ -562,8 +568,10 @@ async function findPageByTargetIdViaTargetList(
562568 pages : Page [ ] ,
563569 targetId : string ,
564570 cdpUrl : string ,
571+ ssrfPolicy ?: SsrFPolicy ,
565572) : Promise < Page | null > {
566573 const cdpHttpBase = normalizeCdpHttpBaseForJsonEndpoints ( cdpUrl ) ;
574+ await assertCdpEndpointAllowed ( cdpUrl , ssrfPolicy ) ;
567575 const targets = await fetchJson <
568576 Array < {
569577 id : string ;
@@ -578,6 +586,7 @@ async function findPageByTargetId(
578586 browser : Browser ,
579587 targetId : string ,
580588 cdpUrl ?: string ,
589+ ssrfPolicy ?: SsrFPolicy ,
581590) : Promise < Page | null > {
582591 const pages = await getAllPages ( browser ) ;
583592 let resolvedViaCdp = false ;
@@ -595,7 +604,7 @@ async function findPageByTargetId(
595604 }
596605 if ( cdpUrl ) {
597606 try {
598- return await findPageByTargetIdViaTargetList ( pages , targetId , cdpUrl ) ;
607+ return await findPageByTargetIdViaTargetList ( pages , targetId , cdpUrl , ssrfPolicy ) ;
599608 } catch {
600609 // Ignore fetch errors and fall through to return null.
601610 }
@@ -609,12 +618,13 @@ async function findPageByTargetId(
609618async function resolvePageByTargetIdOrThrow ( opts : {
610619 cdpUrl : string ;
611620 targetId : string ;
621+ ssrfPolicy ?: SsrFPolicy ;
612622} ) : Promise < Page > {
613623 if ( isBlockedTarget ( opts . cdpUrl , opts . targetId ) ) {
614624 throw new BlockedBrowserTargetError ( ) ;
615625 }
616- const { browser } = await connectBrowser ( opts . cdpUrl ) ;
617- const page = await findPageByTargetId ( browser , opts . targetId , opts . cdpUrl ) ;
626+ const { browser } = await connectBrowser ( opts . cdpUrl , opts . ssrfPolicy ) ;
627+ const page = await findPageByTargetId ( browser , opts . targetId , opts . cdpUrl , opts . ssrfPolicy ) ;
618628 if ( ! page ) {
619629 throw new BrowserTabNotFoundError ( ) ;
620630 }
@@ -624,11 +634,12 @@ async function resolvePageByTargetIdOrThrow(opts: {
624634export async function getPageForTargetId ( opts : {
625635 cdpUrl : string ;
626636 targetId ?: string ;
637+ ssrfPolicy ?: SsrFPolicy ;
627638} ) : Promise < Page > {
628639 if ( opts . targetId && isBlockedTarget ( opts . cdpUrl , opts . targetId ) ) {
629640 throw new BlockedBrowserTargetError ( ) ;
630641 }
631- const { browser } = await connectBrowser ( opts . cdpUrl ) ;
642+ const { browser } = await connectBrowser ( opts . cdpUrl , opts . ssrfPolicy ) ;
632643 const pages = await getAllPages ( browser ) ;
633644 if ( ! pages . length ) {
634645 throw new Error ( "No pages available in the connected browser." ) ;
@@ -648,7 +659,7 @@ export async function getPageForTargetId(opts: {
648659 if ( ! opts . targetId ) {
649660 return first ;
650661 }
651- const found = await findPageByTargetId ( browser , opts . targetId , opts . cdpUrl ) ;
662+ const found = await findPageByTargetId ( browser , opts . targetId , opts . cdpUrl , opts . ssrfPolicy ) ;
652663 if ( found ) {
653664 if ( isBlockedPageRef ( opts . cdpUrl , found ) ) {
654665 throw new BlockedBrowserTargetError ( ) ;
@@ -887,7 +898,9 @@ function cdpSocketNeedsAttach(wsUrl: string): boolean {
887898async function tryTerminateExecutionViaCdp ( opts : {
888899 cdpUrl : string ;
889900 targetId : string ;
901+ ssrfPolicy ?: SsrFPolicy ;
890902} ) : Promise < void > {
903+ await assertCdpEndpointAllowed ( opts . cdpUrl , opts . ssrfPolicy ) ;
891904 const cdpHttpBase = normalizeCdpHttpBaseForJsonEndpoints ( opts . cdpUrl ) ;
892905 const listUrl = appendCdpPath ( cdpHttpBase , "/json/list" ) ;
893906
@@ -976,6 +989,7 @@ export async function forceDisconnectPlaywrightForTarget(opts: {
976989 cdpUrl : string ;
977990 targetId ?: string ;
978991 reason ?: string ;
992+ ssrfPolicy ?: SsrFPolicy ;
979993} ) : Promise < void > {
980994 const normalized = normalizeCdpUrl ( opts . cdpUrl ) ;
981995 const cur = cachedByCdpUrl . get ( normalized ) ;
@@ -996,7 +1010,7 @@ export async function forceDisconnectPlaywrightForTarget(opts: {
9961010 // disconnect Playwright's CDP connection.
9971011 const targetId = normalizeOptionalString ( opts . targetId ) ?? "" ;
9981012 if ( targetId ) {
999- await tryTerminateExecutionViaCdp ( { cdpUrl : normalized , targetId } ) . catch ( ( ) => { } ) ;
1013+ await tryTerminateExecutionViaCdp ( { cdpUrl : normalized , targetId, ssrfPolicy : opts . ssrfPolicy } ) . catch ( ( ) => { } ) ;
10001014 }
10011015
10021016 // Fire-and-forget: don't await because browser.close() may hang on the stuck CDP pipe.
@@ -1007,15 +1021,18 @@ export async function forceDisconnectPlaywrightForTarget(opts: {
10071021 * List all pages/tabs from the persistent Playwright connection.
10081022 * Used for remote profiles where HTTP-based /json/list is ephemeral.
10091023 */
1010- export async function listPagesViaPlaywright ( opts : { cdpUrl : string } ) : Promise <
1024+ export async function listPagesViaPlaywright ( opts : {
1025+ cdpUrl : string ;
1026+ ssrfPolicy ?: SsrFPolicy ;
1027+ } ) : Promise <
10111028 Array < {
10121029 targetId : string ;
10131030 title : string ;
10141031 url : string ;
10151032 type : string ;
10161033 } >
10171034> {
1018- const { browser } = await connectBrowser ( opts . cdpUrl ) ;
1035+ const { browser } = await connectBrowser ( opts . cdpUrl , opts . ssrfPolicy ) ;
10191036 const pages = await getAllPages ( browser ) ;
10201037 const results : Array < {
10211038 targetId : string ;
@@ -1056,7 +1073,7 @@ export async function createPageViaPlaywright(opts: {
10561073 url : string ;
10571074 type : string ;
10581075} > {
1059- const { browser } = await connectBrowser ( opts . cdpUrl ) ;
1076+ const { browser } = await connectBrowser ( opts . cdpUrl , opts . ssrfPolicy ) ;
10601077 const context = browser . contexts ( ) [ 0 ] ?? ( await browser . newContext ( ) ) ;
10611078 ensureContextState ( context ) ;
10621079
@@ -1119,6 +1136,7 @@ export async function createPageViaPlaywright(opts: {
11191136export async function closePageByTargetIdViaPlaywright ( opts : {
11201137 cdpUrl : string ;
11211138 targetId : string ;
1139+ ssrfPolicy ?: SsrFPolicy ;
11221140} ) : Promise < void > {
11231141 const page = await resolvePageByTargetIdOrThrow ( opts ) ;
11241142 await page . close ( ) ;
@@ -1131,6 +1149,7 @@ export async function closePageByTargetIdViaPlaywright(opts: {
11311149export async function focusPageByTargetIdViaPlaywright ( opts : {
11321150 cdpUrl : string ;
11331151 targetId : string ;
1152+ ssrfPolicy ?: SsrFPolicy ;
11341153} ) : Promise < void > {
11351154 const page = await resolvePageByTargetIdOrThrow ( opts ) ;
11361155 try {
0 commit comments