diff --git a/packages/angular/build/src/builders/application/options.ts b/packages/angular/build/src/builders/application/options.ts index 99d5d67efbfd..db5c97d05002 100644 --- a/packages/angular/build/src/builders/application/options.ts +++ b/packages/angular/build/src/builders/application/options.ts @@ -308,14 +308,21 @@ export async function normalizeOptions( } const outputPath = options.outputPath ?? path.join(workspaceRoot, 'dist', projectName); + const resolvedOutputBase = path.resolve( + workspaceRoot, + typeof outputPath === 'string' ? outputPath : outputPath.base, + ); + if (!resolvedOutputBase.startsWith(workspaceRoot + path.sep)) { + throw new Error( + `Output path '${resolvedOutputBase}' must be inside the project root directory '${workspaceRoot}'.`, + ); + } const outputOptions: NormalizedOutputOptions = { browser: 'browser', server: 'server', media: 'media', ...(typeof outputPath === 'string' ? undefined : outputPath), - base: normalizeDirectoryPath( - path.resolve(workspaceRoot, typeof outputPath === 'string' ? outputPath : outputPath.base), - ), + base: normalizeDirectoryPath(resolvedOutputBase), clean: options.deleteOutputPath ?? true, // For app-shell and SSG server files are not required by users. // Omit these when SSR is not enabled. diff --git a/packages/angular/build/src/utils/delete-output-dir.ts b/packages/angular/build/src/utils/delete-output-dir.ts index 45084760793d..cfb19baf8ead 100644 --- a/packages/angular/build/src/utils/delete-output-dir.ts +++ b/packages/angular/build/src/utils/delete-output-dir.ts @@ -7,20 +7,26 @@ */ import { readdir, rm } from 'node:fs/promises'; -import { join, resolve } from 'node:path'; +import { join, resolve, sep } from 'node:path'; /** - * Delete an output directory, but error out if it's the root of the project. + * Delete an output directory, but error out if it's the root of the project or outside it. */ export async function deleteOutputDir( root: string, outputPath: string, emptyOnlyDirectories?: string[], ): Promise { + const resolvedRoot = resolve(root); const resolvedOutputPath = resolve(root, outputPath); - if (resolvedOutputPath === root) { + if (resolvedOutputPath === resolvedRoot) { throw new Error('Output path MUST not be project root directory!'); } + if (!resolvedOutputPath.startsWith(resolvedRoot + sep)) { + throw new Error( + `Output path '${resolvedOutputPath}' MUST be inside the project root '${resolvedRoot}'.`, + ); + } const directoriesToEmpty = emptyOnlyDirectories ? new Set(emptyOnlyDirectories.map((directory) => join(resolvedOutputPath, directory)))