| name | security-first-2025 |
| description | Security-first bash scripting patterns for 2025 (mandatory validation, zero-trust) |
🚨 CRITICAL GUIDELINES
Windows File Path Requirements
MANDATORY: Always Use Backslashes on Windows for File Paths
When using Edit or Write tools on Windows, you MUST use backslashes (\) in file paths, NOT forward slashes (/).
Examples:
- ❌ WRONG:
D:/repos/project/file.tsx
- ✅ CORRECT:
D:\repos\project\file.tsx
This applies to:
- Edit tool file_path parameter
- Write tool file_path parameter
- All file operations on Windows systems
Documentation Guidelines
NEVER create new documentation files unless explicitly requested by the user.
- Priority: Update existing README.md files rather than creating new documentation
- Repository cleanliness: Keep repository root clean - only README.md unless user requests otherwise
- Style: Documentation should be concise, direct, and professional - avoid AI-generated tone
- User preference: Only create additional .md files when user specifically asks for documentation
Security-First Bash Scripting (2025)
Overview
2025 security assessments reveal 60%+ of exploited automation tools lacked adequate input sanitization. This skill provides mandatory security patterns.
Critical Security Patterns
1. Input Validation (Non-Negotiable)
Every input MUST be validated before use:
#!/usr/bin/env bash
set -euo pipefail
validate_input() {
local input="$1"
local pattern="$2"
local max_length="${3:-255}"
if [[ -z "$input" ]]; then
echo "Error: Input required" >&2
return 1
fi
if [[ ! "$input" =~ $pattern ]]; then
echo "Error: Invalid format" >&2
return 1
fi
if [[ ${#input} -gt $max_length ]]; then
echo "Error: Input too long (max $max_length)" >&2
return 1
fi
return 0
}
read -r user_input
if validate_input "$user_input" '^[a-zA-Z0-9_-]+$' 50; then
process "$user_input"
else
exit 1
fi
2. Command Injection Prevention
NEVER use eval or dynamic execution with user input:
user_input="$(cat user_file.txt)"
eval "$user_input"
grep "$user_pattern" file.txt
grep -- "$user_pattern" file.txt
grep_args=("$user_pattern" "file.txt")
grep "${grep_args[@]}"
if [[ "$user_pattern" =~ ^[a-zA-Z0-9]+$ ]]; then
grep "$user_pattern" file.txt
fi
3. Path Traversal Prevention
Sanitize and validate ALL file paths:
#!/usr/bin/env bash
set -euo pipefail
sanitize_path() {
local path="$1"
path="${path//..\/}"
path="${path//\/..\//}"
path="${path#/}"
echo "$path"
}
is_safe_path() {
local file_path="$1"
local base_dir="$2"
local real_path real_base
real_path=$(readlink -f "$file_path" 2>/dev/null) || return 1
real_base=$(readlink -f "$base_dir" 2>/dev/null) || return 1
[[ "$real_path" == "$real_base"/* ]]
}
user_file=$(sanitize_path "$user_input")
if is_safe_path "/var/app/uploads/$user_file" ;
>&2
1
4. Secure Temporary Files
Never use predictable temp file names:
temp_file="/tmp/myapp.tmp"
echo "data" > "$temp_file"
temp_file="/tmp/myapp-$$.tmp"
temp_file=$(mktemp)
chmod 600 "$temp_file"
echo "data" > "$temp_file"
readonly TEMP_FILE=$(mktemp)
trap 'rm -f "$TEMP_FILE"' EXIT INT TERM
readonly TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT INT TERM
chmod 700 "$TEMP_DIR"
5. Secrets Management
NEVER hardcode secrets or expose them:
DB_PASSWORD="supersecret123"
export DB_PASSWORD="supersecret123"
if [[ -f /run/secrets/db_password ]]; then
DB_PASSWORD=$(< /run/secrets/db_password)
chmod 600 /run/secrets/db_password
else
echo "Error: Secret not found" >&2
exit 1
fi
get_secret() {
local secret_name="$1"
aws secretsmanager get-secret-value \
--secret-id "$secret_name" \
--query SecretString \
--output text
}
DB_PASSWORD=$(get_secret "production/database/password")
read -rsp "Enter password: " password
echo
6. Privilege Management
Follow least privilege principle:
#!/usr/bin/env bash
set -euo pipefail
if [[ $EUID -eq 0 ]]; then
echo "Error: Do not run as root" >&2
exit 1
fi
drop_privileges() {
local target_user="$1"
if [[ $EUID -eq 0 ]]; then
echo "Dropping privileges to $target_user" >&2
exec sudo -u "$target_user" "$0" "$@"
fi
}
run_privileged() {
local command="$1"
shift
sudo --non-interactive \
--reset-timestamp \
"$command" "$@"
}
drop_privileges "appuser"
7. Environment Variable Sanitization
Clean environment before executing:
#!/usr/bin/env bash
set -euo pipefail
clean_environment() {
unset IFS
unset CDPATH
unset GLOBIGNORE
export PATH="/usr/local/bin:/usr/bin:/bin"
IFS=$'\n\t'
}
exec_clean() {
env -i \
HOME="$HOME" \
USER="$USER" \
PATH="/usr/local/bin:/usr/bin:/bin" \
"$@"
}
clean_environment
exec_clean /usr/local/bin/myapp
8. Absolute Path Usage (2025 Best Practice)
Always use absolute paths to prevent PATH hijacking:
#!/usr/bin/env bash
set -euo pipefail
curl https://example.com/data
jq '.items[]' data.json
/usr/bin/curl https://example.com/data
/usr/bin/jq '.items[]' data.json
CURL=$(command -v curl) || { echo "curl not found" >&2; exit 1; }
"$CURL" https://example.com/data
Why This Matters:
- Prevents malicious binaries in user PATH
- Standard practice in enterprise environments
- Required for security-sensitive scripts
9. History File Protection (2025 Security)
Disable history for credential operations:
#!/usr/bin/env bash
set -euo pipefail
HISTFILE=/dev/null
export HISTFILE
HISTIGNORE="*password*:*secret*:*token*"
export HISTIGNORE
read -rsp "Enter database password: " db_password
echo
/usr/bin/mysql -p"$db_password" -e "SELECT 1"
unset db_password
Security Checklist (2025)
Every script MUST pass these checks:
Input Validation
Command Safety
File Operations
Secrets
Privileges
Error Handling
Automated Security Scanning
ShellCheck Integration
name: Security Scan
on: [push, pull_request]
jobs:
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: ShellCheck
run: |
find . -name "*.sh" -exec shellcheck \
--severity=error \
--enable=all \
{} +
Custom Security Linting
#!/usr/bin/env bash
set -euo pipefail
lint_script() {
local script="$1"
local issues=0
echo "Checking: $script"
if grep -n "eval" "$script"; then
echo " ❌ Found eval (command injection risk)"
((issues++))
fi
if grep -nE "(password|secret|token|key)\s*=\s*['\"][^'\"]+['\"]" "$script"; then
echo " ❌ Found hardcoded secrets"
((issues++))
fi
if grep -n "/tmp/[a-zA-Z0-9_-]*\\.tmp" "$script"; then
echo " ❌ Found predictable temp file"
((issues++))
fi
if grep -nE '\$[A-Z_]+[^"]' "$script"; then
echo " ⚠️ Found unquoted variables"
((issues++))
fi
if ((issues == ));
}
total_issues=0
IFS= -r -d script;
lint_script || ((total_issues++))
< <(find . -name - f -print0)
((total_issues > ));
1
Real-World Secure Script Template
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
if [[ $EUID -eq 0 ]]; then
echo "Error: Do not run as root" >&2
exit 1
fi
export PATH="/usr/local/bin:/usr/bin:/bin"
unset CDPATH GLOBIGNORE
readonly TEMP_FILE=$(mktemp)
trap 'rm -f "$TEMP_FILE"; exit' EXIT INT TERM
chmod 600 "$TEMP_FILE"
validate_input() {
local input="$1"
if [[ -z "$input" ]]; then
echo "Error: Input required" >&2
return 1
fi
[[ ! =~ ^[a-zA-Z0-9_/-]+$ ]];
>&2
1
[[ -gt 255 ]];
>&2
1
0
}
() {
path=
path=
path=
}
() {
user_input=
! validate_input ;
1
safe_path
safe_path=$(sanitize_path )
}
main
Compliance Standards (2025)
CIS Benchmarks
- Use ShellCheck for automated compliance
- Implement input validation on all user data
- Secure temporary file handling
- Least privilege execution
NIST Guidelines
- Strong input validation (NIST SP 800-53)
- Secure coding practices
- Logging and monitoring
- Access control enforcement
OWASP Top 10
- A03: Injection - Prevent command injection
- A01: Broken Access Control - Path validation
- A02: Cryptographic Failures - Secure secrets
Resources
Security-first development is non-negotiable in 2025. Every script must pass all security checks before deployment.