refactor: consolidate extension ID arrays and centralize target filtering

This commit is contained in:
Tommy D. Rossi
2026-01-14 17:36:46 +01:00
parent 17a15f6c49
commit 330d65e8f9
+57 -15
View File
@@ -16,7 +16,45 @@ type ConnectedTarget = {
targetInfo: Protocol.Target.TargetInfo
}
// Our extension IDs - allow attaching to our own extension pages for debugging
const OUR_EXTENSION_IDS = [
'jfeammnjpkecdekppnclgkkffahnhfhe', // Production extension (Chrome Web Store)
'elnnakgjclnapgflmidlpobefkdmapdm', // Dev extension (loaded unpacked)
]
/**
* Checks if a target should be filtered out (not exposed to Playwright).
* Filters extension pages, service workers, and other restricted targets,
* but allows our own extension pages for debugging purposes.
*/
function isRestrictedTarget(targetInfo: Protocol.Target.TargetInfo): boolean {
const { url, type } = targetInfo
// Filter by type - only allow 'page' type through
// Service workers, web workers, iframes, etc. cause issues when Playwright tries to initialize them
// Iframes are accessible via page.frameLocator() on the parent page
if (type !== 'page') {
return true
}
// Filter by URL - block extension and chrome internal pages
if (!url) {
return false
}
// Allow our own extension pages
if (url.startsWith('chrome-extension://')) {
const extensionId = url.replace('chrome-extension://', '').split('/')[0]
if (OUR_EXTENSION_IDS.includes(extensionId)) {
return false
}
return true
}
// Block other restricted URLs
const blockedPrefixes = ['chrome://', 'devtools://', 'edge://']
return blockedPrefixes.some((prefix) => url.startsWith(prefix))
}
type PlaywrightClient = {
id: string
@@ -298,10 +336,12 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
case 'Target.getTargets': {
return {
targetInfos: Array.from(connectedTargets.values()).map((t) => ({
...t.targetInfo,
attached: true
}))
targetInfos: Array.from(connectedTargets.values())
.filter((t) => !isRestrictedTarget(t.targetInfo))
.map((t) => ({
...t.targetInfo,
attached: true
}))
}
}
@@ -472,11 +512,6 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
// Browsers always send Origin header for WebSocket connections, but Node.js clients don't.
// We only allow our specific extension IDs to prevent malicious websites or extensions
// from connecting to the local WebSocket server.
const ALLOWED_EXTENSION_IDS = [
'jfeammnjpkecdekppnclgkkffahnhfhe', // Production extension (Chrome Web Store)
'elnnakgjclnapgflmidlpobefkdmapdm', // Dev extension (loaded unpacked)
]
app.get('/cdp/:clientId?', (c, next) => {
const origin = c.req.header('origin')
@@ -484,7 +519,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
if (origin) {
if (origin.startsWith('chrome-extension://')) {
const extensionId = origin.replace('chrome-extension://', '')
if (!ALLOWED_EXTENSION_IDS.includes(extensionId)) {
if (!OUR_EXTENSION_IDS.includes(extensionId)) {
logger?.log(chalk.red(`Rejecting /cdp WebSocket from unknown extension: ${extensionId}`))
return c.text('Forbidden', 403)
}
@@ -556,6 +591,10 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
if (method === 'Target.setAutoAttach' && !sessionId) {
for (const target of connectedTargets.values()) {
// Skip restricted targets (extensions, chrome:// URLs, non-page types)
if (isRestrictedTarget(target.targetInfo)) {
continue
}
const attachedPayload = {
method: 'Target.attachedToTarget',
params: {
@@ -581,6 +620,10 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
if (method === 'Target.setDiscoverTargets' && (params as any)?.discover) {
for (const target of connectedTargets.values()) {
// Skip restricted targets (extensions, chrome:// URLs, non-page types)
if (isRestrictedTarget(target.targetInfo)) {
continue
}
const targetCreatedPayload = {
method: 'Target.targetCreated',
params: {
@@ -678,7 +721,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
}
const extensionId = origin.replace('chrome-extension://', '')
if (!ALLOWED_EXTENSION_IDS.includes(extensionId)) {
if (!OUR_EXTENSION_IDS.includes(extensionId)) {
logger?.log(chalk.red(`Rejecting /extension WebSocket from unknown extension: ${extensionId}`))
return c.text('Forbidden', 403)
}
@@ -761,13 +804,12 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
if (method === 'Target.attachedToTarget') {
const targetParams = params as Protocol.Target.AttachedToTargetEvent
const targetType = targetParams.targetInfo.type
// Filter out non-page targets (service workers, web workers, etc.)
// Filter out restricted targets (non-page types, extension pages, chrome:// URLs, etc.)
// These targets can't be properly controlled through chrome.debugger API
// and cause issues when Playwright tries to initialize them (issue #14)
if (targetType !== 'page') {
logger?.log(chalk.gray(`[Server] Ignoring non-page target: ${targetType} (${targetParams.targetInfo.url})`))
if (isRestrictedTarget(targetParams.targetInfo)) {
logger?.log(chalk.gray(`[Server] Ignoring restricted target: ${targetParams.targetInfo.type} (${targetParams.targetInfo.url})`))
return
}