diff --git a/docs/documentation/stories/asset-configuration.md b/docs/documentation/stories/asset-configuration.md index 0f1a2a170053..ef910b1d8561 100644 --- a/docs/documentation/stories/asset-configuration.md +++ b/docs/documentation/stories/asset-configuration.md @@ -36,4 +36,27 @@ The array below does the same as the default one: ] ``` -The contents of `node_modules/some-package/images/` will be available in `dist/some-package/`. \ No newline at end of file +The contents of `node_modules/some-package/images/` will be available in `dist/some-package/`. + +## Writing assets outside of `dist/` + +Because of the security implications, the CLI will always refuse to read or write files outside of +the project itself (scoped by `.angular-cli.json`). It is however possible to write assets outside +the `dist/` build output folder during build. + +Because writing files in your project isn't an expected effect of `ng build`, it is disabled by +default on every assets. In order to allow this behaviour, you need to set `allowOutsideOutDir` +to `true` on your asset definition, like so: + +```json +"assets": [ + { + "glob": "**/*", + "input": "./assets/", + "output": "../not-dist/some/folder/", + "allowOutsideOutDir": true + }, +] +``` + +This needs to be set for every assets you want to write outside of your build output directory. diff --git a/packages/@angular/cli/lib/config/schema.json b/packages/@angular/cli/lib/config/schema.json index 3ba988cb8f08..8520dcc1c9b9 100644 --- a/packages/@angular/cli/lib/config/schema.json +++ b/packages/@angular/cli/lib/config/schema.json @@ -72,6 +72,11 @@ "type": "string", "default": "", "description": "The output path (relative to the outDir)." + }, + "allowOutsideOutDir": { + "type": "boolean", + "description": "Allow assets to be copied outside the outDir.", + "default": false } }, "additionalProperties": false diff --git a/packages/@angular/cli/models/webpack-configs/common.ts b/packages/@angular/cli/models/webpack-configs/common.ts index 8c2111b25516..a02b82448242 100644 --- a/packages/@angular/cli/models/webpack-configs/common.ts +++ b/packages/@angular/cli/models/webpack-configs/common.ts @@ -95,10 +95,26 @@ export function getCommonConfig(wco: WebpackConfigOptions) { asset.output = asset.output || ''; asset.glob = asset.glob || ''; - // Prevent asset configurations from writing outside of the output path + // Prevent asset configurations from writing outside of the output path, except if the user + // specify a configuration flag. + // Also prevent writing outside the project path. That is not overridable. const fullOutputPath = path.resolve(buildOptions.outputPath, asset.output); + if (!fullOutputPath.startsWith(projectRoot)) { + const message = 'An asset cannot be written to a location outside the project.'; + throw new SilentError(message); + } if (!fullOutputPath.startsWith(path.resolve(buildOptions.outputPath))) { - const message = 'An asset cannot be written to a location outside of the output path.'; + if (!asset.allowOutsideOutDir) { + const message = 'An asset cannot be written to a location outside of the output path. ' + + 'You can override this message by setting the `allowOutsideOutDir` ' + + 'property on the asset to true in the CLI configuration.'; + throw new SilentError(message); + } + } + + // Prevent asset configurations from reading files outside of the project. + if (!asset.input.startsWith(projectRoot)) { + const message = 'An asset cannot be read from a location outside the project.'; throw new SilentError(message); } diff --git a/packages/@angular/cli/models/webpack-configs/utils.ts b/packages/@angular/cli/models/webpack-configs/utils.ts index 4eee09a29e78..8679dc5a81cf 100644 --- a/packages/@angular/cli/models/webpack-configs/utils.ts +++ b/packages/@angular/cli/models/webpack-configs/utils.ts @@ -92,4 +92,5 @@ export interface AssetPattern { glob: string; input?: string; output?: string; + allowOutsideOutDir?: boolean; } diff --git a/tests/e2e/tests/build/assets.ts b/tests/e2e/tests/build/assets.ts index 20f32e1dbf3f..9b7e91941339 100644 --- a/tests/e2e/tests/build/assets.ts +++ b/tests/e2e/tests/build/assets.ts @@ -30,10 +30,40 @@ export default function () { .then(() => updateJsonFile('.angular-cli.json', configJson => { const app = configJson['apps'][0]; app['assets'] = [ - { 'glob': '**/*', 'input': '../node_modules/some-package/', 'output': '../package-folder' } + { 'glob': '**/*', 'input': '../node_modules/some-package/', 'output': '../temp' } ]; })) .then(() => expectToFail(() => ng('build'))) + + // Set an exception for the invalid asset config in .angular-cli.json. + .then(() => updateJsonFile('.angular-cli.json', configJson => { + const app = configJson['apps'][0]; + app['assets'] = [ + { 'glob': '**/*', 'input': '../node_modules/some-package/', 'output': '../temp', + 'allowOutsideOutDir': true } + ]; + })) + .then(() => ng('build')) + + // This asset should fail even with the exception above. + .then(() => updateJsonFile('.angular-cli.json', configJson => { + const app = configJson['apps'][0]; + app['assets'] = [ + { 'glob': '**/*', 'input': '../node_modules/some-package/', 'output': '../../temp', + 'allowOutsideOutDir': true } + ]; + })) + .then(() => expectToFail(() => ng('build'))) + + // This asset should also fail from reading from outside the project. + .then(() => updateJsonFile('.angular-cli.json', configJson => { + const app = configJson['apps'][0]; + app['assets'] = [ + { 'glob': '**/*', 'input': '/temp-folder/outside/of/project', 'output': 'temp' } + ]; + })) + .then(() => expectToFail(() => ng('build'))) + // Add asset config in .angular-cli.json. .then(() => updateJsonFile('.angular-cli.json', configJson => { const app = configJson['apps'][0];