Security Patterns - jvPalma/dotrun GitHub Wiki

Security Patterns

Skill Level: Intermediate to Advanced

Essential security practices and patterns for developing secure DotRun scripts and managing collections safely.

Input Validation and Sanitization

Parameter Validation

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 "$@"

Command Injection Prevention

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 "$@"

Secret Management

Environment-Based Secrets

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 "$@"

Temporary File Security

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 "$@"

Collection Security

Collection Validation

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 "$@"

Access Control

Script Permissions

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 "$@"

Audit and Logging

Security Audit Trail

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 "$@"

Security Best Practices Checklist

Script Development

  • 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

Collection Management

  • 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

System Security

  • 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

Next Steps


Security is an ongoing process. Regular reviews and updates of security practices are essential for maintaining a secure DotRun environment.

⚠️ **GitHub.com Fallback** ⚠️