It seems like every project that doesn’t want to bother supporting a wide array of package managers just gives you a one-liner you can put in your terminal emulator to quickly and easily install it.

# Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Bun
curl -fsSL https://bun.sh/install | bash

# k3s
curl -sfL https://get.k3s.io | sh -

# Oh-My-Zsh
sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

# Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Nix
curl -L https://nixos.org/nix/install -o nix-install.sh && sh ./nix-install.sh

When pointing out the issues with this approach, there are a few common concerns:

  • Copying and pasting commands to your prompt is risky.
  • Side-stepping a Package Manager avoids 3rd-Party Review
  • Patches and updates may not be delivered through a standard release channel.

However, most package managers/repos are no real guarantee of security. There’s not a significant amount of third-party review happening in managers like PyPI, NPM, or the AUR, if any at all.

But they do offer some modicum of standardization, which can provide more confidence than just a random script served from a project’s repo or HTTP server.

  • Standard installer formats
  • Easy Uninstall Paths
  • Kicking out bad-actors
  • Bulk upgrades
  • Dependency resolution

You don’t generally get any of these things with some random install.sh served up over cURL/wget. It’s possible depending on what the the script does (some of them just register their own DEB/RPM repos).

But I also don’t blame projects for offering this install route, nor do I blame users for using them. After all, I also use them and have provided them in projects.

  • They reduce friction, because users don’t need to have the package manager setup.
  • They allow access to the latest stable versions, which sometimes are not available on the distribution’s repos.
  • They can handle multiple install strategies depending on various detected situations directly on the machine.

Because of this, they’re not going away any time soon. But those who want more control and security on their machines still may want to use these carefully. And should be extra careful about that first concern I listed. The inherent danger of copying and pasting commands from the web directly to your prompt.

Don’t Paste Directly To Your Prompt

Pastejacking are a well known collection of strategies for tricking someone to paste malicious code into an execution prompt. Either by manipulating people to paste things they don’t understand into their prompt, or by using CSS or Javascript to deceive users about what it is they are copying into their clipboard before they paste it directly into their prompt.

Ctrl+C: curl -fsSL https://cool-app.com/install.sh | sh -
Ctrl+V: rm -rf --no-preserve-root /\n

Some terminal emulators try to protect you from this by detecting if you’re about to paste content that ends in newlines (effectively pasting and executing without verification), but it can also be very annoying when pasting requires extra confirmation via pop-up dialogs.

Even if you trust the source or site, it doesn’t mean that some hacker couldn’t have compromised the source your browser is rendering and exposed your copy action to pastejacking.

Use Protection

One of the best the means to protect yourself is to use bash’s editor mode. Before pasting any command into your prompt, just engage the editor mode:

  • In set -o emacs (default) mode: Ctrl+x Ctrl+e
  • In set -o vi mode: Esc v

This will open your prompt in your $VISUAL editor (or $EDITOR if $VISUAL is not set). And allow you to edit it like any file before you execute it (saving and exiting will execute any lines in the buffer).

This gives you an opportunity to sanity check what you have just pasted into your terminal emulator before it executes. Even if it ends in a newline (which, on the bare prompt, would automatically execute it).

This method is widely available and even gives you the opportunity to make any modifications you might want to the command(s) you’re copying.

Wrap Your Pipes

But all the above will do is ensure that an untrustworthy or compromised site isn’t pastejacking you with malicious code.

An untrustworthy or compromised site can also just serve up malicious code in the install script you’re about to have cURL/wget stream into your shell to execute. So even though what you’re pasting is clean, what you’re about to download and run on your machine might not be. This is especially risky if you have passwordless sudo enabled for your user, as privilege escalation can happen inside the script unattended.

And sometimes even trustworthy sites might do some very invasive things inside their installer scripts that you just don’t like. The problem with install.sh is also that there is no standardization for how they behave and what they do. That’s not to say you get guarantees via packages or package repositories either, but some guardrails or community supervision is better than none.

So for these reasons, I recommend keeping a condom in your shell’s dotfiles:

condom() {
	tmp=$(mktemp) || exit 1
	trap 'rm -f "$tmp"' EXIT

	cat > "$tmp"

	if [ -n "${VISUAL:-}" ]
	then
		"$VISUAL" "$tmp" </dev/tty >/dev/tty
	elif [ -n "${EDITOR:-}" ]
	then
		"$EDITOR" "$tmp" </dev/tty >/dev/tty
	elif command -v vim >/dev/null
	then
		vim "$tmp" </dev/tty >/dev/tty
	elif command -v vi >/dev/null
	then
		vi "$tmp" </dev/tty >/dev/tty
	elif [ -n "${PAGER:-}" ]
	then
		"$PAGER" "$tmp" </dev/tty >/dev/tty
	elif command -v less >/dev/null
	then
		less "$tmp" </dev/tty >/dev/tty
	elif command -v more >/dev/null
	then
		more "$tmp" </dev/tty >/dev/tty
	else
		cat "$tmp" >&2
	fi

	printf '\nExecute? [y/N] ' >/dev/tty
	read -r resp </dev/tty

	case $resp in
		[Yy]*)
			cat "$tmp"
			;;
		*)
			exit 1
			;;
	esac
}

The above POSIX shell function can be used in any Unix command pipe to give you a chance to sanity-check what that one-liner is about to execute on your machine. When placed between the command streaming the script to STDOUT and your executing shell invocation, it will show you what the script does and, if it can find an editor to open it in, give you an opportunity to make any adjustments to it you might want to beforehand.

Condom Usage

Just let the condom provide a barrier of security between the script and your shell by inserting it before the script content reaches the execution command:

# Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | condom | sh

# Bun
curl -fsSL https://bun.sh/install | condom | bash

# k3s
curl -sfL https://get.k3s.io | condom | sh -

# Oh-My-Zsh
sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh | condom)"

# Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | condom)"

# Nix
# For some reason their one-liner writes to a file and then immediately reads it
# off disk. This give you nothing beyond forensics of what you executed, but
# doesn't let you verify what you're about to execute before you do. So just
# convert it to a pipeline before putting on the condom.
curl -fsSL https://nixos.org/nix/install | condom | sh -

Use Your Head

None of the recommendations in this post are foolproof. And there are a myriad number of ways that even official package managers and repositories can do malicious things to your computer (like when Canonical secretly makes apt install x actually set up and use snap to install a program, which I’m referring to as malicious only partly in jest). But copy-pasting install one-liners has some risks all their own (though let’s not pretend people don’t copy and paste apt or dnf install commands from the internet either).

Ultimately you have to use wise judgement before executing anything on your computer, and it’s just one of the responsibilities that comes with being computationally active. Hopefully the two recommendations made above will help you protect yourself and others just a little bit more.