@@ -271,15 +271,27 @@ async function loadConfig(configFile: string | undefined): Promise<Config> {
271271 }
272272}
273273
274- export async function outputFile ( config : FullConfig , clientInfo : ClientInfo , name : string ) : Promise < string > {
274+ export async function outputFile ( config : FullConfig , clientInfo : ClientInfo , fileName : string , options : { origin : 'code' | 'llm' | 'web' } ) : Promise < string > {
275275 const rootPath = firstRootPath ( clientInfo ) ;
276276 const outputDir = config . outputDir
277277 ?? ( rootPath ? path . join ( rootPath , '.playwright-mcp' ) : undefined )
278278 ?? path . join ( process . env . PW_TMPDIR_FOR_TEST ?? os . tmpdir ( ) , 'playwright-mcp-output' , String ( clientInfo . timestamp ) ) ;
279279
280- await fs . promises . mkdir ( outputDir , { recursive : true } ) ;
281- const fileName = sanitizeForFilePath ( name ) ;
282- return path . join ( outputDir , fileName ) ;
280+ // Trust code.
281+ if ( options . origin === 'code' )
282+ return path . resolve ( outputDir , fileName ) ;
283+
284+ // Trust llm to use valid characters in file names.
285+ if ( options . origin === 'llm' ) {
286+ fileName = fileName . split ( '\\' ) . join ( '/' ) ;
287+ const resolvedFile = path . resolve ( outputDir , fileName ) ;
288+ if ( ! resolvedFile . startsWith ( path . resolve ( outputDir ) + path . sep ) )
289+ throw new Error ( `Resolved file path for ${ fileName } is outside of the output directory` ) ;
290+ return resolvedFile ;
291+ }
292+
293+ // Do not trust web, at all.
294+ return path . join ( outputDir , sanitizeForFilePath ( fileName ) ) ;
283295}
284296
285297function pickDefined < T extends object > ( obj : T | undefined ) : Partial < T > {
0 commit comments