Security Patterns - jvPalma/dotrun GitHub Wiki
Skill Level: Intermediate to Advanced
Essential security practices and patterns for developing secure DotRun scripts and managing collections safely.
Always validate script inputs to prevent injection attacks:
#!/usr/bin/env bash
### DOC
# Secure input validation patterns
### DOC
set -euo pipefail
validate_filename() {
local filename="$1"
# Check for null or empty
if [[ -z "$filename" ]]; then
echo "❌ Filename cannot be empty"
return 1
fi
# Allow only alphanumeric, dash, underscore, and dot
if [[ ! "$filename" =~ ^[a-zA-Z0-9._-]+$ ]]; then
echo "❌ Invalid filename: $filename"
echo "💡 Use only letters, numbers, dots, dashes, and underscores"
return 1
fi
# Prevent directory traversal
if [[ "$filename" =~ \.\. ]]; then
echo "❌ Directory traversal not allowed: $filename"
return 1
fi
return 0
}
validate_environment() {
local environment="$1"
local valid_environments=("development" "staging" "production")
for valid_env in "${valid_environments[@]}"; do
if [[ "$environment" == "$valid_env" ]]; then
return 0
fi
done
echo "❌ Invalid environment: $environment"
echo "💡 Valid environments: ${valid_environments[*]}"
return 1
}
validate_url() {
local url="$1"
# Basic URL validation
if [[ ! "$url" =~ ^https?://[a-zA-Z0-9.-]+(\:[0-9]+)?(/.*)?$ ]]; then
echo "❌ Invalid URL format: $url"
return 1
fi
# Prevent internal/private URLs in production
if [[ "$url" =~ (localhost|127\.0\.0\.1|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.) ]]; then
echo "⚠️ Warning: URL points to internal/private network"
read -p "Continue? [y/N]: " -n 1 -r
echo
[[ $REPLY =~ ^[Yy]$ ]] || return 1
fi
return 0
}
main() {
local operation="$1"
local value="$2"
case "$operation" in
filename)
validate_filename "$value"
;;
environment)
validate_environment "$value"
;;
url)
validate_url "$value"
;;
*)
echo "Usage: dr validator <filename|environment|url> <value>"
exit 1
;;
esac
}
main "$@"Protect against command injection in dynamic script execution:
#!/usr/bin/env bash
### DOC
# Safe command execution patterns
### DOC
set -euo pipefail
# Safe command execution with whitelisted commands
safe_execute() {
local command="$1"
shift
# Whitelist of allowed commands
local allowed_commands=("git" "docker" "kubectl" "npm" "python" "node")
# Check if command is allowed
local is_allowed=false
for allowed_cmd in "${allowed_commands[@]}"; do
if [[ "$command" == "$allowed_cmd" ]]; then
is_allowed=true
break
fi
done
if [[ "$is_allowed" == false ]]; then
echo "❌ Command not allowed: $command"
echo "💡 Allowed commands: ${allowed_commands[*]}"
return 1
fi
# Execute with quoted arguments to prevent injection
"$command" "$@"
}
# Safe file operations
safe_file_operation() {
local operation="$1"
local file_path="$2"
# Validate file path
if [[ ! "$file_path" =~ ^[a-zA-Z0-9./_ -]+$ ]]; then
echo "❌ Invalid file path: $file_path"
return 1
fi
# Prevent directory traversal
if [[ "$file_path" =~ \.\. ]]; then
echo "❌ Directory traversal not allowed"
return 1
fi
# Restrict to specific directories
local allowed_base_dirs=("/tmp" "$HOME/workspace" "$DR_CONFIG")
local is_allowed_path=false
for base_dir in "${allowed_base_dirs[@]}"; do
if [[ "$file_path" == "$base_dir"* ]]; then
is_allowed_path=true
break
fi
done
if [[ "$is_allowed_path" == false ]]; then
echo "❌ File path not in allowed directories"
echo "💡 Allowed base directories: ${allowed_base_dirs[*]}"
return 1
fi
case "$operation" in
read)
[[ -f "$file_path" ]] && cat "$file_path"
;;
write)
cat > "$file_path"
;;
delete)
[[ -f "$file_path" ]] && rm "$file_path"
;;
*)
echo "❌ Unknown operation: $operation"
return 1
;;
esac
}
main() {
local action="$1"
shift
case "$action" in
execute)
safe_execute "$@"
;;
file)
safe_file_operation "$@"
;;
*)
echo "Usage: dr safe <execute|file> [args...]"
exit 1
;;
esac
}
main "$@"Never hardcode secrets in scripts. Use environment variables and secure prompts:
#!/usr/bin/env bash
### DOC
# Secure secret management patterns
### DOC
set -euo pipefail
get_secret() {
local secret_name="$1"
local env_var_name="${secret_name^^}" # Convert to uppercase
# Try environment variable first
if [[ -n "${!env_var_name:-}" ]]; then
echo "${!env_var_name}"
return 0
fi
# Try secure file storage
local secret_file="$HOME/.secrets/$secret_name"
if [[ -f "$secret_file" ]]; then
cat "$secret_file"
return 0
fi
# Prompt user securely
echo "Secret '$secret_name' not found in environment or secure storage" >&2
read -s -p "Enter $secret_name: " secret_value
echo >&2
if [[ -z "$secret_value" ]]; then
echo "❌ Secret cannot be empty" >&2
return 1
fi
echo "$secret_value"
}
store_secret() {
local secret_name="$1"
local secret_value="$2"
local secrets_dir="$HOME/.secrets"
mkdir -p "$secrets_dir"
chmod 700 "$secrets_dir"
local secret_file="$secrets_dir/$secret_name"
echo "$secret_value" >"$secret_file"
chmod 600 "$secret_file"
echo "✅ Secret stored securely in $secret_file"
}
# Example usage with API authentication
authenticate_api() {
local api_token
api_token=$(get_secret "api_token")
if [[ -z "$api_token" ]]; then
echo "❌ Failed to get API token"
return 1
fi
# Use the token (never echo it)
curl -H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
"$API_ENDPOINT/authenticate"
}
main() {
case "${1:-}" in
get)
get_secret "$2"
;;
store)
if [[ -z "${3:-}" ]]; then
# Read from stdin for security
read -s -p "Enter secret value: " secret_value
echo
store_secret "$2" "$secret_value"
else
store_secret "$2" "$3"
fi
;;
auth)
authenticate_api
;;
*)
echo "Usage: dr secrets <get|store|auth> [secret-name] [value]"
exit 1
;;
esac
}
main "$@"Handle temporary files securely:
#!/usr/bin/env bash
### DOC
# Secure temporary file handling
### DOC
set -euo pipefail
# Create secure temporary files
create_secure_temp() {
local temp_file
temp_file=$(mktemp)
# Set restrictive permissions immediately
chmod 600 "$temp_file"
# Ensure cleanup on exit
trap "rm -f '$temp_file'" EXIT
echo "$temp_file"
}
# Create secure temporary directory
create_secure_temp_dir() {
local temp_dir
temp_dir=$(mktemp -d)
# Set restrictive permissions
chmod 700 "$temp_dir"
# Ensure cleanup on exit
trap "rm -rf '$temp_dir'" EXIT
echo "$temp_dir"
}
# Example: Process sensitive data securely
process_sensitive_data() {
local input_file="$1"
# Create secure temporary file
local temp_file
temp_file=$(create_secure_temp)
# Process data (example: remove sensitive fields)
jq 'del(.password, .secret_key, .api_token)' "$input_file" >"$temp_file"
# Move processed file to final location
mv "$temp_file" "${input_file}.processed"
echo "✅ Sensitive data processed and cleaned"
}
main() {
case "${1:-}" in
create-file)
create_secure_temp
;;
create-dir)
create_secure_temp_dir
;;
process)
process_sensitive_data "$2"
;;
*)
echo "Usage: dr secure-temp <create-file|create-dir|process> [file]"
exit 1
;;
esac
}
main "$@"Validate collections before importing:
#!/usr/bin/env bash
### DOC
# Security validation for collections
### DOC
set -euo pipefail
validate_collection_source() {
local repo_url="$1"
# Whitelist trusted domains
local trusted_domains=(
"github.com"
"gitlab.com"
"company.github.com"
"internal-git.company.com"
)
local is_trusted=false
for domain in "${trusted_domains[@]}"; do
if [[ "$repo_url" =~ $domain ]]; then
is_trusted=true
break
fi
done
if [[ "$is_trusted" == false ]]; then
echo "⚠️ Untrusted source: $repo_url"
echo "Trusted domains: ${trusted_domains[*]}"
read -p "Continue anyway? [y/N]: " -n 1 -r
echo
[[ $REPLY =~ ^[Yy]$ ]] || return 1
fi
return 0
}
scan_collection_security() {
local collection_path="$1"
local issues=0
echo "🔒 Scanning collection for security issues..."
# Check for hardcoded secrets
if grep -r -i -E "(password|secret|key|token).*=" "$collection_path/bin/" 2>/dev/null; then
echo "⚠️ Potential hardcoded secrets found"
((issues++))
fi
# Check for dangerous commands
if grep -r -E "(rm -rf /|dd if=/dev|mkfs|fdisk)" "$collection_path/bin/" 2>/dev/null; then
echo "⚠️ Potentially dangerous commands found"
((issues++))
fi
# Check file permissions
while IFS= read -r -d '' file; do
local perms
perms=$(stat -c "%a" "$file")
if [[ "$perms" == "777" ]]; then
echo "⚠️ Overly permissive file: $file"
((issues++))
fi
done < <(find "$collection_path" -type f -print0)
# Check for suspicious URLs
if grep -r -E "https?://[^/]*\.(tk|ml|ga|cf)" "$collection_path/" 2>/dev/null; then
echo "⚠️ Suspicious URLs found (free TLD domains)"
((issues++))
fi
if [[ $issues -eq 0 ]]; then
echo "✅ No security issues found"
else
echo "⚠️ $issues potential security issues found"
return 1
fi
return 0
}
quarantine_collection() {
local collection_name="$1"
local quarantine_dir="$DR_CONFIG/quarantine"
mkdir -p "$quarantine_dir"
if [[ -d "$DR_CONFIG/collections/$collection_name" ]]; then
mv "$DR_CONFIG/collections/$collection_name" "$quarantine_dir/"
echo "🚨 Collection moved to quarantine: $quarantine_dir/$collection_name"
fi
}
main() {
case "${1:-}" in
validate-source)
validate_collection_source "$2"
;;
scan)
scan_collection_security "$2"
;;
quarantine)
quarantine_collection "$2"
;;
*)
echo "Usage: dr collection-security <validate-source|scan|quarantine> <repo-url|path|name>"
exit 1
;;
esac
}
main "$@"Implement role-based access control:
#!/usr/bin/env bash
### DOC
# Role-based access control for scripts
### DOC
set -euo pipefail
USER_ROLES_FILE="$DR_CONFIG/user-roles.conf"
get_user_roles() {
local username="${1:-$USER}"
if [[ ! -f "$USER_ROLES_FILE" ]]; then
echo "user" # Default role
return
fi
grep "^$username:" "$USER_ROLES_FILE" | cut -d: -f2 | tr ',' ' '
}
check_script_permission() {
local script_name="$1"
local required_role="$2"
local user_roles
user_roles=$(get_user_roles)
# Admin role has access to everything
if [[ "$user_roles" =~ admin ]]; then
return 0
fi
# Check if user has required role
if [[ "$user_roles" =~ $required_role ]]; then
return 0
fi
echo "❌ Access denied: Script '$script_name' requires '$required_role' role"
echo "💡 Your roles: $user_roles"
return 1
}
# Example: Production deployment script with access control
secure_deploy() {
local environment="$1"
case "$environment" in
production)
check_script_permission "deploy-production" "deployer" || return 1
;;
staging)
check_script_permission "deploy-staging" "developer" || return 1
;;
development)
# No special permissions needed
;;
*)
echo "❌ Unknown environment: $environment"
return 1
;;
esac
echo "🚀 Deploying to $environment..."
# Deployment logic here
}
main() {
case "${1:-}" in
check)
check_script_permission "$2" "$3"
;;
roles)
get_user_roles "${2:-}"
;;
deploy)
secure_deploy "$2"
;;
*)
echo "Usage: dr access-control <check|roles|deploy> [args...]"
exit 1
;;
esac
}
main "$@"Implement comprehensive security logging:
#!/usr/bin/env bash
### DOC
# Security audit logging
### DOC
set -euo pipefail
AUDIT_LOG="$DR_CONFIG/security-audit.log"
log_security_event() {
local event_type="$1"
local event_details="$2"
local severity="${3:-INFO}"
local timestamp
timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
local username="${USER:-unknown}"
local hostname="${HOSTNAME:-unknown}"
echo "$timestamp,$severity,$event_type,$username,$hostname,$event_details" >>"$AUDIT_LOG"
}
audit_script_execution() {
local script_name="$1"
shift
local args="$*"
log_security_event "SCRIPT_EXECUTION" "script=$script_name args=$args" "INFO"
# Execute the script
dr "$script_name" "$@"
local exit_code=$?
if [[ $exit_code -eq 0 ]]; then
log_security_event "SCRIPT_SUCCESS" "script=$script_name" "INFO"
else
log_security_event "SCRIPT_FAILURE" "script=$script_name exit_code=$exit_code" "WARN"
fi
return $exit_code
}
audit_collection_import() {
local repo_url="$1"
local collection_name="$2"
log_security_event "COLLECTION_IMPORT" "repo=$repo_url name=$collection_name" "INFO"
# Perform security scan
if dr collection-security scan "$DR_CONFIG/collections/$collection_name"; then
log_security_event "COLLECTION_SCAN_PASS" "name=$collection_name" "INFO"
else
log_security_event "COLLECTION_SCAN_FAIL" "name=$collection_name" "WARN"
dr collection-security quarantine "$collection_name"
fi
}
generate_security_report() {
if [[ ! -f "$AUDIT_LOG" ]]; then
echo "No audit log found"
return
fi
echo "🔒 Security Audit Report"
echo "========================"
echo
echo "Recent security events:"
tail -20 "$AUDIT_LOG" | while IFS=, read -r timestamp severity event_type username hostname details; do
echo "[$severity] $timestamp - $event_type by $username@$hostname: $details"
done
echo
echo "Security warnings:"
grep ",WARN," "$AUDIT_LOG" | tail -10
echo
echo "Failed script executions:"
grep "SCRIPT_FAILURE" "$AUDIT_LOG" | tail -10
}
main() {
case "${1:-}" in
log)
log_security_event "$2" "$3" "${4:-INFO}"
;;
exec)
shift
audit_script_execution "$@"
;;
import)
audit_collection_import "$2" "$3"
;;
report)
generate_security_report
;;
*)
echo "Usage: dr security-audit <log|exec|import|report> [args...]"
exit 1
;;
esac
}
main "$@"- Validate all inputs and parameters
- Use allowlists instead of blocklists
- Avoid command injection vulnerabilities
- Never hardcode secrets or credentials
- Use secure temporary file handling
- Implement proper error handling
- Set restrictive file permissions
- Validate collection sources before importing
- Scan collections for security issues
- Use trusted repositories when possible
- Implement access controls for sensitive scripts
- Regular security audits of collections
- Quarantine suspicious collections
- Enable audit logging for all operations
- Implement role-based access control
- Regular security reviews and updates
- Monitor for unusual activity patterns
- Backup and secure audit logs
- Keep DotRun and dependencies updated
- Team Collaboration Best Practices - Secure team workflows
- Performance Optimization - Balancing security and performance
- Architecture Overview - Understanding security architecture
Security is an ongoing process. Regular reviews and updates of security practices are essential for maintaining a secure DotRun environment.