Making the curl-Bash Installer Pattern Safer
AI generated
$_
#!/
Bash · Security · Supply Chain · Installer
Making the curl-Bash Installer Pattern Safer
Separate download, checksum and execution instead of blindly running whatever the server sends

curl -fsSL https://install.example.com | bash installs in a single line, but runs code with the calling user's full privileges before anyone has actually looked at it. Understanding exactly what risks this pattern brings makes it possible to substantially harden it with a few extra steps, without giving up all the convenience for end users.

15 min read curl | bash · Checksum · GPG Bash · Security · Supply Chain

1. Why the curl-Bash pattern is so widespread

The pattern curl -fsSL https://install.example.com | bash is the suggested installation method for countless developer tools, because from the provider's point of view it is about as simple as it gets: a single line, no package manager repository to maintain, no platform specific packages, and immediately runnable on practically any system with Bash and curl. For the provider that substantially lowers the entry barrier and reduces support effort across different operating systems.

That exact simplicity is also at the core of the problem: the user executes code with their own account's privileges that they have neither seen nor verified at the moment of execution, implicitly trusting that the server delivers exactly what the documentation promises, that the connection was not tampered with, and that the server itself has not been compromised.

2. Risk 1: no checksum, no proof of the actual content

With classic curl | bash, there is no mechanism at all confirming that the received code actually matches what the provider intended to publish. If the connection to the server is not consistently secured with TLS, an attacker somewhere on the network path, for example on an open WiFi network or through a compromised proxy, can swap the content of the response unnoticed and inject arbitrary code that then runs with the calling user's privileges.

But even with a clean TLS connection, a server side risk remains: if the installer endpoint itself is compromised, for example through a stolen deployment credential or a CDN vulnerability, the server delivers malicious code over a perfectly legitimate, encrypted connection, something a pure TLS check could never detect. A cryptographic checksum, verified against an independently published value, is the only mechanism that covers both cases.


# The risky classic: no verification of any kind before execution,
# and if the download stalls mid-stream, bash may already be
# executing the first, incomplete part of the script.
curl -fsSL https://install.example.com | bash

3. Risk 2: TOCTOU and server side variable responses

A more subtle weakness is a TOCTOU problem (time-of-check to time-of-use): even if a user checks the installer code in a browser beforehand, that does not guarantee the exact same content actually arrives at the moment of the real call. Between the check and the execution, the server can change the content, whether through a regular update, a compromised deployment process, or a deliberately different response for different requests.

Some installer servers also deliver different code depending on the User-Agent header, the IP address, or other request characteristics, for example to automatically select operating system specific installation steps. That is not a security problem by itself, but it makes any manual pre-check of the script in a browser worthless, because the content actually delivered via curl can differ from what was shown in the browser.

4. Risk 3: execution starts before the download is complete

Bash reads input from a pipe line by line, or block by block, not only after receiving it fully. If the network connection drops in the middle of the transfer, Bash may already have executed part of the script while the rest is missing, resulting in an inconsistent, partially executed state that is harder to diagnose than a clean, complete failure. A well written installer script typically wraps itself in a single outer function that is only invoked at the very end of the script to defuse exactly this problem, but that cannot be relied on with every provider.

This behavior differs fundamentally from an approach where the complete script is first saved locally as a file and only then executed: there either the complete, intact file exists, or the download has visibly failed, there is no intermediate state with partially executed code.

5. Making it safer: separating download, checksum and execution

The single most important step toward a safer installer pattern is running the three phases, download, verification and execution, as explicit, separate commands instead of merging them into a single pipe. The script is first downloaded completely as a file, then compared against an independently published cryptographic checksum, and only executed after that verification succeeds.

What matters is that the checksum itself does not come from the same source as the script, if that source could be compromised, but is ideally published through a second, independent channel, for example in the release notes of a Git repository or in a signed announcement, so an attacker who only controls the download server cannot also automatically forge the published checksum.


#!/usr/bin/env bash
set -euo pipefail

INSTALLER_URL="https://install.example.com/install.sh"
EXPECTED_SHA256="a3f5c9e1b8d2..."  # published independently, e.g. in release notes
TMP_SCRIPT="$(mktemp)"
trap 'rm -f "$TMP_SCRIPT"' EXIT

# Step 1: download only, never execute directly from the pipe
curl -fsSL -o "$TMP_SCRIPT" "$INSTALLER_URL"

# Step 2: verify against an independently published checksum
actual_sha256="$(sha256sum "$TMP_SCRIPT" | cut -d' ' -f1)"
if [[ "$actual_sha256" != "$EXPECTED_SHA256" ]]; then
  echo "Checksum mismatch! Refusing to execute." >&2
  exit 1
fi

# Step 3: only now, after successful verification, actually run it
bash "$TMP_SCRIPT"

6. GPG signatures as an additional layer of protection

A plain checksum only confirms that the downloaded file matches a previously seen version, it says nothing about whether that version actually came from the claimed provider. A GPG signature goes a step further: the provider signs the script with their private key, and the user verifies the signature against the public key they already obtained through a trustworthy channel beforehand, for example a keyserver or the official project website.

That additionally protects against the case where an attacker swaps both the script and its accompanying checksum on the same compromised server, because without access to the provider's private signing key the attacker cannot produce a valid signature for their tampered script, even while controlling the entire server infrastructure.


#!/usr/bin/env bash
set -euo pipefail

curl -fsSL -o install.sh https://install.example.com/install.sh
curl -fsSL -o install.sh.sig https://install.example.com/install.sh.sig

# The public key must already be trusted locally, e.g. imported once
# from the project's official website, not fetched fresh every time
gpg --verify install.sh.sig install.sh

bash install.sh

7. Running with restricted privileges and reviewing beforehand

Even after a successful checksum or signature check, it remains worthwhile not to run an unfamiliar installer script with full root privileges by default when that is not strictly necessary. Many installers request sudo even though they really only create files in the user's home directory, so a quick look through the script to see which commands actually need elevated privileges is worth it before running it with sudo as a matter of routine.

For extra safety, an unfamiliar installer script can first be tested in an isolated container or a virtual machine before it runs on a production system. That way, potential damage, for example accidentally installed extra software or unwanted network connections, stays confined to the isolated environment and can be observed before the actual production use.


#!/usr/bin/env bash
set -euo pipefail

# Dry run in an isolated, throwaway container before trusting the
# installer on a real machine
docker run --rm -it \
  -v "$(pwd)/install.sh:/tmp/install.sh:ro" \
  ubuntu:24.04 \
  bash -c "apt-get update -qq && bash /tmp/install.sh"

8. When the raw pattern is still acceptable

Not every use of curl | bash deserves the same level of caution. Within a trusted, internal infrastructure of your own, for example when a deployment script fetches an installer from your own, certificate secured internal artifact server, the risk of a tampered server is significantly lower than with a public third party endpoint, and the extra effort for checksum or signature verification can deliberately be lower in such a context.

For publicly distributed installers meant to run on arbitrary user machines with an unknown trust basis, the opposite holds true: precisely where the provider has the least control over the user's environment, checksum and signature verification matter the most, because a single compromised installer endpoint can potentially affect thousands of systems at once.

9. Levels of protection at a glance

Choosing the right level of protection depends on the trust relationship between user and provider, the reach of the installer, and how much protection the target system actually needs.

Approach Protection against a tampered server Protection against TOCTOU Extra effort
Raw curl | bash No No None
Download, then verify sha256sum Yes, against a known hash Partial, if the hash is published separately Low
Additionally verify a GPG signature Yes, even with a compromised server Yes Moderate, one-time key import required
Distribution package manager (apt, dnf) Yes, via repository signatures Yes Low, but the package needs ongoing maintenance

Mironsoft

Shell automation, DevOps tooling and deployment infrastructure

Shell scripts that hold up in production?

We review existing Bash scripts, spot fragile patterns and replace them with robust Bash patterns: complete error handling, logging and safe parallelization for your deployment stack.

Code Review

ShellCheck analysis and manual review for critical Bash pattern violations.

Refactoring

Retrofitting error handling, logging and safe file operations.

CI Integration

Wiring ShellCheck and BATS into pipelines and building regression tests.

10. Summary

Safer curl-Bash Installers: The Essentials at a Glance

Core risk

curl | bash runs code with full user privileges, without a checksum, signature, or prior review.

TOCTOU

A manual check in a browser does not guarantee the exact same content arrives at the actual call.

Hardening

Download, verify sha256sum against an independently published hash, then execute, as separate steps.

Extra protection

A GPG signature additionally protects against a fully compromised server that swaps script and checksum together.

11. FAQ: Safer curl-Bash Installers: The Essentials at a Glance

1What exactly is risky about the curl | bash pattern?
The user runs code with their own privileges that they have not previously seen, without a checksum or signature confirming the content actually came from the claimed provider.
2Isn't a TLS connection enough protection?
TLS protects against tampering in transit, but not against the server itself being compromised and delivering malicious code over a perfectly legitimate, encrypted connection.
3What does TOCTOU mean in this context?
Time-of-check to time-of-use: a manual check of the script in a browser does not guarantee the exact same content is delivered at the actual curl call.
4Why is it risky that Bash reads from a pipe?
Bash processes input block by block, not only after receiving it in full. If the connection drops mid-transfer, part of the script may already have run while the rest is missing.
5How do I actually make the pattern safer?
Download the script completely as a file first, verify the checksum against an independently published value, and only execute it after that check succeeds, instead of merging download and execution into one pipe.
6Why does the checksum need to be published independently?
If it comes from the same, potentially compromised source as the script, an attacker can swap both at once. A second, independent channel prevents that.
7What does a GPG signature add beyond a checksum?
It confirms the script actually came from the holder of the private key, not just that it matches a previously seen version. That protects even against a fully compromised server.
8Should I always run installer scripts with sudo?
Not automatically. Many installers request sudo even though they only create files in the home directory. A quick look through the script shows which commands actually need elevated privileges.
9When is the raw curl | bash pattern still acceptable?
Within trusted, internal infrastructure with a secured artifact server, where the risk of a tampered server is significantly lower than with a public third party endpoint.
10Is a distribution package manager fundamentally safer?
Yes, because repository signatures are verified by default. The downside is the maintenance burden for the provider to build and keep a package current for every supported distribution.