From 7e17452a07d96408466d3fe319497db076af367c Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 19 Oct 2017 22:25:33 -0700 Subject: [PATCH 1/3] fix(@angular/cli): add a flag to let assets outside of outDir On top of #7778 Fixes #8122 --- packages/@angular/cli/lib/config/schema.json | 5 ++++ .../cli/models/webpack-configs/common.ts | 16 ++++++++++--- .../cli/models/webpack-configs/utils.ts | 1 + tests/e2e/tests/build/assets.ts | 23 ++++++++++++++++++- 4 files changed, 41 insertions(+), 4 deletions(-) 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..16bd9a49a0ef 100644 --- a/packages/@angular/cli/models/webpack-configs/common.ts +++ b/packages/@angular/cli/models/webpack-configs/common.ts @@ -95,12 +95,22 @@ 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(path.resolve(buildOptions.outputPath))) { - const message = 'An asset cannot be written to a location outside of the output path.'; + 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))) { + 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); + } + } // Ensure trailing slash. if (isDirectory(path.resolve(asset.input))) { 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..f553b7c1d7f7 100644 --- a/tests/e2e/tests/build/assets.ts +++ b/tests/e2e/tests/build/assets.ts @@ -30,10 +30,31 @@ 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'))) + // Add asset config in .angular-cli.json. .then(() => updateJsonFile('.angular-cli.json', configJson => { const app = configJson['apps'][0]; From 88947a3a10ebfd1d183ef04476e787cedb5067e4 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 16 Nov 2017 12:06:24 -0800 Subject: [PATCH 2/3] fix(@angular/cli): prevents using assets from outside the project This is a security risk. Think reading things from the home directory. --- packages/@angular/cli/models/webpack-configs/common.ts | 6 ++++++ tests/e2e/tests/build/assets.ts | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/packages/@angular/cli/models/webpack-configs/common.ts b/packages/@angular/cli/models/webpack-configs/common.ts index 16bd9a49a0ef..a02b82448242 100644 --- a/packages/@angular/cli/models/webpack-configs/common.ts +++ b/packages/@angular/cli/models/webpack-configs/common.ts @@ -112,6 +112,12 @@ export function getCommonConfig(wco: WebpackConfigOptions) { } } + // 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); + } + // Ensure trailing slash. if (isDirectory(path.resolve(asset.input))) { asset.input += '/'; diff --git a/tests/e2e/tests/build/assets.ts b/tests/e2e/tests/build/assets.ts index f553b7c1d7f7..9b7e91941339 100644 --- a/tests/e2e/tests/build/assets.ts +++ b/tests/e2e/tests/build/assets.ts @@ -55,6 +55,15 @@ export default function () { })) .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]; From 156b7198a6df4aebfe76b2eb7996aa8bbb1953f2 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 16 Nov 2017 12:20:40 -0800 Subject: [PATCH 3/3] docs: update asset story for the new allowOutsideOutDir flag --- .../stories/asset-configuration.md | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) 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.