The sudo nightmare Electron bug

Lately I’ve been working in an Electron application to flash iso/img images to drives in an easy way in all major operating systems based on the flashing npm module we developed when working on the Resin CLI tool.

After spending literally all day (~8 hours) debugging a very weird issue, I want to write it down here, since its hilarious.

Given the nature of the Electron application, it requires administration permissions to be able to access raw devices directly. The application checks if the current user has such permissions and if not, prompt the user for the administrative password, and re-run the application. For this purpose we used two modules:

Each which provides such functionality for Windows and macOS/Linux respectively.

We also configured an npm script, called start to run the application. So far so good.

Turns out that when running npm start in macOS, we got the following error:

Command failed: /bin/sh -c sudo -n /Users/jviotti/Projects/resin/etcher/node_modules/electron-prebuilt/dist/Electron.app/Contents/MacOS/Electron lib/etcher.js
env: node\r: No such file or directory

The carriage return symbol leads us to think the problem is caused by a Node.js file containing a hashbang like #!/usr/bin/env node encoded with Windows file endings.

After searching the whole codebase and installed dependencies for files relevant files containing such a hash bang, and running dos2unix on them, the issue persists.

What was even more confusing was that the following command ran without any issues from the terminal, correctly prompting for permissions and without any sign of the mysterious carriage return output.

sudo -n /Users/jviotti/Projects/resin/etcher/node_modules/electron-prebuilt/dist/Electron.app/Contents/MacOS/Electron lib/etcher.js

After trying to reproduce the issue with the smallest possible code surface (removing require blocks and basically simplifying the application to just the code that performs the elevation), there was no sign of progress.

I created a tiny Electron application that reused sudo-prompt with a similar routine than our main application, and that worked fine as well.

The only difference I could spot was the installed dependencies. Not the required ones, but the ones that actually lived in the node_modules directory, which was key to solve the bug.

I started removing modules from node_modules/ which were not used by the slimmed down application, until bam! The issue was gone when removing windosu.

This made sense since it’s a Windows module and we can assume it was created in a Windows environment and contain carriage return line endings.

After doing a small search for /usr/bin/env node, I stumbled into windosu/bin/sudo, a Node.js script for when using the module globally.

I ran dos2unix on it, re-run the application with npm start, and nothing.

$ npm start

> etcher@0.0.1 start /Users/jviotti/Projects/resin/etcher
> electron lib/etcher.js

The app didn’t even run, nor prompt me for my password. But clearly something changed when converting windosu/bin/sudo to UNIX line endings, so for some reason, this script was being called.

By inspecting sudo-prompt code, we can see it runs sudo with the -n flag, which means “non-interactive” as a smart way to check the permissions of the current user.

Turns out that binary files specificed as bin in the package.json get linked to node_modules/.bin when installed locally, and this directory gets added to the PATH when executing an npm script.

Since the binary that windosu exposes is called sudo, it shadowed /usr/bin/sudo, so sudo-prompt was unintentially calling windosu instead of the real sudo, leading to well, nothing good.

The 8 hours worth fix consisted in 6 characters: calling /usr/bin/sudo instead of sudo within sudo-prompt, and all back to normal!