diff --git a/packages/playwright-core/src/tools/backend/context.ts b/packages/playwright-core/src/tools/backend/context.ts index d95462452d37f..8d0c7399dcc9b 100644 --- a/packages/playwright-core/src/tools/backend/context.ts +++ b/packages/playwright-core/src/tools/backend/context.ts @@ -353,6 +353,15 @@ export class Context { code: `process.env['${secretName}']`, }; } + + redactSecrets(text: string): string { + for (const [secretName, secretValue] of Object.entries(this.config.secrets ?? {})) { + if (!secretValue) + continue; + text = text.replaceAll(secretValue, `${secretName}`); + } + return text; + } } function originOrHostGlob(originOrHost: string) { diff --git a/packages/playwright-core/src/tools/backend/logFile.ts b/packages/playwright-core/src/tools/backend/logFile.ts index 1bbaefcb4db01..66bbf6efc867b 100644 --- a/packages/playwright-core/src/tools/backend/logFile.ts +++ b/packages/playwright-core/src/tools/backend/logFile.ts @@ -91,7 +91,7 @@ export class LogFile { return; this._file ??= await this._context.outputFile({ prefix: this._filePrefix, ext: 'log', date: new Date(this._startTime) }, { origin: 'code' }); const relativeTime = Math.round(wallTime - this._startTime); - const logLine = `[${String(relativeTime).padStart(8, ' ')}ms] ${text}\n`; + const logLine = `[${String(relativeTime).padStart(8, ' ')}ms] ${this._context.redactSecrets(text)}\n`; await fs.promises.appendFile(this._file, logLine); const lineCount = logLine.split('\n').length - 1; diff --git a/packages/playwright-core/src/tools/backend/response.ts b/packages/playwright-core/src/tools/backend/response.ts index db10fb696f5cd..388fb04d80673 100644 --- a/packages/playwright-core/src/tools/backend/response.ts +++ b/packages/playwright-core/src/tools/backend/response.ts @@ -111,7 +111,7 @@ export class Response { private async _writeFile(resolvedFile: ResolvedFile, data: Buffer | string | null) { if (typeof data === 'string') - await fs.promises.writeFile(resolvedFile.fileName, this._redactSecrets(data), 'utf-8'); + await fs.promises.writeFile(resolvedFile.fileName, this._context.redactSecrets(data), 'utf-8'); else if (data) await fs.promises.writeFile(resolvedFile.fileName, data); this._writtenFiles.add(path.resolve(resolvedFile.fileName)); @@ -155,16 +155,6 @@ export class Response { this._includeSnapshotRoot = root; } - private _redactSecrets(text: string): string { - for (const [secretName, secretValue] of Object.entries(this._context.config.secrets ?? {})) { - if (!secretValue) - continue; - text = text.replaceAll(secretValue, `${secretName}`); - } - return text; - } - - async serialize(): Promise { const allSections = await this._build(); await this._enforceOutputBudget(); @@ -211,7 +201,7 @@ export class Response { const content: (TextContent | ImageContent)[] = [ { type: 'text', - text: sanitizeUnicode(this._redactSecrets(serializedText)), + text: sanitizeUnicode(this._context.redactSecrets(serializedText)), } ]; diff --git a/tests/mcp/secrets.spec.ts b/tests/mcp/secrets.spec.ts index eeae81a5bec5a..266c7f34ea376 100644 --- a/tests/mcp/secrets.spec.ts +++ b/tests/mcp/secrets.spec.ts @@ -16,7 +16,7 @@ import fs from 'node:fs'; -import { test, expect } from './fixtures'; +import { test, expect, parseResponse, consoleEntries } from './fixtures'; test('browser_type', async ({ startClient, server }) => { const secretsFile = test.info().outputPath('secrets.env'); @@ -65,6 +65,43 @@ await page.getByRole('textbox').press('Enter');`, }); +test('secrets are redacted in console log artifact', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41509' } }, async ({ startClient, server }) => { + const secretsFile = test.info().outputPath('secrets.env'); + await fs.promises.writeFile(secretsFile, 'X-PASSWORD=password123'); + + const { client } = await startClient({ + args: ['--secrets', secretsFile], + }); + + server.setContent('/', ` + + + + + `, 'text/html'); + + await client.callTool({ + name: 'browser_navigate', + arguments: { + url: server.PREFIX, + }, + }); + + const response = parseResponse(await client.callTool({ + name: 'browser_type', + arguments: { + element: 'textbox', + target: 'e2', + text: 'X-PASSWORD', + submit: true, + }, + })); + + const consoleLog = await consoleEntries(response); + expect(consoleLog).not.toContain('password123'); + expect(consoleLog).toContain('X-PASSWORD'); +}); + test('browser_fill_form', async ({ startClient, server }) => { const secretsFile = test.info().outputPath('secrets.env'); await fs.promises.writeFile(secretsFile, 'X-PASSWORD=password123');