glunty

Blog   /   Reading code in plain English   /  

Reading curl-as-bash safely before you paste

How to read a curl command or curl-pipe-bash one-liner before running it. Decoding the URL, headers, body, and what the surrounding shell will do.

The most dangerous shell command on the internet is curl https://example.com/install.sh | bash. Every install guide that uses it asks you to download untrusted text and execute it as a shell script with the privileges of your current user. If the URL is hijacked, the server is compromised, or DNS is poisoned, the consequence is whatever malicious code shows up in your shell.

The mitigation is not to refuse curl pipelines (you would lose half the install instructions on the internet) but to read them. Most are safe. Some are not. Reading takes a minute and saves more time than the worst case costs.

This post walks through how to read curl commands and curl-piped-to-bash patterns safely. The pillar on reading code in plain English covers the general strategy; this is the curl-and-bash drill-down. The bash explainer and curl parser accelerate the read.

What curl actually does

A curl command is a shorthand for an HTTP request. The flags translate to method, URL, headers, body, auth, redirects, and a handful of behavioral options. The curl parser at glunty decomposes any curl command into the structured request it represents: method, URL, headers, body, auth, cookies, multipart fields.

Reading curl alone is usually safe. The danger is what surrounds the curl command, especially when the output gets piped to a shell.

The “curl | bash” pattern

The shape: curl -fsSL https://some-host.com/install.sh | bash. Or worse: curl https://some-host.com/install | sh -s -- --flag.

What this does:

  1. curl fetches the URL.
  2. The response body (which is a shell script) goes to stdout.
  3. The pipe sends stdout to bash (or sh).
  4. bash reads the script line by line and executes it.

The script runs in your shell environment, with your user’s privileges, with full access to your home directory, your SSH keys, your environment variables, and your network. Whatever the script says is what runs.

The script is downloaded over HTTPS, so a passive attacker on the network cannot modify it. But:

  • The server hosting the script can serve different content to different IPs (you may not see what someone else sees).
  • The server can serve different content over time. The script you read at noon may not be the script that runs at midnight.
  • A subdomain takeover, a leaked server credential, or a compromised CI pipeline can replace the script with malicious content.
  • DNS spoofing can redirect to a different server (mitigated by HTTPS but still possible against new domains).

The risk is not that HTTPS is broken. The risk is that the upstream party is trustworthy at the moment you ran the command, which is a different question.

Reading the curl command itself

Before you trust what is on the other end of the pipe, read the curl command for clues:

The URL. Is it the host you expect? Look at the registered domain (the second-level domain plus TLD) carefully. Lookalike domains substitute similar characters: g0ogle.com, paypaI.com (capital I instead of lowercase l). The URL encoder/decoder helps decode percent-encoded URLs that may obscure the real host.

The path. Is it under /install, /setup, /get (typical for install scripts) or something more suspicious? Long random paths with query parameters that include cookies or tokens are red flags.

The flags. Common safe flags:

  • -f / --fail: fail on HTTP error (good; prevents executing an HTML error page as a script)
  • -s / --silent: suppress progress output
  • -S / --show-error: show errors despite silent
  • -L / --location: follow redirects
  • -o file / -O: write to a file (instead of stdout, breaks the pipe-to-bash pattern)

Suspicious flags:

  • --insecure / -k: disable HTTPS certificate validation (run away)
  • --data / -d: this is a POST with data; install scripts do not usually need this
  • -H "X-Custom: secret": custom auth or tracking headers may leak data
  • -u user:pass: basic auth in the command (do not paste this command anywhere)

The redirect chain. With -L, curl follows redirects. The final URL is what gets executed. The initial URL is what you read. If they differ significantly, you may have downloaded something other than what you expected. Use curl -fsSL -o /tmp/script.sh URL to save and read first.

The “curl, then read, then run” alternative

The safer pattern, recommended by security-aware projects:

curl -fsSLO https://example.com/install.sh
less install.sh   # actually read it
bash install.sh   # run it

This separates fetching from executing. You can read the script, share it with a colleague, run a static analyzer on it, search for known bad patterns, then decide.

If you are evaluating a new tool’s install script, this is the right path. The few seconds it takes to read a 200-line shell script costs less than the few hours it takes to clean up a compromised laptop.

Reading the install script itself

Once you have the script, read it for the patterns that would compromise your machine:

Unconditional sudo. A script that runs sudo early and broadly may install system-wide packages or modify protected directories. This is normal for OS package installers. It is suspicious for tools that should install per-user.

Modifications to PATH or shell rc files. A script that appends to your .bashrc, .zshrc, or .profile is changing your shell environment in ways you have not yet seen. Most install scripts do this for legitimate reasons (adding a binary to PATH). Read what they add.

Curl downloading additional payloads. A script that uses curl to fetch more code from the same or different domains is a chain. The chain may be longer than you expect. Each curl in the chain is another trust hop.

Eval of dynamic content. eval "$(remote_command)" is execution of remote output. The same risk as curl-pipe-bash, just nested. Look for these patterns and trace what each one runs.

Touching sensitive files. Reads from ~/.aws/credentials, ~/.ssh/, /etc/shadow, /etc/sudoers are red flags unless the script is explicitly an SSH or AWS configuration tool.

The bash explainer decodes any of these patterns in plain English. Paste the script, read what each line does. The explainer flags potentially-destructive constructs in the notes section.

Bash-specific traps inside the script

Even a script you trust can do unexpected things due to bash idioms:

Word splitting. A line like for f in $(ls *.log); do rm $f; done looks like it deletes log files. It does, mostly. But filenames with spaces split into multiple “files” that do not exist, then the rm fails or (worse) deletes wrong files. The fix in well-written scripts: for f in *.log; do rm -- "$f"; done.

Glob expansion in unexpected places. A line like mv $source $dest glob-expands both arguments. If $source is empty, the command becomes mv $dest, which is a syntax error. If $dest is unset, the command becomes mv $source, which moves the source somewhere unintended.

set -e does not catch everything. Many scripts start with set -e (exit on error). It does not catch errors inside command substitutions, inside if conditions, or inside pipelines (without set -o pipefail). A script with set -e may continue past errors you assumed it would catch.

Heredocs that expand. A script with cat <<EOF expands variables and command substitutions inside the heredoc. The unquoted EOF is the danger; cat <<'EOF' (with quoted delimiter) is literal. If a heredoc contains user-controlled data with backticks or $(...), the unquoted form may execute it.

These are the patterns that turn an otherwise-safe script into a bug factory. The bash explainer surfaces them when present.

How to read curl commands in code reviews and runbooks

When you encounter a curl command in someone else’s code or runbook, walk the same checklist:

  1. Decode the curl command with the curl parser. Read the resulting method, URL, headers, body.
  2. Verify the URL is the host you expect. Check for lookalike domains.
  3. Verify the auth headers (Authorization, Cookie, custom tokens) do not leak credentials. The JWT decoder helps if the token is a JWT.
  4. If the command is piped to bash, save the response to a file first and read it before executing.
  5. If the command is wrapped in a shell script, use the bash explainer to decode the surrounding logic.

Reading a curl command takes one minute. Reading a small install script takes 10. Both are cheaper than recovering from a compromised machine. The pillar on reading code in plain English covers the general strategy that applies here too: state intent, decompose, verify, test edge cases.

Closing

curl is not the danger; trust is. A curl command piped to bash is fine when you trust the source and the script. It is catastrophic when you do not. The only way to know which is which is to read carefully. The tools at glunty (curl parser, bash explainer, URL encoder, JWT decoder) lower the cost of careful reading enough that there is no excuse to skip it.

Embedded tool from glunty.com