From fcc0132f2c8e853cb228e5e89fc55b3fe8e399cb Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Fri, 5 Jun 2026 00:12:01 +0000 Subject: [PATCH] fix(ng-dev): prevent arbitrary code execution via HTML injection --- ng-dev/release/notes/context.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/ng-dev/release/notes/context.ts b/ng-dev/release/notes/context.ts index 4da161211d..44ad38546b 100644 --- a/ng-dev/release/notes/context.ts +++ b/ng-dev/release/notes/context.ts @@ -12,6 +12,16 @@ import {GithubConfig} from '../../utils/config.js'; import {ReleaseNotesConfig} from '../config/index.js'; import {compareString} from '../../utils/locale.js'; +/** Escapes HTML special characters in a string. */ +function escapeHtml(str: string): string { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + /** List of types to be included in the release notes. */ const typesToIncludeInReleaseNotes = Object.values(COMMIT_TYPES) .filter((type) => type.releaseNotesLevel === ReleaseNotesLevel.Visible) @@ -58,10 +68,21 @@ export class RenderContext { _categorizeCommits(commits: CommitFromGitLog[]): CategorizedCommit[] { return commits.map((commit) => { const {description, groupName} = this.data.categorizeCommit?.(commit) ?? {}; + const escapedBreakingChanges = commit.breakingChanges.map((bc) => ({ + ...bc, + text: escapeHtml(bc.text), + })); + const escapedDeprecations = commit.deprecations.map((dep) => ({ + ...dep, + text: escapeHtml(dep.text), + })); return { - groupName: groupName ?? commit.scope, - description: description ?? commit.subject, ...commit, + type: escapeHtml(commit.type), + groupName: escapeHtml(groupName ?? commit.scope), + description: escapeHtml(description ?? commit.subject), + breakingChanges: escapedBreakingChanges, + deprecations: escapedDeprecations, }; }); }