refactor: consolidate extension ID arrays and centralize target filtering
This commit is contained in:
+57
-15
@@ -16,7 +16,45 @@ type ConnectedTarget = {
|
|||||||
targetInfo: Protocol.Target.TargetInfo
|
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 = {
|
type PlaywrightClient = {
|
||||||
id: string
|
id: string
|
||||||
@@ -298,10 +336,12 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
|
|||||||
|
|
||||||
case 'Target.getTargets': {
|
case 'Target.getTargets': {
|
||||||
return {
|
return {
|
||||||
targetInfos: Array.from(connectedTargets.values()).map((t) => ({
|
targetInfos: Array.from(connectedTargets.values())
|
||||||
...t.targetInfo,
|
.filter((t) => !isRestrictedTarget(t.targetInfo))
|
||||||
attached: true
|
.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.
|
// 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
|
// We only allow our specific extension IDs to prevent malicious websites or extensions
|
||||||
// from connecting to the local WebSocket server.
|
// 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) => {
|
app.get('/cdp/:clientId?', (c, next) => {
|
||||||
const origin = c.req.header('origin')
|
const origin = c.req.header('origin')
|
||||||
|
|
||||||
@@ -484,7 +519,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
|
|||||||
if (origin) {
|
if (origin) {
|
||||||
if (origin.startsWith('chrome-extension://')) {
|
if (origin.startsWith('chrome-extension://')) {
|
||||||
const extensionId = origin.replace('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}`))
|
logger?.log(chalk.red(`Rejecting /cdp WebSocket from unknown extension: ${extensionId}`))
|
||||||
return c.text('Forbidden', 403)
|
return c.text('Forbidden', 403)
|
||||||
}
|
}
|
||||||
@@ -556,6 +591,10 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
|
|||||||
|
|
||||||
if (method === 'Target.setAutoAttach' && !sessionId) {
|
if (method === 'Target.setAutoAttach' && !sessionId) {
|
||||||
for (const target of connectedTargets.values()) {
|
for (const target of connectedTargets.values()) {
|
||||||
|
// Skip restricted targets (extensions, chrome:// URLs, non-page types)
|
||||||
|
if (isRestrictedTarget(target.targetInfo)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
const attachedPayload = {
|
const attachedPayload = {
|
||||||
method: 'Target.attachedToTarget',
|
method: 'Target.attachedToTarget',
|
||||||
params: {
|
params: {
|
||||||
@@ -581,6 +620,10 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
|
|||||||
|
|
||||||
if (method === 'Target.setDiscoverTargets' && (params as any)?.discover) {
|
if (method === 'Target.setDiscoverTargets' && (params as any)?.discover) {
|
||||||
for (const target of connectedTargets.values()) {
|
for (const target of connectedTargets.values()) {
|
||||||
|
// Skip restricted targets (extensions, chrome:// URLs, non-page types)
|
||||||
|
if (isRestrictedTarget(target.targetInfo)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
const targetCreatedPayload = {
|
const targetCreatedPayload = {
|
||||||
method: 'Target.targetCreated',
|
method: 'Target.targetCreated',
|
||||||
params: {
|
params: {
|
||||||
@@ -678,7 +721,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
|
|||||||
}
|
}
|
||||||
|
|
||||||
const extensionId = origin.replace('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 /extension WebSocket from unknown extension: ${extensionId}`))
|
logger?.log(chalk.red(`Rejecting /extension WebSocket from unknown extension: ${extensionId}`))
|
||||||
return c.text('Forbidden', 403)
|
return c.text('Forbidden', 403)
|
||||||
}
|
}
|
||||||
@@ -761,13 +804,12 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
|
|||||||
|
|
||||||
if (method === 'Target.attachedToTarget') {
|
if (method === 'Target.attachedToTarget') {
|
||||||
const targetParams = params as Protocol.Target.AttachedToTargetEvent
|
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
|
// These targets can't be properly controlled through chrome.debugger API
|
||||||
// and cause issues when Playwright tries to initialize them (issue #14)
|
// and cause issues when Playwright tries to initialize them (issue #14)
|
||||||
if (targetType !== 'page') {
|
if (isRestrictedTarget(targetParams.targetInfo)) {
|
||||||
logger?.log(chalk.gray(`[Server] Ignoring non-page target: ${targetType} (${targetParams.targetInfo.url})`))
|
logger?.log(chalk.gray(`[Server] Ignoring restricted target: ${targetParams.targetInfo.type} (${targetParams.targetInfo.url})`))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user