Bash Security
Command injection risks, safe handling of secrets, quoting pitfalls that become vulnerabilities, and hardening scripts that run with elevated privileges.
Command Injection: The #1 Bash Security Risk
# DANGEROUS: user input concatenated directly into a command
read -p "Enter filename: " FILENAME
cat $FILENAME # if FILENAME="; rm -rf ~", this becomes TWO commands
# Also dangerous
eval "ls $USER_INPUT" # eval executes ANYTHING in the string, including injected commands
# SAFER: quote the variable, don't use eval on untrusted input
cat "$FILENAME"
# Validate input against an allowlist before using it at all
if [[ ! "$FILENAME" =~ ^[a-zA-Z0-9_./-]+$ ]]; then
echo "Invalid filename" >&2
exit 1
fi
eval combined with any unsanitized input is one of the most dangerous patterns in Bash — avoid eval on anything derived from user input, environment variables from untrusted sources, or external API responses.
Never Log or Echo Secrets
# DANGEROUS: secret visible in shell history, process list (ps aux), and any trace/logs
API_KEY="sk-12345"
curl "https://api.example.com?key=$API_KEY" # visible in ps aux while curl runs, and in shell history
# SAFER: pass secrets via files, stdin, or environment variables, and avoid printing them
curl -H "Authorization: Bearer $(cat /run/secrets/api_key)" https://api.example.com
set +x # ensure tracing is off before touching anything secret, even temporarily
# Command-line arguments are visible to ANY user on the system via `ps aux` while the process runs
mysql -u root -pMyPassword123 # BAD — password visible in process list
mysql -u root -p < /dev/null # prompts interactively instead — password never appears in ps aux
MYSQL_PWD="$PASSWORD" mysql -u root # environment variables are somewhat safer, but still readable via /proc/<pid>/environ by root
Secure Temporary Files
# DANGEROUS: predictable filename, race condition — another process/user could pre-create it
TMPFILE="/tmp/myapp_output"
# SAFE: mktemp generates a unique, unpredictable name with safe permissions
TMPFILE=$(mktemp)
chmod 600 "$TMPFILE" # explicit, in case umask is unexpectedly permissive
trap 'rm -f "$TMPFILE"' EXIT
Restricting What a Script Can Do (Least Privilege)
# If a script only needs to read a specific file, don't run the WHOLE script as root
# just because ONE step needs elevated privileges — scope sudo narrowly:
sudo -u appuser cat /etc/myapp/secret.conf # only this one command runs with elevated context, not the whole script
# In /etc/sudoers, scope exactly what a service account can run, not blanket ALL:
# deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp
Validating and Sanitizing All External Input
validate_number() {
[[ "$1" =~ ^[0-9]+$ ]] || { echo "Not a valid number: $1" >&2; exit 1; }
}
validate_path() {
# Reject paths attempting directory traversal
case "$1" in
*".."*) echo "Path traversal attempt detected" >&2; exit 1 ;;
esac
}
validate_number "$1"
validate_path "$2"
Setting a Restrictive umask in Sensitive Scripts
umask 077 # new files/directories created by this script are readable only by their owner
Auditing a Script for Common Issues
shellcheck script.sh # catches unquoted variables, common injection-prone patterns
grep -n "eval\|\$(cat" script.sh # manually flag risky patterns for review
Production Considerations
- Treat every piece of external input — command-line args, environment variables, file contents, API responses — as untrusted until validated; this is the same mindset as web application input validation, just applied to shell scripts.
- Secrets should never appear in command-line arguments (visible via
ps auxto any user on the host) or inset -xtrace output — use files with restricted permissions or a proper secrets manager. - Run
shellcheckin CI specifically because many of its warnings (unquoted variables,evalmisuse) are security issues, not just style preferences.
Quick Interview Answer
“The biggest Bash security risks are command injection via unquoted variables or
evalon untrusted input, and secrets leaking through command-line arguments (visible inps aux), shell history, orset -xtraces. The defenses are consistent: always double-quote variables, validate/allowlist external input before using it in a command, avoidevalon anything not fully trusted, pass secrets via files or stdin rather than arguments, and usemktempinstead of predictable temp filenames to avoid race conditions.”
Common Mistakes
- Using
evalon any input that isn’t fully controlled by the script author. - Passing secrets as command-line arguments, where they’re visible to any user via
ps aux. - Assuming quoting is “just a style choice” rather than recognizing it as the primary defense against command injection.
Add More Questions to This Guide
Know a question that should be here? Share it and help the community!
Open Google Form