What is a curl | sh
installer?
Many software projects provide a shell script to automate installation. In order to make it look simple, they’ll suggest downloading that installation script using curl or wget and immediately pipe it to a shell. For example, to install Rust they suggest you run the following in your favorite terminal:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
I call these “curl pipe to shell” (curl | sh
) installers. Lots of projects
use this pattern. Here’s a list of software that I personally use that include
curl | sh
as an installation option:
What exactly are these scripts doing? Running curl | $EDITOR -
can get
you started answering that question. Sometimes the scripts can be complex with
many different code paths. I’ve seen installers which download additional
scripts which are executed in turn. Most alarming of all is if you’re asked to
provide your password for root access through sudo
(I’m looking at you IBM
Cloud CLI 🤨) !
In addition to inspecting the script itself, I like to inspect the results of actually running the script. Rootless containers are perfect for this! Podman is my preferred tool, but Docker is also able to run rootless these days.
Choose a base, install then diff
Let’s use Starship as an example and go through the steps I’d use to inspect
the results of its curl | sh
installer.
Choose a base image
I’m running Debian 12 (bookworm) right now, so we’ll start with the docker.io/debian:bookworm image. This image does not include curl, though, so we’ll need to build on top of it. Here’s a simple Containerfile:
FROM docker.io/debian:bookworm RUN apt-get update RUN apt-get install -y curl
Building this will create our start image.
podman build -t debian-curl:bookwork .
Run the base image and open an interactive TTY
Giving it a name is always helpful but not required.
podman run -it --name starship-install debian-curl:bookworm
From inside the container, run the
curl | sh
installerAccording to their documentation, the following
curl | sh
is provided to install the latest version of Starship in Linux:curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Follow the interactive prompts, remembering the selections you make. It’s important to remember this procedure only tests a single code path!
When the installer finishes,
exit
out of the container.Diff the before and after images
podman diff starship-installer
The output for me is
C /etc C /tmp A /tmp/tmp.40X4dFTsTy A /tmp/tmp.40X4dFTsTy.tar.gz C /usr C /usr/local C /usr/local/bin A /usr/local/bin/starship C /root A /root/.bash_history
The podman-diff documentation provides the following table:
Symbol Description A A file or directory was added. D A file or directory was deleted. C A file or directory was changed. So the
podman diff
shows a compressed archive in/tmp
, thestarship
binary in/usr/local/bin/
and a.bash_history
file.From here, you can go back into the container and inspect further if you wish.
podman start -a starship-installer
Cleanup
When you’re finished, don’t forget to remove the container.
podman rm starship-installer
Caveats
This is meant to compliment inspecting the script itself, not replace it!
This tests only a single code path! Differences between your environment and the container you can result in different outcomes.
Intermediate states are overlooked. It’s possible that the installer could do unwanted things 😧, then clean up after itself and end in a state that looks OK in or final diff.
Other things I haven’t thought of 🙃.
Post history
Date | Description |
---|---|
2024-02-29 | Initial publish |