From 52f3d99d04899c584710a94f1b96329c04864cc6 Mon Sep 17 00:00:00 2001 From: Herdiyan Adam Putra Date: Sat, 27 Jun 2026 15:30:59 +0700 Subject: [PATCH] fix(@angular-devkit/build-angular): prevent OS command injection in ssr-dev-server builder spawnAsObservable() was joining command and args into a single string before passing to spawn(). In startNodeServer(), outputPath from angular.json was embedded in args with manual shell quoting and shell: true. Bash evaluates $() inside double-quoted strings, so a crafted outputPath value in angular.json can trigger arbitrary command execution on ng serve. Fix: pass command and args separately to spawn() so Node.js uses execve() directly. Remove the manual quoting around path and drop shell: true. --- .../build_angular/src/builders/ssr-dev-server/index.ts | 4 ++-- .../build_angular/src/builders/ssr-dev-server/utils.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/index.ts b/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/index.ts index 445252ff158d..003eb693bb56 100644 --- a/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/index.ts +++ b/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/index.ts @@ -236,14 +236,14 @@ function startNodeServer( const path = join(outputPath, 'main.js'); const env = { ...process.env, PORT: '' + port, NG_ALLOWED_HOSTS: host ?? 'localhost' }; - const args = ['--enable-source-maps', `"${path}"`]; + const args = ['--enable-source-maps', path]; if (inspectMode) { args.unshift('--inspect-brk'); } return of(null).pipe( delay(0), // Avoid EADDRINUSE error since it will cause the kill event to be finish. - switchMap(() => spawnAsObservable('node', args, { env, shell: true })), + switchMap(() => spawnAsObservable(process.execPath, args, { env })), tap((res) => log({ stderr: res.stderr, stdout: res.stdout }, logger)), ignoreElements(), // Emit a signal after the process has been started diff --git a/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/utils.ts b/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/utils.ts index 059c0e0a89e9..7ed821d07950 100644 --- a/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/utils.ts +++ b/packages/angular_devkit/build_angular/src/builders/ssr-dev-server/utils.ts @@ -29,7 +29,7 @@ export function spawnAsObservable( options: SpawnOptions = {}, ): Observable<{ stdout?: string; stderr?: string }> { return new Observable((obs) => { - const proc = spawn(`${command} ${args.join(' ')}`, options); + const proc = spawn(command, args, options); if (proc.stdout) { proc.stdout.on('data', (data) => obs.next({ stdout: data.toString() })); }