fix(@angular/cli): resolve executables strictly from PATH - #33758
fix(@angular/cli): resolve executables strictly from PATH#33758alan-agius4 wants to merge 2 commits into
Conversation
12b5aa7 to
62cbca6
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a utility function findExecutableOnPath to safely resolve executables from the PATH environment variable, preventing implicit resolution from the current working directory on Windows. It integrates this utility when executing git and which. The review feedback highlights critical security and correctness improvements: avoiding fallback to bare command names ('git' and 'which') when they are not found on the PATH to prevent command injection risks, and stripping double quotes from PATH directory entries on Windows to correctly handle paths with spaces.
62cbca6 to
e60ed21
Compare
Update executable invocation logic to resolve system binaries (such as `git` and `which`) strictly from the `PATH` environment variable. This prevents bare command names passed to `execFileSync` / `execFile` from implicitly searching and resolving binaries relative to `process.cwd()` on Windows. Fixes angular#33755
e60ed21 to
7d94382
Compare
|
Thanks for turning this around so fast, and for picking up One small edge case on
A
One line covers it: const dir = rawDir.startsWith('"') && rawDir.endsWith('"') ? rawDir.slice(1, -1) : rawDir;
if (!isAbsolute(dir)) {
continue;
}or Also agree with your framing on the issue itself: a workspace you have chosen to build in already executes project code through lifecycle scripts, builders and schematics, so this is defense in depth rather than a boundary. Thanks for treating it that way. |
|
Good call! Updated |
|
Other tools don't do this and it seems quite complex with the mix of PATH/PATHEXT and multiple system calls. Are we sure this is a viable path forward? This can also break legitimate use cases like custom git shims or developer/project specific wrappers. If we were to do this, would it be better to try setting the |
|
@clydin I think you're right, and it's checkable, so I measured it rather than argue. Windows 10.0.26200, Node v24.17.0, a plant Setting the variable from inside the running process works — it does not need to be inherited: Negative control, same runs against a directory with no plant: canary never fires and the real git answers, so the Three things that follow, and one of them is a foot-gun:
On your "custom git shims" concern — that cuts against this approach too, and more bluntly: the env var is process-global, so it disables working-directory resolution for every child the CLI spawns, whereas Happy to share the probe scripts if useful. |
|
In that case, I would propose adding something similar to the following near the top of https://github.com/angular/angular-cli/blob/04888ea72136426672cf002327c1d413cbdda71a/packages/angular/cli/bin/ng.js The As a separate fix, it may be useful to replace the direct execution of the |
|
Measured both halves of that proposal on Windows 10.0.26200 / Node v24.17.0, same plant harness as before (a The Even with And the hatch has inverted semantics for the two values a user is most likely to reach for:
Worth noting the two variables in that snippet are inverted in opposite directions: Given the No opinion on the |
dgp1130
left a comment
There was a problem hiding this comment.
This can also break legitimate use cases like custom git shims or developer/project specific wrappers.
+1, whether or not we agree with Windows decision to execute binaries in the CWD, that is the decision Microsoft made and actively preventing it creates compatibility problems, either for node_modules/ in our transitive dependencies or user projects which might rely on the feature. This strikes me as a request to harden Windows rather than Angular.
That said, I can agree defense in depth is generally helpful and I agree this feature is bad for security, so I don't object to mitigating the impact here and will defer to you @alan-agius4, just sharing my own perspective.
| const pathExt = process.env.PATHEXT | ||
| ? process.env.PATHEXT.split(delimiter) | ||
| : ['.com', '.exe', '.bat', '.cmd']; |
| * @returns The absolute path to the binary if found on `PATH`, or `undefined`. | ||
| */ | ||
| export function findExecutableOnPath(binaryName: string): string | undefined { | ||
| const envPath = process.env.PATH || process.env.Path || ''; |
There was a problem hiding this comment.
Question: Is there a specific platform which uses ${Path} as distinct from ${PATH}? I've never seen that before.
| * @returns The absolute path to the binary if found on `PATH`, or `undefined`. | ||
| */ | ||
| export function findExecutableOnPath(binaryName: string): string | undefined { | ||
| const envPath = process.env.PATH || process.env.Path || ''; |
| continue; | ||
| } | ||
|
|
||
| const dir = rawDir.startsWith('"') && rawDir.endsWith('"') ? rawDir.slice(1, -1) : rawDir; |
There was a problem hiding this comment.
Question: Do we have to deal with escaping? The fact that we need to parse quotes makes me generally uncomfortable that there's more hidden complexity here.
Update executable invocation logic to resolve system binaries (such as
gitandwhich) strictly from thePATHenvironment variable.This prevents bare command names passed to
execFileSync/execFilefrom implicitly searching and resolving binaries relative toprocess.cwd()on Windows.Fixes #33755