| name | security-first-2025 |
| description | Security-first bash scripting patterns for 2025 (mandatory validation, zero-trust). PROACTIVELY activate for: (1) reviewing or writing security-sensitive bash scripts, (2) preventing command injection via input validation, (3) safely handling untrusted input (URLs, filenames, env vars), (4) protecting HISTFILE and avoiding secret leakage, (5) safe temporary file creation (mktemp), (6) absolute paths and PATH hardening, (7) avoiding eval and dynamic command construction, (8) signal handling for cleanup, (9) ShellCheck SC2068 / SC2086 compliance. Provides: mandatory-validation patterns, secret-handling rules, mktemp recipes, signal-safe cleanup, and a security review checklist. |
🚨 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 ""
1
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/" ;
>&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
((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
[[ ! =~ ^[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.