π¨ 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
- Input Validation (Non-Negotiable)
Every input MUST be validated before use:
#!/usr/bin/env bash set -euo pipefail
β REQUIRED: Validate all inputs
validate_input() { local input="$1" local pattern="$2" local max_length="${3:-255}"
# Check empty
if [[ -z "$input" ]]; then
echo "Error: Input required" >&2
return 1
fi
# Check pattern
if [[ ! "$input" =~ $pattern ]]; then
echo "Error: Invalid format" >&2
return 1
fi
# Check length
if [[ ${#input} -gt $max_length ]]; then
echo "Error: Input too long (max $max_length)" >&2
return 1
fi
return 0
}
Usage
read -r user_input if validate_input "$user_input" '^[a-zA-Z0-9_-]+$' 50; then process "$user_input" else exit 1 fi
- Command Injection Prevention
NEVER use eval or dynamic execution with user input:
β DANGEROUS - Command injection vulnerability
user_input="$(cat user_file.txt)" eval "$user_input" # NEVER DO THIS
β DANGEROUS - Indirect command injection
grep "$user_pattern" file.txt # If pattern is "-e /etc/passwd"
β SAFE - Use -- separator
grep -- "$user_pattern" file.txt
β SAFE - Use arrays
grep_args=("$user_pattern" "file.txt") grep "${grep_args[@]}"
β SAFE - Validate before use
if [[ "$user_pattern" =~ ^[a-zA-Z0-9]+$ ]]; then grep "$user_pattern" file.txt fi
- Path Traversal Prevention
Sanitize and validate ALL file paths:
#!/usr/bin/env bash set -euo pipefail
Sanitize path components
sanitize_path() { local path="$1"
# Remove dangerous patterns
path="${path//..\/}" # Remove ../
path="${path//\/..\//}" # Remove /../
path="${path#/}" # Remove leading /
echo "$path"
}
Validate path is within allowed directory
is_safe_path() { local file_path="$1" local base_dir="$2"
# Resolve to absolute paths
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
# Check path starts with base
[[ "$real_path" == "$real_base"/* ]]
}
Usage
user_file=$(sanitize_path "$user_input") if is_safe_path "/var/app/uploads/$user_file" "/var/app/uploads"; then cat "/var/app/uploads/$user_file" else echo "Error: Access denied" >&2 exit 1 fi
- Secure Temporary Files
Never use predictable temp file names:
β DANGEROUS - Race condition vulnerability
temp_file="/tmp/myapp.tmp" echo "data" > "$temp_file" # Can be symlinked by attacker
β DANGEROUS - Predictable name
temp_file="/tmp/myapp-$$.tmp" # PID can be guessed
β SAFE - Use mktemp
temp_file=$(mktemp) chmod 600 "$temp_file" # Owner-only permissions echo "data" > "$temp_file"
β SAFE - Automatic cleanup
readonly TEMP_FILE=$(mktemp) trap 'rm -f "$TEMP_FILE"' EXIT INT TERM
β SAFE - Temp directory
readonly TEMP_DIR=$(mktemp -d) trap 'rm -rf "$TEMP_DIR"' EXIT INT TERM chmod 700 "$TEMP_DIR"
- Secrets Management
NEVER hardcode secrets or expose them:
β DANGEROUS - Hardcoded secrets
DB_PASSWORD="supersecret123"
β DANGEROUS - Secrets in environment (visible in ps)
export DB_PASSWORD="supersecret123"
β SAFE - Read from secure file
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
β SAFE - Use cloud secret managers
get_secret() { local secret_name="$1"
# AWS Secrets Manager
aws secretsmanager get-secret-value \
--secret-id "$secret_name" \
--query SecretString \
--output text
}
DB_PASSWORD=$(get_secret "production/database/password")
β SAFE - Prompt for sensitive data (no echo)
read -rsp "Enter password: " password echo # Newline after password
- Privilege Management
Follow least privilege principle:
#!/usr/bin/env bash set -euo pipefail
Check not running as root
if [[ $EUID -eq 0 ]]; then echo "Error: Do not run as root" >&2 exit 1 fi
Drop privileges if started as root
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 specific command with minimal privileges
run_privileged() { local command="$1" shift
# Use sudo with minimal scope
sudo --non-interactive \
--reset-timestamp \
"$command" "$@"
}
Usage
drop_privileges "appuser"
- Environment Variable Sanitization
Clean environment before executing:
#!/usr/bin/env bash set -euo pipefail
Clean environment
clean_environment() { # Unset dangerous variables unset IFS unset CDPATH unset GLOBIGNORE
# Set safe PATH (absolute paths only)
export PATH="/usr/local/bin:/usr/bin:/bin"
# Set safe IFS
IFS=$'\n\t'
}
Execute command in clean environment
exec_clean() {
env -i
HOME="$HOME"
USER="$USER"
PATH="/usr/local/bin:/usr/bin:/bin"
"$@"
}
Usage
clean_environment exec_clean /usr/local/bin/myapp
- Absolute Path Usage (2025 Best Practice)
Always use absolute paths to prevent PATH hijacking:
#!/usr/bin/env bash set -euo pipefail
β DANGEROUS - Vulnerable to PATH manipulation
curl https://example.com/data jq '.items[]' data.json
β SAFE - Absolute paths
/usr/bin/curl https://example.com/data /usr/bin/jq '.items[]' data.json
β SAFE - Verify command location
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
- History File Protection (2025 Security)
Disable history for credential operations:
#!/usr/bin/env bash set -euo pipefail
Disable history for this session
HISTFILE=/dev/null export HISTFILE
Or disable specific commands
HISTIGNORE="password:secret:token" export HISTIGNORE
Handle sensitive operations
read -rsp "Enter database password: " db_password echo
Use password (not logged to history)
/usr/bin/mysql -p"$db_password" -e "SELECT 1"
Clear variable
unset db_password
Security Checklist (2025)
Every script MUST pass these checks:
Input Validation
-
All user inputs validated with regex patterns
-
Maximum length enforced on all inputs
-
Empty/null inputs rejected
-
Special characters escaped or rejected
Command Safety
-
No eval with user input
-
No dynamic variable names from user input
-
All command arguments use -- separator
-
Arrays used instead of string concatenation
File Operations
-
All paths validated against directory traversal
-
Temp files created with mktemp
-
File permissions set restrictively (600/700)
-
Cleanup handlers registered (trap EXIT)
Secrets
-
No hardcoded passwords/keys/tokens
-
Secrets read from secure storage
-
Secrets never logged or printed
-
Secrets cleared from memory when done
Privileges
-
Runs with minimum required privileges
-
Root execution rejected unless necessary
-
Privilege drops implemented where needed
-
Sudo scope minimized
Error Handling
-
set -euo pipefail enabled
-
All errors logged to stderr
-
Sensitive data not exposed in errors
-
Exit codes meaningful
Automated Security Scanning
ShellCheck Integration
.github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs: shellcheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3
- name: ShellCheck
run: |
# Fail on security issues
find . -name "*.sh" -exec shellcheck \
--severity=error \
--enable=all \
{} +
Custom Security Linting
#!/usr/bin/env bash
security-lint.sh - Check scripts for security issues
set -euo pipefail
lint_script() { local script="$1" local issues=0
echo "Checking: $script"
# Check for eval
if grep -n "eval" "$script"; then
echo " β Found eval (command injection risk)"
((issues++))
fi
# Check for hardcoded secrets
if grep -nE "(password|secret|token|key)\s*=\s*['\"][^'\"]+['\"]" "$script"; then
echo " β Found hardcoded secrets"
((issues++))
fi
# Check for predictable temp files
if grep -n "/tmp/[a-zA-Z0-9_-]*\\.tmp" "$script"; then
echo " β Found predictable temp file"
((issues++))
fi
# Check for unquoted variables
if grep -nE '\$[A-Z_]+[^"]' "$script"; then
echo " β οΈ Found unquoted variables"
((issues++))
fi
if ((issues == 0)); then
echo " β No security issues found"
fi
return "$issues"
}
Scan all scripts
total_issues=0 while IFS= read -r -d '' script; do lint_script "$script" || ((total_issues++)) done < <(find . -name "*.sh" -type f -print0)
if ((total_issues > 0)); then echo "β Found security issues in $total_issues scripts" exit 1 else echo "β All scripts passed security checks" fi
Real-World Secure Script Template
#!/usr/bin/env bash
Secure Script Template (2025)
set -euo pipefail IFS=$'\n\t'
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
Security: Reject root execution
if [[ $EUID -eq 0 ]]; then echo "Error: Do not run as root" >&2 exit 1 fi
Security: Clean environment
export PATH="/usr/local/bin:/usr/bin:/bin" unset CDPATH GLOBIGNORE
Security: Secure temp file
readonly TEMP_FILE=$(mktemp) trap 'rm -f "$TEMP_FILE"; exit' EXIT INT TERM chmod 600 "$TEMP_FILE"
Validate input
validate_input() { local input="$1"
if [[ -z "$input" ]]; then
echo "Error: Input required" >&2
return 1
fi
if [[ ! "$input" =~ ^[a-zA-Z0-9_/-]+$ ]]; then
echo "Error: Invalid characters in input" >&2
return 1
fi
if [[ ${#input} -gt 255 ]]; then
echo "Error: Input too long" >&2
return 1
fi
return 0
}
Sanitize file path
sanitize_path() { local path="$1" path="${path//../}" path="${path#/}" echo "$path" }
Main function
main() { local user_input="${1:-}"
# Validate
if ! validate_input "$user_input"; then
exit 1
fi
# Sanitize
local safe_path
safe_path=$(sanitize_path "$user_input")
# Process safely
echo "Processing: $safe_path"
# ... your logic here ...
}
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
-
CIS Docker Benchmark
-
OWASP Command Injection
-
ShellCheck Security Rules
-
NIST SP 800-53
Security-first development is non-negotiable in 2025. Every script must pass all security checks before deployment.