fix(@angular/build): reject outputPath that escapes workspace root#32986
Open
CodeByMoriarty wants to merge 1 commit intoangular:mainfrom
Open
fix(@angular/build): reject outputPath that escapes workspace root#32986CodeByMoriarty wants to merge 1 commit intoangular:mainfrom
CodeByMoriarty wants to merge 1 commit intoangular:mainfrom
Conversation
There was a problem hiding this comment.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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:
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 ''.
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?
What is the current behavior?
Issue Number: N/A
What is the new behavior?
Does this PR introduce a breaking change?
Other information