From 7c92fa8122b60b0357459fc705812d3598039472 Mon Sep 17 00:00:00 2001 From: Aaron Shim Date: Thu, 17 Oct 2024 17:39:58 +0000 Subject: [PATCH] Options get plumbed through to the builder. --- .../build/src/builders/application/options.ts | 2 ++ .../src/builders/application/schema.json | 27 +++++++++++++++++++ .../src/tools/esbuild/index-html-generator.ts | 11 +++++++- .../build/src/utils/index-file/auto-csp.ts | 8 ++++-- .../utils/index-file/index-html-generator.ts | 12 ++++++--- 5 files changed, 53 insertions(+), 7 deletions(-) diff --git a/packages/angular/build/src/builders/application/options.ts b/packages/angular/build/src/builders/application/options.ts index a454e88e375c..79c0052af18e 100644 --- a/packages/angular/build/src/builders/application/options.ts +++ b/packages/angular/build/src/builders/application/options.ts @@ -398,6 +398,7 @@ export async function normalizeOptions( partialSSRBuild = false, externalRuntimeStyles, instrumentForCoverage, + security, } = options; // Return all the normalized options @@ -461,6 +462,7 @@ export async function normalizeOptions( partialSSRBuild: usePartialSsrBuild || partialSSRBuild, externalRuntimeStyles, instrumentForCoverage, + security, }; } diff --git a/packages/angular/build/src/builders/application/schema.json b/packages/angular/build/src/builders/application/schema.json index 60e725568eb0..68810a78df6f 100644 --- a/packages/angular/build/src/builders/application/schema.json +++ b/packages/angular/build/src/builders/application/schema.json @@ -37,6 +37,33 @@ "type": "string", "description": "Customize the base path for the URLs of resources in 'index.html' and component stylesheets. This option is only necessary for specific deployment scenarios, such as with Angular Elements or when utilizing different CDN locations." }, + "security": { + "description": "Security features to protect against XSS and other common attacks", + "type": "object", + "additionalProperties": false, + "properties": { + "autoCsp": { + "description": "Enables auto-CSP generation. Will default to true once we are out of experimental/preview phases.", + "default": false, + "oneOf": [ + { + "type": "object", + "properties": { + "unsafeEval": { + "type": "boolean", + "description": "Include the `unsafe-eval` directive in the auto-CSP. Please only enable this if you are absolutely sure that you need to, as allowing calls to eval will weaken the XSS defenses provided by the auto-CSP.", + "default": false + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + } + } + }, "scripts": { "description": "Global scripts to be included in the build.", "type": "array", diff --git a/packages/angular/build/src/tools/esbuild/index-html-generator.ts b/packages/angular/build/src/tools/esbuild/index-html-generator.ts index 53f1270e48f0..4d11ed4fa45a 100644 --- a/packages/angular/build/src/tools/esbuild/index-html-generator.ts +++ b/packages/angular/build/src/tools/esbuild/index-html-generator.ts @@ -80,6 +80,15 @@ export async function generateIndexHtml( throw new Error(`Output file does not exist: ${relativefilePath}`); }; + // Read the Auto CSP options. + const autoCsp = buildOptions.security?.autoCsp; + const autoCspOptions = + autoCsp === true + ? { unsafeEval: false } + : autoCsp + ? { unsafeEval: !!autoCsp.unsafeEval } + : undefined; + // Create an index HTML generator that reads from the in-memory output files const indexHtmlGenerator = new IndexHtmlGenerator({ indexPath: indexHtmlOptions.input, @@ -94,7 +103,7 @@ export async function generateIndexHtml( buildOptions.prerenderOptions || buildOptions.appShellOptions ), - autoCsp: true, + autoCsp: autoCspOptions, }); indexHtmlGenerator.readAsset = readAsset; diff --git a/packages/angular/build/src/utils/index-file/auto-csp.ts b/packages/angular/build/src/utils/index-file/auto-csp.ts index 5437a6c7a2e6..180f0bada12c 100644 --- a/packages/angular/build/src/utils/index-file/auto-csp.ts +++ b/packages/angular/build/src/utils/index-file/auto-csp.ts @@ -104,7 +104,7 @@ export function hashTextContent(scriptText: string): string { * @param html Markup that should be processed. * @returns The transformed HTML that contains the `` tag CSP and dynamic loader scripts. */ -export async function autoCsp(html: string): Promise { +export async function autoCsp(html: string, unsafeEval = false): Promise { const { rewriter, transformedContent } = await htmlRewritingStream(html); let openedScriptTag: StartTag | undefined = undefined; @@ -190,7 +190,11 @@ export async function autoCsp(html: string): Promise { if (tag.tagName === 'head') { // See what hashes we came up with! secondPass.rewriter.emitRaw( - ``, + ``, ); } }); diff --git a/packages/angular/build/src/utils/index-file/index-html-generator.ts b/packages/angular/build/src/utils/index-file/index-html-generator.ts index ec09fb39bbd7..b88074deee12 100644 --- a/packages/angular/build/src/utils/index-file/index-html-generator.ts +++ b/packages/angular/build/src/utils/index-file/index-html-generator.ts @@ -33,6 +33,10 @@ export interface IndexHtmlGeneratorProcessOptions { hints?: { url: string; mode: HintMode; as?: string }[]; } +export interface AutoCspOptions { + unsafeEval: boolean; +} + export interface IndexHtmlGeneratorOptions { indexPath: string; deployUrl?: string; @@ -44,7 +48,7 @@ export interface IndexHtmlGeneratorOptions { cache?: NormalizedCachedOptions; imageDomains?: string[]; generateDedicatedSSRContent?: boolean; - autoCsp?: boolean; + autoCsp?: AutoCspOptions; } export type IndexHtmlTransform = (content: string) => Promise; @@ -91,7 +95,7 @@ export class IndexHtmlGenerator { // Auto-CSP (as the last step) if (options.autoCsp) { - this.csrPlugins.push(autoCspPlugin()); + this.csrPlugins.push(autoCspPlugin(options.autoCsp.unsafeEval)); } } @@ -205,8 +209,8 @@ function addNoncePlugin(): IndexHtmlGeneratorPlugin { return (html) => addNonce(html); } -function autoCspPlugin(): IndexHtmlGeneratorPlugin { - return (html) => autoCsp(html); +function autoCspPlugin(unsafeEval: boolean): IndexHtmlGeneratorPlugin { + return (html) => autoCsp(html, unsafeEval); } function postTransformPlugin({ options }: IndexHtmlGenerator): IndexHtmlGeneratorPlugin {