New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix: Avoid shell mangling during eslint --init #8936
Conversation
LGTM |
Thanks for the PR, this definitely seems like a bug. Does this fix work on Windows (and if it does, does it work on Windows with Node 4)? I recall that it wasn't possible to use an array of arguments with If we can't pass the arguments directly then we could fall back to escaping them to solve the immediate issue.
Agreed. In this case, I don't think the problem is exploitable because crafting malicious input would require the attacker to put something malicious in the package on npm, and if they can do that then they can already run arbitrary code anyway (since we're downloading the package immediately afterwards). But I agree that it would be a good idea to avoid spawning a shell, if it's possible to do so with our supported platforms. |
Argh. It does not work on Windows, and I can’t figure out a reasonable way to make it work on Windows, due to a combination of Windows insanity (Windows programs are launched with a single argument string, not an argument list; script files can only be launched through cmd.exe; and cmd.exe uses its own quoting rules that subtly different from the rest of the system) with Node insanity (you would think that there would be an API where you give it a list of strings and it runs a program in whatever way it has to such that it sees that list of strings, but actually, the APIs that look like they take a list of strings really just blindly smash them together with spaces). The closest thing I can find to a solution is cross-spawn. Can we use that? |
If you ran ‘eslint --init’ and selected the Google style guide, it would end up calling execSync("npm i --save-dev eslint@>=4.1.1"). Because execSync spawns the child through a shell, this had the effect of running ‘npm i --save-dev eslint@’ with its output redirected to a new file named ‘=4.1.1’, leaving the wrong version in package.json. Fix this by spawning processes using the cross-spawn package, which avoids spawning a shell at all on Unix and quotes the arguments appropriately on Windows. Signed-off-by: Anders Kaseorg <andersk@mit.edu>
LGTM |
Here’s the fix using cross-spawn. I’ve tested this version to work on Ubuntu (Node.js 4.8.3) and Windows (Node.js 4.4.7 and Node.js 8.1.4). |
I'm not adamantly opposed to adding a dependency, but I would rather avoid it if there's a simple way to do this without a dependency. Would it work to just use |
@not-an-aardvark It would be entirely reasonable to assume that would work, but it doesn’t. On Windows,
|
I think you're using
without the first argument being in an array. |
As a separate note: (edit: quote removed, issue has been addressed) It's fine to criticize technical decisions that went into developing Node, but please refrain from personally attacking the people who develop Node. (See the "Be respectful" section in our code of conduct.) |
@not-an-aardvark Right, that’s what I meant. I’ve fixed my comment above.
Sorry, I should have channelled my frustration more productively. I have edited out that part of my comment above. |
Thanks, I appreciate it. Do you know if there's a simple workaround (e.g. escaping the arguments) that would solve the issue despite the confusing Node API? My thought process is that this is a very minor part of ESLint that only happens in one place, and it would be nice if we didn't have to include an entire process-spawning module just for this one usage. (We generally try to avoid adding unnecessary dependencies because they increase the install time, among other reasons. But if the alternative would be to add a significant amount of complexity, then I'm fine with it.) |
Depends on what you mean by “simple”. There are different, incompatible quoting rules for Windows (code in cross-spawn, which doesn’t need to quote on Unix) and Unix (code in shell-escape, which doesn’t support Windows). We could implement both of them behind a platform test. Or we could implement just Windows quoting in a wrapper that switches between |
cc'ing @mysticatea since I think we might have run into a similar problem before with |
If possible, I’d recommend just using cross-spawn since they already probably solve most of these problems anyway. It’s a very commonly-depended-upon module so anything using eslint is likely already pulling it in through another dependency. If you implement your own shell escaping by basically copying cross-spawn’s code, then you have code duplication and it will get out of date… |
Thank you for the carbon copy. I think the best solution is However, in this case, I think enough that just enclosing by double quotes like |
For what it's worth, |
I obviously don’t get to decide which solution you accept, but speaking for myself, I’m not going to write a patch that merely concatenates double-quote characters around the argument, because I’d prefer not to be the author of code associated with future bugs and security vulnerabilities when the assumptions change or when it’s reused in a different context. Leaky abstractions are what got us into this mess; let’s avoid building more of them.
|
Ok, you've convinced me that we should just use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks!
(edit: Waiting a day or two before merging this just in case anyone wants to review)
Thanks for contributing! |
What is the purpose of this pull request? (put an "X" next to item)
[X] Bug fix (template)
Tell us about your environment
What did you do? Please include the actual source code causing the issue.
Ran
eslint --init
and selected the Google style guide.What actually happened? Please include the actual, raw output from ESLint. See above.
This ended up calling
execSync("npm i --save-dev eslint>=4.1.1")
. BecauseexecSync
spawns the child through a shell, this had the effect of runningnpm i --save-dev eslint
with its output redirected to a new file named=4.1.1
, leaving the wrong version inpackage.json
.It’s always better to avoid spawning a shell when possible for correctness reasons, to say nothing of scary security reasons.
What changes did you make? (Give an overview) Replaced all uses of
execSync
withexecFileSync
to avoid spawning a shell, and updated the corresponding tests.Is there anything you'd like reviewers to focus on? Nothing in particular.