66
77import fs from 'node:fs/promises' ;
88import path from 'node:path' ;
9+ import { fileURLToPath } from 'node:url' ;
910
1011import type { TargetUniverse } from './DevtoolsUtils.js' ;
1112import { UniverseManager } from './DevtoolsUtils.js' ;
@@ -18,21 +19,22 @@ import {
1819 type ListenerMap ,
1920 type UncaughtError ,
2021} from './PageCollector.js' ;
21- import type {
22- Browser ,
23- BrowserContext ,
24- ConsoleMessage ,
25- Debugger ,
26- HTTPRequest ,
27- Page ,
28- ScreenRecorder ,
29- Viewport ,
30- Target ,
31- Extension ,
22+ import {
23+ Locator ,
24+ PredefinedNetworkConditions ,
25+ type Browser ,
26+ type BrowserContext ,
27+ type ConsoleMessage ,
28+ type Debugger ,
29+ type HTTPRequest ,
30+ type Page ,
31+ type ScreenRecorder ,
32+ type Viewport ,
33+ type Target ,
34+ type Extension ,
35+ type Root ,
36+ type DevTools ,
3237} from './third_party/index.js' ;
33- import type { DevTools } from './third_party/index.js' ;
34- import { Locator } from './third_party/index.js' ;
35- import { PredefinedNetworkConditions } from './third_party/index.js' ;
3638import { listPages } from './tools/pages.js' ;
3739import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js' ;
3840import type { Context , SupportedExtensions } from './tools/ToolDefinition.js' ;
@@ -42,7 +44,7 @@ import type {
4244 GeolocationOptions ,
4345 ExtensionServiceWorker ,
4446} from './types.js' ;
45- import { ensureExtension , saveTemporaryFile } from './utils/files.js' ;
47+ import { ensureExtension , getTempFilePath } from './utils/files.js' ;
4648import { getNetworkMultiplierFromString } from './WaitForHelper.js' ;
4749
4850interface McpContextOptions {
@@ -90,6 +92,7 @@ export class McpContext implements Context {
9092 #locatorClass: typeof Locator ;
9193 #options: McpContextOptions ;
9294 #heapSnapshotManager = new HeapSnapshotManager ( ) ;
95+ #roots: Root [ ] | undefined = undefined ;
9396
9497 private constructor (
9598 browser : Browser ,
@@ -154,6 +157,34 @@ export class McpContext implements Context {
154157 return context ;
155158 }
156159
160+ roots ( ) : Root [ ] | undefined {
161+ return this . #roots;
162+ }
163+
164+ setRoots ( roots : Root [ ] | undefined ) : void {
165+ this . #roots = roots ;
166+ }
167+
168+ validatePath ( filePath : string ) : void {
169+ const roots = this . roots ( ) ;
170+ if ( roots === undefined ) {
171+ return ;
172+ }
173+ const absolutePath = path . resolve ( filePath ) ;
174+ for ( const root of roots ) {
175+ const rootPath = path . resolve ( fileURLToPath ( root . uri ) ) ;
176+ if (
177+ absolutePath === rootPath ||
178+ absolutePath . startsWith ( rootPath + path . sep )
179+ ) {
180+ return ;
181+ }
182+ }
183+ throw new Error (
184+ `Access denied: path ${ filePath } is not within any of the workspace roots ${ JSON . stringify ( roots ) } .` ,
185+ ) ;
186+ }
187+
157188 resolveCdpRequestId ( page : McpPage , cdpRequestId : string ) : number | undefined {
158189 if ( ! cdpRequestId ) {
159190 this . logger ( 'no network request' ) ;
@@ -643,13 +674,18 @@ export class McpContext implements Context {
643674 data : Uint8Array < ArrayBufferLike > ,
644675 filename : string ,
645676 ) : Promise < { filepath : string } > {
646- return await saveTemporaryFile ( data , filename ) ;
677+ const filepath = await getTempFilePath ( filename ) ;
678+ this . validatePath ( filepath ) ;
679+ await fs . writeFile ( filepath , data ) ;
680+ return { filepath} ;
647681 }
682+
648683 async saveFile (
649684 data : Uint8Array < ArrayBufferLike > ,
650685 clientProvidedFilePath : string ,
651686 extension : SupportedExtensions ,
652687 ) : Promise < { filename : string } > {
688+ this . validatePath ( clientProvidedFilePath ) ;
653689 try {
654690 const filePath = ensureExtension (
655691 path . resolve ( clientProvidedFilePath ) ,
@@ -721,6 +757,7 @@ export class McpContext implements Context {
721757 }
722758
723759 async installExtension ( extensionPath : string ) : Promise < string > {
760+ this . validatePath ( extensionPath ) ;
724761 const id = await this . browser . installExtension ( extensionPath ) ;
725762 return id ;
726763 }
@@ -751,25 +788,29 @@ export class McpContext implements Context {
751788 async getHeapSnapshotAggregates (
752789 filePath : string ,
753790 ) : Promise < Record < string , AggregatedInfoWithUid > > {
791+ this . validatePath ( filePath ) ;
754792 return await this . #heapSnapshotManager. getAggregates ( filePath ) ;
755793 }
756794
757795 async getHeapSnapshotStats (
758796 filePath : string ,
759797 ) : Promise < DevTools . HeapSnapshotModel . HeapSnapshotModel . Statistics > {
798+ this . validatePath ( filePath ) ;
760799 return await this . #heapSnapshotManager. getStats ( filePath ) ;
761800 }
762801
763802 async getHeapSnapshotStaticData (
764803 filePath : string ,
765804 ) : Promise < DevTools . HeapSnapshotModel . HeapSnapshotModel . StaticData | null > {
805+ this . validatePath ( filePath ) ;
766806 return await this . #heapSnapshotManager. getStaticData ( filePath ) ;
767807 }
768808
769809 async getHeapSnapshotNodesByUid (
770810 filePath : string ,
771811 uid : number ,
772812 ) : Promise < DevTools . HeapSnapshotModel . HeapSnapshotModel . ItemsRange > {
813+ this . validatePath ( filePath ) ;
773814 return await this . #heapSnapshotManager. getNodesByUid ( filePath , uid ) ;
774815 }
775816}
0 commit comments