Skip to content

fix(@angular/build): reject outputPath that escapes workspace root#32986

Open
CodeByMoriarty wants to merge 1 commit intoangular:mainfrom
CodeByMoriarty:main
Open

fix(@angular/build): reject outputPath that escapes workspace root#32986
CodeByMoriarty wants to merge 1 commit intoangular:mainfrom
CodeByMoriarty:main

Conversation

@CodeByMoriarty
Copy link
Copy Markdown

@CodeByMoriarty CodeByMoriarty commented Apr 10, 2026

Prevent a malicious angular.json from setting outputPath outside the workspace root (e.g. ".."), which caused the default application builder to recursively delete the workspace parent directory and sibling content before writing build output there.

Current behavior:

normalizeOptions() resolves the user-supplied outputPath (e.g. "..") relative to workspaceRoot without validating that the result stays inside the workspace. deleteOutputDir() only rejects a path that is exactly equal to the workspace root; it accepts any other path, including ancestors or siblings.

A malicious angular.json with build.options.outputPath set to ".." causes a default ng build to:

  1. Resolve the output base to the workspace's parent directory.
  2. Recursively delete every entry under that parent (including the workspace itself and any sibling files/directories) as part of normal pre-build cleanup.
  3. Write browser build artefacts into the parent directory.
  4. Crash with ENOENT when it later tries to read assets from the now-deleted workspace.

a single ng build can destroy the victim workspace and writable sibling content, then write build output into an attacker-chosen parent directory.

New behavior:

Two defence-in-depth guards are added:

  1. Early validation in normalizeOptions() (packages/angular/build/src/builders/application/options.ts)

    After resolving the output base, the function now checks that it is a strict descendant of workspaceRoot. If not, it throws immediately before any filesystem work begins:

    Error: Output path '' must be inside the project root
    directory ''.

  2. Boundary check in deleteOutputDir() (packages/angular/build/src/utils/delete-output-dir.ts)

    The deletion helper now rejects any outputPath whose resolved form is not a strict descendant of the workspace root (previously it only rejected an exact match). This acts as a last-resort guard even if upstream validation is bypassed:

    Error: Output path '' MUST be inside the project root ''.

Breaking change: None.

outputPath values that already point inside the workspace are unaffected. Only values that resolve to a path at or above workspaceRoot are now rejected with a clear error, which was never a supported or intended configuration.

Before the fix the workspace was deleted and the build wrote output into the parent directory. After the fix the build aborts immediately with the new error message and nothing outside the workspace is touched.

Fixes security report output-path-outside-workspace-deletion.

PR Checklist

Please check to confirm your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Copy link
Copy Markdown

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

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 implements validation to ensure the output directory is a strict descendant of the project root, preventing accidental operations on the root directory. A logic error was found in options.ts where the project root is still allowed as an output path, and a fix was suggested to correctly enforce the restriction.

Prevent a malicious angular.json from setting outputPath outside the
workspace root (e.g. ".."), which caused the default application builder
to recursively delete the workspace parent directory and sibling content
before writing build output there.

**Current behavior:**

normalizeOptions() resolves the user-supplied outputPath (e.g. "..") relative
to workspaceRoot without validating that the result stays inside the workspace.
deleteOutputDir() only rejects a path that is exactly equal to the workspace
root; it accepts any other path, including ancestors or siblings.

A malicious angular.json with build.options.outputPath set to ".." causes a
default ng build to:
1. Resolve the output base to the workspace's parent directory.
2. Recursively delete every entry under that parent (including the workspace
   itself and any sibling files/directories) as part of normal pre-build cleanup.
3. Write browser build artefacts into the parent directory.
4. Crash with ENOENT when it later tries to read assets from the now-deleted
   workspace.

Severity: High - a single ng build can destroy the victim workspace and
writable sibling content, then write build output into an attacker-chosen
parent directory.

**New behavior:**

Two defence-in-depth guards are added:

1. Early validation in normalizeOptions()
   (packages/angular/build/src/builders/application/options.ts)

   After resolving the output base, the function now checks that it is a strict
   descendant of workspaceRoot. If not, it throws immediately before any
   filesystem work begins:

     Error: Output path '<resolved>' must be inside the project root
     directory '<workspaceRoot>'.

2. Boundary check in deleteOutputDir()
   (packages/angular/build/src/utils/delete-output-dir.ts)

   The deletion helper now rejects any outputPath whose resolved form is not a
   strict descendant of the workspace root (previously it only rejected an exact
   match). This acts as a last-resort guard even if upstream validation is
   bypassed:

     Error: Output path '<resolved>' MUST be inside the project root '<root>'.

**Breaking change:** None.

outputPath values that already point inside the workspace are unaffected. Only
values that resolve to a path at or above workspaceRoot are now rejected with a
clear error, which was never a supported or intended configuration.

Validated with the PoC in
security-reports/002-output-path-outside-workspace-deletion/. Before the fix
the workspace was deleted and the build wrote output into the parent directory.
After the fix the build aborts immediately with the new error message and
nothing outside the workspace is touched.

Fixes security report 002-output-path-outside-workspace-deletion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant