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.
Table of Contents
- 1. Why the curl-Bash pattern is so widespread
- 2. Risk 1: no checksum, no proof of the actual content
- 3. Risk 2: TOCTOU and server side variable responses
- 4. Risk 3: execution starts before the download is complete
- 5. Making it safer: separating download, checksum and execution
- 6. GPG signatures as an additional layer of protection
- 7. Running with restricted privileges and reviewing beforehand
- 8. When the raw pattern is still acceptable
- 9. Levels of protection at a glance
- 10. Summary
- 11. FAQ
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.