77 */
88
99import { workspaces } from '@angular-devkit/core' ;
10- import { dirname , join } from 'node:path' ;
10+ import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' ;
11+ import { dirname , isAbsolute , join , relative as relativePath , resolve } from 'node:path' ;
12+ import { fileURLToPath } from 'node:url' ;
1113import { AngularWorkspace } from '../../utilities/config' ;
1214import { type Host , LocalWorkspaceHost } from './host' ;
1315import { McpToolContext } from './tools/tool-registry' ;
@@ -92,11 +94,13 @@ export async function resolveWorkspaceAndProject({
9294 workspacePathInput,
9395 projectNameInput,
9496 mcpWorkspace,
97+ server,
9598} : {
9699 host : Host ;
97100 workspacePathInput ?: string ;
98101 projectNameInput ?: string ;
99102 mcpWorkspace ?: AngularWorkspace ;
103+ server ?: McpServer ;
100104} ) : Promise < {
101105 workspace : AngularWorkspace ;
102106 workspacePath : string ;
@@ -106,6 +110,29 @@ export async function resolveWorkspaceAndProject({
106110 let workspace : AngularWorkspace ;
107111
108112 if ( workspacePathInput ) {
113+ if ( server ) {
114+ // Validate that the provided workspace path is within the allowed MCP roots.
115+ // This prevents attackers from tricking the server into loading and executing code
116+ // from arbitrary locations on the filesystem.
117+ const rootsResponse = await server . server . listRoots ( ) ;
118+ const roots = rootsResponse . roots ;
119+ const normalizedInputPath = resolve ( workspacePathInput ) ;
120+
121+ const isAllowed = roots . some ( ( root ) => {
122+ const rootPath = resolve ( fileURLToPath ( root . uri ) ) ;
123+ const relative = relativePath ( rootPath , normalizedInputPath ) ;
124+
125+ return ! relative . startsWith ( '..' ) && ! isAbsolute ( relative ) ;
126+ } ) ;
127+
128+ if ( ! isAllowed ) {
129+ throw new Error (
130+ `Workspace path is outside the allowed MCP roots: ${ workspacePathInput } . ` +
131+ "You can use 'list_projects' to find available workspaces." ,
132+ ) ;
133+ }
134+ }
135+
109136 if ( ! host . existsSync ( workspacePathInput ) ) {
110137 throw new Error (
111138 `Workspace path does not exist: ${ workspacePathInput } . ` +
0 commit comments