Skip to content

Security

Chapter 44: Security Considerations in Bash

Section titled “Chapter 44: Security Considerations in Bash”

This chapter covers security best practices for bash scripting. Security is critical for production scripts that handle sensitive data, system resources, and automation in DevOps environments. A single vulnerability can compromise your entire infrastructure.


┌────────────────────────────────────────────────────────────────┐
│ Security Principles │
├────────────────────────────────────────────────────────────────┤
│ │
│ 1. Input Validation │
│ - Validate all user input │
│ - Sanitize data │
│ - Use allowlists not blocklists │
│ │
│ 2. Principle of Least Privilege │
│ - Use minimum required permissions │
│ - Run with minimal privileges │
│ - Avoid running as root when possible │
│ │
│ 3. Secure Coding Practices │
│ - Quote all variables │
│ - Use -- to separate options │
│ - Avoid command injection │
│ │
│ 4. Secret Management │
│ - Never hardcode secrets │
│ - Use environment variables or vaults │
│ - Clear secrets from memory │
│ │
│ 5. Logging and Monitoring │
│ - Log security events │
│ - Monitor for anomalies │
│ - Alert on suspicious activity │
│ │
└────────────────────────────────────────────────────────────────┘

#!/usr/bin/env bash
# Input validation examples
# Validate numeric input
if [[ ! "$1" =~ ^[0-9]+$ ]]; then
echo "Error: Invalid number"
exit 1
fi
# Validate file path - alphanumeric and basic chars only
if [[ ! "$1" =~ ^[a-zA-Z0-9/_-]+$ ]]; then
echo "Error: Invalid characters in path"
exit 1
fi
# Validate email format
if [[ ! "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "Error: Invalid email"
exit 1
fi
# Validate IP address
if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
echo "Error: Invalid IP address"
exit 1
fi
#!/usr/bin/env bash
# ❌ Bad - blocklist (can be bypassed)
if [[ "$input" == *".."* ]]; then
echo "Invalid"
exit 1
fi
# ✓ Good - allowlist
if [[ ! "$input" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "Invalid"
exit 1
fi

#!/usr/bin/env bash
# Always quote variables
# ❌ Dangerous - unquoted variable
rm -rf $directory # Could expand to multiple args
# If directory="/tmp /var", becomes: rm -rf /tmp /var
# ✓ Safe - quoted variable
rm -rf "$directory"
# Use -- to separate options from arguments
# ❌ Dangerous
rm -rf "$filename" # If filename starts with -
# ✓ Safe
rm -- "$filename"
#!/usr/bin/env bash
# Get canonical path
real_path=$(realpath "$input_path")
# Verify it's within allowed directory
allowed_dir="/var/www/html"
if [[ "$real_path" != "$allowed_dir"* ]]; then
echo "Error: Path outside allowed directory"
exit 1
fi
# Additional check
allowed_dir=$(realpath "/var/www/html")
input_dir=$(dirname "$(realpath "$input_path")")
if [[ "$input_dir" != "$allowed_dir" ]]; then
echo "Error: Path traversal detected"
exit 1
fi

#!/usr/bin/env bash
# ❌ Never do this
PASSWORD="secret123"
API_KEY="sk-abc123"
DB_PASSWORD="dbpass"
# ✓ Use environment variables
: "${DATABASE_PASSWORD:?Set DATABASE_PASSWORD environment variable}"
: "${API_KEY:?Set API_KEY environment variable}"
# This will exit with error if not set
# ✓ Use secret management tools
# HashiCorp Vault
# vault read secret/database
# AWS Secrets Manager
# aws secretsmanager get-secret-value --secret-id my-secret
# Kubernetes Secrets
# kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d
# .env files (add to .gitignore)
if [[ -f .env ]]; then
set -a # Auto-export
source .env
set +a
fi

#!/usr/bin/env bash
# Script permissions
chmod 700 script.sh # Owner only (rwx------)
chmod 755 /usr/local/bin # Public execute (rwxr-xr-x)
# Sensitive files
chmod 600 secrets.txt # Owner only (rw-------)
chmod 400 key.pem # Read only (r--------)
# Use umask for new files
umask 077 # Most restrictive: files created will be 600
# Check effective permissions
stat -c "%a %n" filename
# Verify before running privileged commands
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
fi

#!/usr/bin/env bash
# ❌ Dangerous - command injection vulnerability
cmd="ls -la $user_input"
eval "$cmd"
# If user_input is "; rm -rf /", you're in trouble!
# ✓ Safe - validate input
if [[ "$user_input" =~ ^[a-zA-Z0-9_-]+$ ]]; then
cmd="ls -la $user_input"
eval "$cmd"
else
echo "Invalid input"
exit 1
fi
# ✓ Better - use arrays
args=(-la "$user_input")
ls "${args[@]}"
# ✓ Best - avoid eval entirely
ls -la -- "$user_input"
#!/usr/bin/env bash
# Remove dangerous characters
sanitize_filename() {
local filename="$1"
# Remove null bytes, newlines, path separators
echo "$filename" | tr -d '\0\n/'
}
# Use in script
filename=$(sanitize_filename "$filename")

#!/usr/bin/env bash
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
fi
# Use sudo for specific operations
# Instead of: sudo ./script.sh
# Use: sudo some_command
# Drop privileges when possible
# Using su-exec, chroot, or similar
if [[ $EUID -eq 0 ]]; then
# Drop to unprivileged user
su -s /bin/bash nobody -c 'run_command'
fi
# Check sudo without password
if sudo -n true 2>/dev/null; then
can_sudo=true
fi

#!/usr/bin/env bash
#
# Secure script template for production use
#
set -euo pipefail
# Security options
readonly SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_VERSION="1.0.0"
# Logging
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
log_security() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SECURITY: $*" | tee -a /var/log/security.log >&2
}
# Input validation
validate_input() {
local input="$1"
local pattern="$2"
if [[ ! "$input" =~ $pattern ]]; then
log_security "Invalid input: $input"
return 1
fi
return 0
}
# Sanitize path
sanitize_path() {
local path="$1"
# Remove dangerous sequences
echo "$path" | sed 's/\.\.//g; s/;//g; s/&&//g'
}
# Temporary file handling
setup_temp() {
local tmpfile
tmpfile=$(mktemp) || exit 1
echo "$tmpfile"
}
cleanup() {
local exit_status=$?
# Clean up temp files
[[ -n "${tmpfile:-}" ]] && rm -f "$tmpfile"
exit $exit_status
}
trap cleanup EXIT
# Main function
main() {
log "Starting $SCRIPT_NAME v$SCRIPT_VERSION"
# Validate inputs
validate_input "$variable" "^[a-zA-Z0-9_]+$" || exit 1
# Rest of script
}
main "$@"

#!/usr/bin/env bash
# Close unnecessary file descriptors
exec 3>/dev/null # Close fd 3
exec 4</dev/null # Close fd 4
# Redirect sensitive output
exec 2>/var/log/error.log
# Restore stderr later
exec 3>&2
exec 2>/dev/null
# ... operations ...
exec 2>&3
exec 3>&-

In this chapter, you learned:

  • ✅ Security principles and concepts
  • ✅ Input validation techniques
  • ✅ Secure coding practices
  • ✅ Secret management approaches
  • ✅ Permission best practices
  • ✅ Command injection prevention
  • ✅ Privilege escalation handling
  • ✅ Secure script templates
  • ✅ File descriptor security

Continue to the next chapter to learn about Interview Questions and prepare for your DevOps/SRE interviews.


Previous Chapter: Performance Optimization Next Chapter: Interview Questions