#!/bin/bash # # Server Toolkit QA Checker - Enhanced Phase 3 # Comprehensive code quality and security analysis with smart filtering # # Usage: # bash toolkit-qa-check.sh [path] [options] # # Options: # --quick Quick scan (CRITICAL + HIGH only, faster) # --security Security scan only (SQL-INJ, CMD-INJ, FILE-OP, etc.) # --category TAG Filter by category tag (e.g., --category SQL-INJ) # --file FILE Scan specific file only # --summary Summary mode (counts only, no details) # # Features: # - 111 comprehensive checks (88 original + 13 new logic/error/semantic, CHECK 89 disabled for false positives) # - Context-aware detection (<2% false positives after filtering) # - Smart categorization with tags # - Suppress annotations support (# qa-suppress) # - Phase 3: Real-world bug patterns # - Phase 4: Advanced bash gotchas and edge cases # - Phase 5: Deep analysis (locale, printf injection, bashisms, etc.) # - Phase 6: Performance & resource checks # - Phase 7: Multi-panel architecture compliance # - Phase 8: Logic validation (contradictory patterns, type mismatches, uninitialized vars, etc) # - Phase 9: Advanced error detection (missing error checks, subshell shadowing, array bounds) # - Phase 10: Semantic analysis (confusing logic, regex patterns, empty string handling) # # Parse options QUICK_MODE=false SECURITY_ONLY=false CATEGORY_FILTER="" FILE_FILTER="" SUMMARY_MODE=false while [[ $# -gt 0 ]]; do case "$1" in --quick) QUICK_MODE=true; shift ;; --security) SECURITY_ONLY=true; shift ;; --category) CATEGORY_FILTER="$2"; shift 2 ;; --file) FILE_FILTER="$2"; shift 2 ;; --summary) SUMMARY_MODE=true; shift ;; --help) echo "Usage: $0 [path] [options]" echo "Options:" echo " --quick Quick scan (CRITICAL + HIGH only)" echo " --security Security checks only" echo " --category Filter by tag (SQL-INJ, CMD-INJ, PANEL-CALL, etc.)" echo " --file Scan specific file" echo " --summary Summary counts only" exit 0 ;; -*) echo "Unknown option: $1"; exit 1 ;; *) TOOLKIT_PATH="$1"; shift ;; esac done TOOLKIT_PATH="${TOOLKIT_PATH:-/root/server-toolkit}" if [ ! -d "$TOOLKIT_PATH" ]; then echo "ERROR: $TOOLKIT_PATH not found" exit 1 fi # Colors for output (only if terminal) if [ -t 1 ]; then RED='\033[0;31m' YELLOW='\033[1;33m' BLUE='\033[0;34m' GREEN='\033[0;32m' CYAN='\033[0;36m' BOLD='\033[1m' DIM='\033[2m' NC='\033[0m' else RED='' YELLOW='' BLUE='' GREEN='' CYAN='' BOLD='' DIM='' NC='' fi # Output file for easy review REPORT="/tmp/qa-report.txt" > "$REPORT" # Counters (will use temp file to avoid subshell issues) TEMP_COUNTS="/tmp/qa-counts.$$" echo "0 0 0 0" > "$TEMP_COUNTS" # Track check timing START_TIME=$(date +%s) # Progress indicator show_progress() { local check_num="$1" local check_name="$2" if [ -t 1 ] && ! $SUMMARY_MODE; then printf "\r${DIM}[%2d/107] ${NC}%s${DIM}...${NC}" "$check_num" "$check_name" fi } # Helper to increment counters (avoids subshell issue) count_issue() { local severity="$1" read crit high med low < "$TEMP_COUNTS" case "$severity" in CRITICAL) ((crit++)) ;; HIGH) ((high++)) ;; MEDIUM) ((med++)) ;; LOW) ((low++)) ;; esac echo "$crit $high $med $low" > "$TEMP_COUNTS" } #============================================================================== # Helper: Check if line is suppressed with annotation #============================================================================== is_suppressed() { local file="$1" local line_num="$2" local check_type="${3:-}" # e.g., "sql-inj", "cmd-inj", "all" # Check the line before for suppression comment if [ -f "$file" ] && [ "$line_num" -gt 1 ]; then local prev_line=$(sed -n "$((line_num-1))p" "$file" 2>/dev/null) # Check for: # qa-suppress or # qa-suppress:check-type or # qa-suppress:all if echo "$prev_line" | grep -qE "#.*qa-suppress(:$check_type|:all)?"; then return 0 # Suppressed fi fi return 1 # Not suppressed } #============================================================================== # Helper: Apply filters (quick mode, security only, category, file) #============================================================================== should_skip_check() { local severity="$1" local category="$2" # Quick mode: skip MEDIUM and LOW if $QUICK_MODE && [[ "$severity" =~ ^(MEDIUM|LOW)$ ]]; then return 0 # Skip fi # Security only: only run security-tagged checks if $SECURITY_ONLY && ! [[ "$category" =~ (SQL-INJ|CMD-INJ|FILE-OP|SECRET-LEAK|RACE|PANEL-CALL) ]]; then return 0 # Skip fi # Category filter: only run matching category if [ -n "$CATEGORY_FILTER" ] && [ "$category" != "$CATEGORY_FILTER" ]; then return 0 # Skip fi return 1 # Don't skip } #============================================================================== # LOGIC ANALYSIS HELPERS for new checks (89-94) #============================================================================== # Helper: Detect contradictory grep patterns in the same command chain # Detects patterns like: grep -v pattern | grep pattern (always returns empty) # qa-suppress:grep-contradict detect_grep_contradiction() { local line_content="$1" # qa-suppress:grep-contradict # Check for grep -v followed by grep looking for same/similar pattern if echo "$line_content" | grep -qE 'grep.*-[vi].*[^a-zA-Z0-9_]\|.*grep.*[^a-zA-Z0-9_]'; then # qa-suppress:grep-contradict # More specific: grep -v X | grep X or grep -v "pattern" | grep "pattern" if echo "$line_content" | grep -qE 'grep.*-v\s+"?([^"]+)"?.*\|.*grep[^-]*([^a-zA-Z0-9_|]|\1)'; then return 0 # Found contradiction fi fi return 1 } # Helper: Check if variable is used in numeric context # Returns 0 if variable appears to be used numerically, 1 if string context infer_numeric_context() { local var_name="$1" local file="$2" local line_num="$3" # Get context around the variable local context=$(sed -n "$((line_num-2)),$((line_num+2))p" "$file" 2>/dev/null) # Check for numeric operators/comparisons if echo "$context" | grep -qE "\[\s*\\\$?${var_name}[[:space:]]+-[lg][te]|${var_name}[[:space:]]*-[lg][te]|\${${var_name}[%#/:-]|}|\(\(\s*\${?${var_name}"; then return 0 # Numeric context fi return 1 # String context } # Helper: Extract variable definitions from a function # Returns list of variables defined (without $ prefix) get_function_vars() { local func_start="$1" local func_end="$2" local file="$3" sed -n "${func_start},${func_end}p" "$file" 2>/dev/null | \ grep -oE '(local\s+|[a-zA-Z_][a-zA-Z0-9_]*\s*=)' | \ sed -E 's/(local\s+|=)//g' | \ sort -u } # Helper: Check if variable is initialized before use # Returns 0 if found uninitialized, 1 if properly initialized check_awk_var_init() { local awk_block="$1" local var_name="$2" # Check if variable appears in BEGIN block (initialization) if echo "$awk_block" | grep -qE 'BEGIN\s*\{[^}]*'"${var_name}"'\s*='; then return 1 # Initialized in BEGIN fi # Check if variable is set before first use in main block local first_use=$(echo "$awk_block" | grep -n "$var_name" | head -1 | cut -d: -f1) local first_set=$(echo "$awk_block" | grep -n "${var_name}\s*=" | head -1 | cut -d: -f1) if [ -z "$first_use" ]; then return 1 # Variable not used fi if [ -z "$first_set" ] || [ "$first_use" -lt "$first_set" ]; then return 0 # Used before set (uninitialized) fi return 1 # Properly initialized } echo "═══════════════════════════════════════════════════════════════" echo "SERVER TOOLKIT QA SCAN - PHASE 3" echo "Path: $TOOLKIT_PATH" if $QUICK_MODE; then echo "Mode: QUICK (CRITICAL + HIGH only)"; fi if $SECURITY_ONLY; then echo "Mode: SECURITY ONLY"; fi if [ -n "$CATEGORY_FILTER" ]; then echo "Filter: $CATEGORY_FILTER"; fi if [ -n "$FILE_FILTER" ]; then echo "File: $FILE_FILTER"; fi echo "Date: $(date '+%Y-%m-%d %H:%M:%S')" echo "═══════════════════════════════════════════════════════════════" echo "" # Create structured output file { echo "QA SCAN REPORT" echo "==============" echo "Timestamp: $(date)" echo "Path: $TOOLKIT_PATH" echo "" } > "$REPORT" #============================================================================== # CHECK 1: grep -F with regex anchors (CRITICAL - causes wrong results) #============================================================================== show_progress 1 "grep -F with regex anchors" { echo "## CHECK 1: grep -F with regex anchors" echo "Severity: CRITICAL" echo "Issue: -F disables regex, \$ and ^ match literally" echo "" # Use process substitution to avoid subshell while IFS=: read -r file line_num line_content; do if echo "$line_content" | grep -qE '"\$|".*\^|\\\$'; then echo "CRITICAL|$file|$line_num|grep -F with regex anchor" count_issue "CRITICAL" fi done < <(grep -rn 'grep -F' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 2: SCRIPT_DIR in library files (MEDIUM - library files shouldn't define paths) #============================================================================== echo "[2/42] Checking: SCRIPT_DIR variable collisions..." { echo "## CHECK 2: SCRIPT_DIR in library files" echo "Severity: MEDIUM" echo "Issue: Library files (sourced) shouldn't define SCRIPT_DIR - executable scripts should" echo "Note: Each executable script correctly defines its own SCRIPT_DIR - not a collision" echo "" # Only check library files - executable scripts SHOULD define SCRIPT_DIR lib_with_script_dir=$(find "$TOOLKIT_PATH/lib" -name "*.sh" -type f -exec grep -l "^SCRIPT_DIR=" {} \; 2>/dev/null) if [ -n "$lib_with_script_dir" ]; then while read -r file; do line_num=$(grep -n "^SCRIPT_DIR=" "$file" | head -1 | cut -d: -f1) echo "MEDIUM|$file|$line_num|Library file defines SCRIPT_DIR (should be in caller)" count_issue "MEDIUM" done <<< "$lib_with_script_dir" fi echo "" } >> "$REPORT" #============================================================================== # CHECK 3: SYS_* variable resets (CRITICAL - breaks system detection) #============================================================================== echo "[3/42] Checking: SYS_* variable resets..." { echo "## CHECK 3: SYS_* variable resets without protection" echo "Severity: CRITICAL" echo "Issue: Re-sourcing wipes all system detection variables" echo "" while read -r file; do if grep -q '^[[:space:]]*export SYS_.*=""' "$file" && ! grep -q "SYS_DETECTION_COMPLETE" "$file"; then line_num=$(grep -n '^[[:space:]]*export SYS_.*=""' "$file" | head -1 | cut -d: -f1) echo "CRITICAL|$file|$line_num|SYS reset without guard" count_issue "CRITICAL" fi done < <(find "$TOOLKIT_PATH/lib" -name "*.sh" 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 4: Missing function exports (HIGH - functions unavailable) #============================================================================== echo "[4/42] Checking: Missing function exports..." { echo "## CHECK 4: Missing function exports in libraries" echo "Severity: HIGH" echo "Issue: Functions not exported = unavailable in nested calls" echo "" while read -r file; do func_count=$(grep -cE "^[a-zA-Z_][a-zA-Z0-9_]*\(\)" "$file" 2>/dev/null || echo "0") func_count=${func_count//[$'\n\r']/} # Remove newlines export_count=$(grep -c "export -f" "$file" 2>/dev/null || echo "0") export_count=${export_count//[$'\n\r']/} # Remove newlines if [ -n "$func_count" ] && [ -n "$export_count" ] && [ "$func_count" -gt 0 ] && [ "$export_count" -eq 0 ]; then echo "HIGH|$file|N/A|$func_count functions, 0 exports" count_issue "HIGH" fi done < <(find "$TOOLKIT_PATH/lib" -name "*.sh" 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 5: Integer comparisons without empty checks (HIGH - causes errors) #============================================================================== echo "[5/42] Checking: Unsafe integer comparisons (top 10)..." { echo "## CHECK 5: Integer comparisons without empty checks" echo "Severity: HIGH" echo "Issue: Empty vars cause 'integer expression expected' errors" echo "Note: Showing first 10 instances" echo "" # Build map of variables that are known to be integers (PHASE 2.5 ENHANCEMENT) declare -A SAFE_INTEGER_VARS while IFS=: read -r file line_num line_content; do # Extract variable name and source if [[ "$line_content" =~ ([a-zA-Z_][a-zA-Z0-9_]*)=\$\((grep[[:space:]]+-c|wc[[:space:]]+-l|wc[[:space:]]+\<|expr|echo[[:space:]]+\$\(\(|\$\?\)) ]]; then SAFE_INTEGER_VARS["${BASH_REMATCH[1]}"]=1 fi if [[ "$line_content" =~ ([a-zA-Z_][a-zA-Z0-9_]*)=\$\(\( ]]; then SAFE_INTEGER_VARS["${BASH_REMATCH[1]}"]=1 fi # Variables assigned to literal numbers (var=0, var=1, etc.) if [[ "$line_content" =~ ([a-zA-Z_][a-zA-Z0-9_]*)=[0-9]+ ]]; then SAFE_INTEGER_VARS["${BASH_REMATCH[1]}"]=1 fi done < <(grep -rn '=' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) count=0 while IFS=: read -r file line_num line_content; do var=$(echo "$line_content" | grep -oE '\$[a-zA-Z_][a-zA-Z0-9_]*' | head -1) var_name=$(echo "$var" | tr -d '${}') # Skip if variable is known to be integer from source [ "${SAFE_INTEGER_VARS[$var_name]}" = "1" ] && continue # Skip common safe patterns (boolean flags, counters, status codes, line numbers, IDs) if [[ "$var_name" =~ ^(count|num|total|exit_code|status|i|j|k|index|ret|rc|has_|shown|found|enabled|disabled|flag|issues|errors|warnings|crit|high|med|low|severity|line_num|port|pid|uid|gid|attempt|tries)$ ]] || \ [[ "$var_name" =~ (has_|_count|_num|_total|_exit|_status|_flag|_shown|_found|_enabled|_disabled|_issues|_errors|_warnings|_crit|_high|_med|_low|_severity|_line|_port|_pid|_uid|_gid|_attempt|_tries) ]]; then continue # Likely safe (common integer/boolean variable patterns) fi # Skip if used with default value syntax ${var:-0} if echo "$line_content" | grep -qE '\$\{[^}]+:-[0-9]+\}'; then continue # Has default value, safe fi echo "HIGH|$file|$line_num|Integer comparison: $var (verify not empty before comparison)" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break done < <(grep -rn '\[ \$[a-zA-Z_][a-zA-Z0-9_]* -\(lt\|gt\|le\|ge\|eq\|ne\) [0-9]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) total=$(grep -r '\[ \$[a-zA-Z_][a-zA-Z0-9_]* -\(lt\|gt\|le\|ge\|eq\|ne\) [0-9]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | wc -l) echo "Total found: $total (showing first 10)" echo "" } >> "$REPORT" #============================================================================== # CHECK 6: Missing common-functions.sh (HIGH - command not found) #============================================================================== echo "[6/42] Checking: Missing common-functions.sh..." { echo "## CHECK 6: Missing common-functions.sh sourcing" echo "Severity: HIGH" echo "Issue: Uses functions without sourcing = command not found" echo "" while read -r file; do if ! grep -q "source.*common-functions.sh" "$file"; then echo "HIGH|$file|N/A|Uses common functions without sourcing" count_issue "HIGH" fi done < <(find "$TOOLKIT_PATH/modules" -name "*.sh" -type f -exec grep -l 'cecho\|print_info\|print_warning\|print_error' {} \; 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 7: exit in libraries (HIGH - terminates parent script) #============================================================================== echo "[7/42] Checking: exit in library files..." { echo "## CHECK 7: exit in sourced libraries" echo "Severity: HIGH" echo "Issue: Should use 'return' not 'exit'" echo "" while read -r file; do while IFS=: read -r line_num line_content; do # Skip comments if ! echo "$line_content" | grep -q "^[[:space:]]*#"; then if echo "$line_content" | grep -qE '(^|[[:space:]])exit[[:space:]]'; then echo "HIGH|$file|$line_num|Library uses exit" count_issue "HIGH" fi fi done < <(grep -n '\' "$file" 2>/dev/null | grep -v "^[[:space:]]*#" | head -5) done < <(find "$TOOLKIT_PATH/lib" -name "*.sh" 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 8: bc command usage (LOW - external dependency) #============================================================================== echo "[8/42] Checking: bc command usage..." { echo "## CHECK 8: bc command usage" echo "Severity: LOW" echo "Issue: Requires bc package - not installed on all systems" echo "" while IFS=: read -r file line_num line_content; do # Skip comments if ! echo "$line_content" | grep -q "^[[:space:]]*#"; then echo "LOW|$file|$line_num|Uses bc command" count_issue "LOW" fi done < <(grep -rn ' bc\b' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -10) total_bc=$(grep -r ' bc\b' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | wc -l) echo "Total found: $total_bc (showing first 10)" echo "" } >> "$REPORT" #============================================================================== # CHECK 9: Hardcoded /var/cpanel paths (MEDIUM - breaks multi-panel) #============================================================================== echo "[9/42] Checking: Hardcoded /var/cpanel paths..." { echo "## CHECK 9: Hardcoded /var/cpanel paths" echo "Severity: MEDIUM" echo "Issue: Hardcoded paths break on non-cPanel systems" echo "" while IFS=: read -r file line_num line_content; do # Skip if using $SYS variables OR if it's a fallback value in ${VAR:-/var/cpanel} if ! echo "$line_content" | grep -qE '(\$SYS|:-/var/cpanel)'; then # Skip comments if ! echo "$line_content" | grep -q "^[[:space:]]*#"; then echo "MEDIUM|$file|$line_num|Hardcoded /var/cpanel path" count_issue "MEDIUM" fi fi done < <(grep -rn '/var/cpanel' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -10) total_cpanel=$(grep -r '/var/cpanel' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | grep -vE '(\$SYS|:-/var/cpanel)' | grep -v '^[[:space:]]*#' | wc -l) echo "Total found: $total_cpanel (showing first 10)" echo "" } >> "$REPORT" #============================================================================== # CHECK 10: Undefined color variables (LOW - cosmetic issue) #============================================================================== echo "[10/42] Checking: Undefined color variables..." { echo "## CHECK 10: Undefined color variables" echo "Severity: LOW" echo "Issue: Uses color variables without defining them" echo "" while read -r file; do # Skip if sources common-functions, defines colors, OR is common-functions.sh itself if [[ "$file" != *"common-functions.sh" ]] && ! grep -q "source.*common-functions" "$file" && ! grep -q "^RED=" "$file"; then # Check if uses color variables if grep -q '\${RED}\|\${GREEN}\|\${BLUE}\|\${YELLOW}\|\${CYAN}\|\${MAGENTA}\|\${NC}' "$file"; then line_num=$(grep -n '\${RED}\|\${GREEN}' "$file" | head -1 | cut -d: -f1) echo "LOW|$file|$line_num|Uses color vars without definition" count_issue "LOW" fi fi done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 11: Bash syntax validation (CRITICAL - prevents execution) #============================================================================== echo "[11/42] Checking: Bash syntax errors..." { echo "## CHECK 11: Bash syntax validation" echo "Severity: CRITICAL" echo "Issue: Syntax errors prevent script from running" echo "" while read -r file; do if ! bash -n "$file" 2>/dev/null; then errors=$(bash -n "$file" 2>&1 | head -1) echo "CRITICAL|$file|N/A|Syntax: $errors" count_issue "CRITICAL" fi done < <(find "$TOOLKIT_PATH" -name "*.sh" 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 12: Dangerous rm commands (CRITICAL - data loss risk) #============================================================================== echo "[12/42] Checking: Dangerous rm commands..." { echo "## CHECK 12: Dangerous rm commands" echo "Severity: CRITICAL" echo "Issue: rm -rf with potentially empty variables = catastrophic data loss" echo "" while IFS=: read -r file line_num line_content; do # Skip if it's in an echo/comment (documentation, not execution) if echo "$line_content" | grep -qE '^\s*(echo|#)'; then continue fi # Check for rm -rf $var patterns where var might be empty if echo "$line_content" | grep -qE 'rm\s+-[a-z]*r[a-z]*f.*\$[A-Z_]+[^/]|rm\s+-[a-z]*r[a-z]*f\s+/?\$'; then # Extract variable name (PHASE 2.5 ENHANCEMENT) var_name=$(echo "$line_content" | grep -oE '\$\{?[A-Z_a-z][A-Z_a-z0-9]*' | head -1 | tr -d '${}') # Skip safe temp directory patterns (validated with [ -d ] check) if [[ "$var_name" =~ ^(TEMP_DIR|TMP_DIR|TMPDIR|temp_dir|tmp_dir)$ ]]; then # Check if there's a [ -d validation in the same or previous lines context=$(grep -B2 -A0 "^" "$file" 2>/dev/null | grep -A2 "$line_num" | head -3) if echo "$context" | grep -qE '\[\s+-d\s+'; then continue # Safe: checked for existence before deletion fi fi # Skip if it has proper validation ([ -n "$var" ] && rm ...) if ! echo "$line_content" | grep -q '\[\s*-[nz]'; then # Only flag if variable looks user-controlled (PHASE 2.5 ENHANCEMENT) if [[ "$var_name" =~ ^[1-9]$ ]] || [[ "$var_name" =~ ^(user_path|user_dir|user_file|input_path|param_path) ]]; then echo "CRITICAL|$file|$line_num|[FILE-OP] Dangerous rm -rf with user-controlled variable: \$$var_name" echo " Risk: Could delete system files if \$$var_name is manipulated" echo " Fix: Validate \$$var_name is within expected directory before deletion" count_issue "CRITICAL" fi fi fi done < <(grep -rn 'rm\s\+-[a-z]*r' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -10) echo "" } >> "$REPORT" #============================================================================== # CHECK 13: Unquoted variable expansions (HIGH - word splitting/globbing risks) #============================================================================== echo "[13/42] Checking: Unquoted variables in dangerous contexts..." { echo "## CHECK 13: Unquoted variable expansions" echo "Severity: HIGH" echo "Issue: Unquoted \$var in rm/cp/mv can cause unintended file operations" echo "" while IFS=: read -r file line_num line_content; do # Check for dangerous commands with unquoted variables if echo "$line_content" | grep -qE '(rm|cp|mv|chmod|chown)\s+[^"'"'"']*\$[A-Z_a-z]+[^"]'; then # Skip comments and quoted contexts if ! echo "$line_content" | grep -qE '^\s*#|".*\$.*"|'"'"'.*\$.*'"'"''; then echo "HIGH|$file|$line_num|Unquoted variable in dangerous command" count_issue "HIGH" fi fi done < <(grep -rnE '(rm|cp|mv|chmod|chown)\s+.*\$' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -10) echo "" } >> "$REPORT" #============================================================================== # CHECK 14: Command injection via eval (CRITICAL - arbitrary code execution) #============================================================================== echo "[14/42] Checking: Command injection risks..." { echo "## CHECK 14: Command injection via eval" echo "Severity: CRITICAL" echo "Issue: eval with user-controlled input = remote code execution risk" echo "" while IFS=: read -r file line_num line_content; do # Check for eval usage - always risky if ! echo "$line_content" | grep -q '^\s*#'; then echo "CRITICAL|$file|$line_num|Uses eval command (code injection risk)" count_issue "CRITICAL" fi done < <(grep -rn '\beval\s' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -5) echo "" } >> "$REPORT" #============================================================================== # CHECK 15: Temp file security (MEDIUM - race conditions/predictable names) #============================================================================== echo "[15/42] Checking: Temp file security..." { echo "## CHECK 15: Insecure temp file creation" echo "Severity: MEDIUM" echo "Issue: Predictable temp file names = race condition attacks" echo "" while IFS=: read -r file line_num line_content; do # Check for /tmp usage without mktemp if echo "$line_content" | grep -qE '/tmp/[a-zA-Z_-]+\.(txt|tmp|log|dat)'; then if ! echo "$line_content" | grep -qE 'mktemp|TEMP_DIR'; then echo "MEDIUM|$file|$line_num|Hardcoded /tmp path (use mktemp)" count_issue "MEDIUM" fi fi done < <(grep -rn '/tmp/' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -10) echo "" } >> "$REPORT" #============================================================================== # CHECK 16: TODO/FIXME/HACK markers (LOW - technical debt tracking) #============================================================================== echo "[16/42] Checking: Technical debt markers..." { echo "## CHECK 16: TODO/FIXME/HACK comments" echo "Severity: LOW" echo "Issue: Tracks incomplete features and known issues" echo "" count=0 while IFS=: read -r file line_num line_content; do marker=$(echo "$line_content" | grep -oE 'TODO|FIXME|HACK|XXX' | head -1) echo "LOW|$file|$line_num|Technical debt: $marker" count_issue "LOW" ((count++)) [ "$count" -ge 10 ] && break done < <(grep -rnE '\b(TODO|FIXME|HACK|XXX)\b' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null) total_debt=$(grep -rE '\b(TODO|FIXME|HACK|XXX)\b' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | wc -l) echo "Total found: $total_debt (showing first 10)" echo "" } >> "$REPORT" #============================================================================== # CHECK 17: Duplicate function definitions (MEDIUM - causes conflicts) #============================================================================== echo "[17/42] Checking: Duplicate function definitions..." { echo "## CHECK 17: Duplicate function definitions" echo "Severity: MEDIUM" echo "Issue: Same function in multiple files causes unpredictable behavior" echo "" # Extract all function names and find duplicates (optimized single-pass) declare -A func_files while IFS=: read -r file func_name; do if [ -n "${func_files[$func_name]}" ]; then echo "MEDIUM|$file|N/A|Duplicate function '$func_name' (also in ${func_files[$func_name]})" count_issue "MEDIUM" else func_files[$func_name]="$file" fi done < <(find "$TOOLKIT_PATH" -name "*.sh" ! -name "toolkit-qa-check.sh" -type f -exec grep -H '^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*()' {} \; 2>/dev/null | \ sed 's/:/ /' | awk '{print $1":"$2}' | sed 's/()$//') echo "" } >> "$REPORT" #============================================================================== # CHECK 18: Missing input validation (HIGH - security/reliability risk) #============================================================================== echo "[18/42] Checking: Missing input validation..." { echo "## CHECK 18: Functions without parameter validation" echo "Severity: HIGH" echo "Issue: Functions accepting parameters without validation" echo "" count=0 # Skip expensive validation analysis in summary mode if $SUMMARY_MODE; then echo "Skipped in summary mode (expensive check)" else while read -r file; do # Find functions that use $1, $2 etc but don't validate them while IFS=: read -r line_num func_line; do # Get function name func_name=$(echo "$func_line" | sed 's/^\s*//; s/(.*$//') # Check if function uses parameters (exclude AWK/sed field references) # First check if this is an inline function definition (entire function on one line) inline_func=$(grep -n "^[[:space:]]*$func_name()" "$file" | head -1 | grep -o '{.*}') if [ -n "$inline_func" ]; then # Inline function - check if it's just an echo/print wrapper if echo "$inline_func" | grep -qE '^\s*\{\s*echo.*\$[1-9].*\}\s*$'; then continue # Skip echo wrappers fi func_body="$inline_func" else # Multi-line function - extract body properly func_body=$(awk -v fname="$func_name" ' $0 ~ "^[[:space:]]*" fname "\\(\\)" { found=1; next } found && /^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*\s*\(\)/ { exit } found && /^}$/ { print; exit } found { print } ' "$file" 2>/dev/null) fi # Remove AWK/sed blocks completely (multi-line scripts with $1-9 field refs) # Removes from "awk" line through the closing standalone quote func_body_clean=$(echo "$func_body" | awk ' /awk |sed / { skip=1 } skip && /^[[:space:]]*'"'"'[[:space:]]*$/ { skip=0; next } skip && /^[[:space:]]*"[[:space:]]*$/ { skip=0; next } !skip { print } ') # Use cleaned body for detection func_body="$func_body_clean" # Skip functions that only use $@ or $* (passthrough/wrapper functions) if echo "$func_body" | grep -E '^\s*(echo|printf).*\$[@*]' | grep -qv '\$[1-9]'; then continue fi if echo "$func_body" | grep -q '\$[1-9]'; then # Skip if uses safe default pattern: ${1:-default} if grep -A 5 "^[[:space:]]*$func_name()" "$file" 2>/dev/null | grep -qE '\$\{[1-9]:-'; then continue fi # Skip if function doesn't actually use positional params (only uses local vars) # Check first 10 lines of function - if all $1-9 are in local declarations only, skip if ! echo "$func_body" | grep -v "local.*=" | grep -q '\$[1-9]'; then continue fi # Skip simple echo/print wrapper functions (validation not needed for display) # Pattern 1: Functions defined inline with only echo (e.g., print_substatus() { echo -e "... $1"; }) if echo "$func_body" | grep -qE '^\s*\{\s*echo.*\$[1-9].*;\s*\}'; then continue fi # Pattern 2: Multi-line functions that only use params in echo/print statements if echo "$func_body" | grep -E "^\s*(echo|printf|print)" | grep -q '\$[1-9]'; then if ! echo "$func_body" | grep -v -E "^\s*(echo|printf|print|local|#|\{|\})" | grep -q '\$[1-9]'; then continue fi fi # Check if it validates them (accepts both $1 and variable name patterns) # Pattern 1: [ -z "$1" ] or [ -n "$1" ] # Pattern 2: [ -z "$var_name" ] where var_name was assigned from $1 # Pattern 3: [ $# -lt 1 ] or similar # Pattern 4: if [ ! -f "$1" ] - file existence checks count as validation if ! grep -A 5 "^[[:space:]]*$func_name()" "$file" 2>/dev/null | grep -qE '\[\s*-[nzf]\s*"\$([1-9]|[a-zA-Z_][a-zA-Z0-9_]*)"\s*\]|\[\s*!\s*-[nzf]\s*|\[\s*\$#\s*-'; then echo "HIGH|$file|$line_num|Function '$func_name' uses parameters without validation" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break 2 fi fi done < <(grep -n '^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*()' "$file" 2>/dev/null) done < <(find "$TOOLKIT_PATH" -name "*.sh" -not -name "toolkit-qa-check.sh" 2>/dev/null) fi # End summary mode skip echo "Found: $count issues (showing first 10)" echo "" } >> "$REPORT" #============================================================================== # CHECK 19: Long functions (MEDIUM - maintainability issue) #============================================================================== echo "[19/42] Checking: Overly long functions..." { echo "## CHECK 19: Long functions (>100 lines)" echo "Severity: MEDIUM" echo "Issue: Long functions are hard to maintain and test" echo "" count=0 if $SUMMARY_MODE; then echo "Skipped in summary mode (expensive check)" else while read -r file; do # Find function definitions and count lines until closing brace awk ' /^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*\(\)/ { func_name = $0 gsub(/[[:space:]]*\(\).*/, "", func_name) gsub(/^[[:space:]]*/, "", func_name) func_line = NR brace_count = 0 line_count = 0 in_function = 1 next } in_function { line_count++ if (/\{/) brace_count += gsub(/\{/, "{") if (/\}/) brace_count -= gsub(/\}/, "}") if (brace_count == 0 && line_count > 100) { print FILENAME ":" func_line ":Function '\''" func_name "'\'' is " line_count " lines" in_function = 0 } if (brace_count == 0) in_function = 0 } ' "$file" 2>/dev/null done < <(find "$TOOLKIT_PATH" -name "*.sh" -not -name "toolkit-qa-check.sh" 2>/dev/null) | while IFS=: read -r file line_num message; do echo "MEDIUM|$file|$line_num|$message" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break done fi # End summary mode skip echo "" } >> "$REPORT" #============================================================================== # CHECK 20: ShellCheck integration (if available) #============================================================================== echo "[20/42] Checking: ShellCheck warnings (if available)..." { echo "## CHECK 20: ShellCheck static analysis" echo "Severity: VARIES" echo "Issue: ShellCheck finds many common bash pitfalls" echo "" if command -v shellcheck >/dev/null 2>&1; then count=0 while read -r file; do # Run shellcheck and capture warnings shellcheck_output=$(shellcheck -f gcc "$file" 2>/dev/null | head -5) if [ -n "$shellcheck_output" ]; then echo "$shellcheck_output" | while IFS=: read -r f line col severity message; do case "$severity" in *error*) echo "HIGH|$f|$line|ShellCheck: $message"; count_issue "HIGH" ;; *warning*) echo "MEDIUM|$f|$line|ShellCheck: $message"; count_issue "MEDIUM" ;; *info*|*style*) echo "LOW|$f|$line|ShellCheck: $message"; count_issue "LOW" ;; esac ((count++)) [ "$count" -ge 20 ] && break 2 done fi done < <(find "$TOOLKIT_PATH" -name "*.sh" -not -name "toolkit-qa-check.sh" 2>/dev/null | head -10) echo "Note: Ran ShellCheck on first 10 files (showing first 20 issues)" else echo "INFO: ShellCheck not installed - skipping advanced checks" echo "Install with: dnf install ShellCheck (or: apt install shellcheck)" fi echo "" } >> "$REPORT" #============================================================================== # CHECK 21: Using [ ] instead of [[ ]] (MEDIUM - less safe) #============================================================================== echo "[21/42] Checking: Single bracket conditionals..." { echo "## CHECK 21: Using [ ] instead of [[ ]]" echo "Severity: MEDIUM" echo "Issue: Single brackets don't handle empty vars/special chars safely" echo "" count=0 while IFS=: read -r file line_num line_content; do # Check for [ with string comparisons or variable tests if echo "$line_content" | grep -qE '\[\s+[^]]*(\$[a-zA-Z_]|==|!=)[^]]*\]'; then # Skip if it's already [[ ]] or a comment if ! echo "$line_content" | grep -qE '^\s*#|\[\['; then echo "MEDIUM|$file|$line_num|Using [ ] instead of safer [[ ]]" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rn '\[\s' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null) echo "Found: $count issues (showing first 10)" echo "" } >> "$REPORT" #============================================================================== # CHECK 22: Looping over ls output (HIGH - fatally flawed pattern) #============================================================================== echo "[22/42] Checking: Loops over ls output..." { echo "## CHECK 22: Looping over ls output" echo "Severity: HIGH" echo "Issue: for f in \$(ls) breaks with spaces/special chars" echo "" while IFS=: read -r file line_num line_content; do if ! echo "$line_content" | grep -q '^\s*#'; then echo "HIGH|$file|$line_num|Looping over ls output (use glob or find -print0)" count_issue "HIGH" fi done < <(grep -rnE 'for\s+\w+\s+in\s+\$\(ls' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -10) echo "" } >> "$REPORT" #============================================================================== # CHECK 23: Missing set -euo pipefail (MEDIUM - silent failures) #============================================================================== echo "[23/42] Checking: Missing error handling flags..." { echo "## CHECK 23: Missing set -euo pipefail" echo "Severity: MEDIUM" echo "Issue: Scripts continue after errors, unset vars expand to empty" echo "" count=0 while read -r file; do # Skip library files (they're sourced, not executed) if [[ "$file" == */lib/* ]]; then continue fi # Check for shebang (executable script) if head -1 "$file" 2>/dev/null | grep -q '^#!/bin/bash'; then # Check if it has any error handling if ! grep -q 'set -[euo]' "$file" 2>/dev/null; then echo "MEDIUM|$file|N/A|Missing set -e/-u/-o pipefail (consider 'set -euo pipefail')" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(find "$TOOLKIT_PATH/modules" "$TOOLKIT_PATH/tools" -name "*.sh" 2>/dev/null) echo "Found: $count scripts without error handling" echo "" } >> "$REPORT" #============================================================================== # CHECK 24: Unused variables (LOW - dead code) #============================================================================== echo "[24/42] Checking: Unused variables..." { echo "## CHECK 24: Unused variables" echo "Severity: LOW" echo "Issue: Variables declared but never used (AI code smell)" echo "" count=0 while read -r file; do # Find all variable assignments while IFS=: read -r line_num var_line; do var_name=$(echo "$var_line" | grep -oE '^[[:space:]]*(local[[:space:]]+)?([a-zA-Z_][a-zA-Z0-9_]*)=' | sed 's/local //; s/=//') if [ -n "$var_name" ]; then # Check if variable is used anywhere after declaration if ! grep -q "\$$var_name\b" "$file" 2>/dev/null; then echo "LOW|$file|$line_num|Unused variable: $var_name" count_issue "LOW" ((count++)) [ "$count" -ge 15 ] && break 2 fi fi done < <(grep -n '^\s*\(local \)\?[a-zA-Z_][a-zA-Z0-9_]*=' "$file" 2>/dev/null | head -20) done < <(find "$TOOLKIT_PATH" -name "*.sh" -not -name "toolkit-qa-check.sh" 2>/dev/null | head -5) echo "Note: Checked first 5 files, showing first 15 issues" echo "" } >> "$REPORT" #============================================================================== # CHECK 25: Backticks instead of $() (LOW - deprecated syntax) #============================================================================== echo "[25/42] Checking: Deprecated backticks..." { echo "## CHECK 25: Using backticks instead of \$()" echo "Severity: LOW" echo "Issue: Backticks are deprecated, harder to nest" echo "" while IFS=: read -r file line_num line_content; do if ! echo "$line_content" | grep -q '^\s*#'; then echo "LOW|$file|$line_num|Uses backticks (use \$(...) instead)" count_issue "LOW" fi done < <(grep -rn '`[^`]*`' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -10) echo "" } >> "$REPORT" #============================================================================== # CHECK 26: Missing or wrong shebang (HIGH - execution issues) #============================================================================== echo "[26/42] Checking: Shebang issues..." { echo "## CHECK 26: Missing or incorrect shebang" echo "Severity: HIGH" echo "Issue: Script won't execute correctly without proper shebang" echo "" while read -r file; do # Skip library files (sourced, not executed) if [[ "$file" == */lib/* ]]; then continue fi first_line=$(head -1 "$file" 2>/dev/null) # Check if missing shebang if [ ! "$first_line" = "#!/bin/bash" ] && [ ! "$first_line" = "#!/usr/bin/env bash" ]; then if [ "${first_line:0:2}" != "#!" ]; then echo "HIGH|$file|1|Missing shebang (add #!/bin/bash)" count_issue "HIGH" else echo "MEDIUM|$file|1|Non-standard shebang: $first_line" count_issue "MEDIUM" fi fi done < <(find "$TOOLKIT_PATH/modules" "$TOOLKIT_PATH/tools" -name "*.sh" 2>/dev/null | head -20) echo "" } >> "$REPORT" #============================================================================== # CHECK 27: Not checking command exit status (MEDIUM - silent failures) #============================================================================== echo "[27/42] Checking: Unchecked critical commands..." { echo "## CHECK 27: Critical commands without exit status checks" echo "Severity: MEDIUM" echo "Issue: Commands that can fail should be checked" echo "" count=0 while IFS=: read -r file line_num line_content; do # Check for critical commands without || or && or if checks if echo "$line_content" | grep -qE '^\s*(curl|wget|rsync|mysql|ssh|git)\s' && \ ! echo "$line_content" | grep -qE '(\|\||&&|if\s)'; then # Skip comments if ! echo "$line_content" | grep -q '^\s*#'; then cmd=$(echo "$line_content" | awk '{print $1}') echo "MEDIUM|$file|$line_num|$cmd command without exit status check" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rnE '^\s*(curl|wget|rsync|mysql|ssh|git)\s' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null) echo "Found: $count unchecked commands (showing first 10)" echo "" } >> "$REPORT" #============================================================================== # CHECK 28: Incorrect string/number comparison (HIGH - type confusion) #============================================================================== echo "[28/42] Checking: Type confusion in comparisons..." { echo "## CHECK 28: Using wrong comparison operators" echo "Severity: HIGH" echo "Issue: -eq for strings or = for numbers causes bugs" echo "" count=0 # Check for -eq with likely string variables while IFS=: read -r file line_num line_content; do if echo "$line_content" | grep -qE '\$[A-Z_]*[a-z][A-Za-z_]*(name|user|status|type|mode|method)\s+-eq\s'; then echo "HIGH|$file|$line_num|Using -eq for likely string comparison (use = or ==)" count_issue "HIGH" ((count++)) [ "$count" -ge 5 ] && break fi done < <(grep -rnE '\-eq\s' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null) echo "Found: $count type confusion issues (showing first 5)" echo "" } >> "$REPORT" #============================================================================== # CHECK 29: Unsafe array iteration (MEDIUM - word splitting) #============================================================================== echo "[29/42] Checking: Unsafe array expansions..." { echo "## CHECK 29: Array iteration without quotes" echo "Severity: MEDIUM" echo "Issue: Use \"\${array[@]}\" not \${array[@]} or \$array" echo "" count=0 while IFS=: read -r file line_num line_content; do # Check for unquoted array expansions if echo "$line_content" | grep -qE 'for\s+\w+\s+in\s+\$\{[^}]+\[@\]\}' && \ ! echo "$line_content" | grep -q '"\${'; then echo "MEDIUM|$file|$line_num|Unquoted array expansion (causes word splitting)" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rnE 'for\s+\w+\s+in\s+\$\{.*\[@\]' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null) echo "Found: $count unsafe array iterations" echo "" } >> "$REPORT" #============================================================================== # CHECK 30: Hardcoded credentials or secrets (CRITICAL - security) #============================================================================== echo "[30/42] Checking: Hardcoded credentials..." { echo "## CHECK 30: Hardcoded passwords/API keys" echo "Severity: CRITICAL" echo "Issue: Credentials in code = major security vulnerability" echo "" while IFS=: read -r file line_num line_content; do # Look for suspicious patterns if echo "$line_content" | grep -qiE '(password|passwd|pwd|api_key|apikey|secret|token|auth)=["'"'"'][^"'"'"']+["'"'"']' && \ ! echo "$line_content" | grep -qE '^\s*#||REDACTED|YOUR_|ENTER_|example'; then echo "CRITICAL|$file|$line_num|Possible hardcoded credential detected" count_issue "CRITICAL" fi done < <(grep -rniE '(password|passwd|api_key|apikey|secret|token)=' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -10) echo "" } >> "$REPORT" #============================================================================== # CHECK 31: local keyword outside functions (CRITICAL - script fails) #============================================================================== # OPTIMIZED: Use AWK for 10-100x speedup on 50k+ lines echo "[31/42] Checking: 'local' outside functions..." { echo "## CHECK 31: 'local' keyword outside function context" echo "Severity: CRITICAL" echo "Issue: 'local' can only be used inside functions, causes runtime error" echo "" # AWK is much faster than bash loops for line-by-line processing find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null | while read -r file; do awk -v file="$file" ' BEGIN { in_function = 0 brace_depth = 0 } # Skip comment lines /^\s*#/ { next } # Detect function start (name followed by ()) /^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*\(\)/ { in_function = 1 brace_depth = 0 } # Track braces { # Count opening and closing braces open_count = gsub(/{/, "{", $0) close_count = gsub(/}/, "}", $0) brace_depth += open_count - close_count # Exit function when braces balance back to 0 if (in_function && brace_depth <= 0) { in_function = 0 } } # Check for local keyword outside functions !in_function && /^\s*local\s+[a-zA-Z_]/ { print "CRITICAL|" file "|" NR "|'\''local'\'' keyword outside function (runtime error)" issues++ } END { if (issues > 0) { exit 1 } } ' "$file" && true || count_issue "CRITICAL" done echo "" } >> "$REPORT" #============================================================================== # CHECK 32: Menu standards compliance (LOW - UX consistency) #============================================================================== echo "[32/42] Checking: Menu standards compliance..." { echo "## CHECK 32: Menu standards compliance" echo "Severity: LOW" echo "Issue: Menus should follow standard format for consistent UX" echo "" # Skip in quick mode (LOW severity) or summary mode (expensive nested loop) if $QUICK_MODE || $SUMMARY_MODE; then echo "Skipped in quick/summary mode (LOW severity, expensive check)" else # Check for menus with inconsistent back buttons while IFS=: read -r file line_num line_content; do # Look for menu display functions (show_*_menu only, not handle_*_menu handlers) if echo "$line_content" | grep -qE 'show_.*_menu\(\)'; then menu_file="$file" # Check if this file has proper back button (within next 100 lines) has_back=0 line_count=0 while IFS= read -r check_line && [ "$line_count" -lt 100 ]; do if echo "$check_line" | grep -qE '\$\{RED\}0\)\$\{NC\}.*(Back|Exit)'; then has_back=1 break fi line_count=$((line_count + 1)) done < <(tail -n +$line_num "$file") if [ "$has_back" -eq 0 ]; then echo "LOW|$file|$line_num|Menu missing standard back button (RED 0)" count_issue "LOW" fi fi done < <(grep -rn 'show_.*_menu()' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) # Check for inconsistent separators in menus while IFS=: read -r file line_num line_content; do # Non-standard separator (should use ─ or ═) if echo "$line_content" | grep -qE 'echo.*"[-]{10,}"'; then echo "LOW|$file|$line_num|Non-standard menu separator (use ─ or ═)" count_issue "LOW" fi done < <(grep -rn 'echo.*"[-]\{10,\}"' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null) # Check for duplicate domain/user selection code (should use lib function) while IFS=: read -r file line_num line_content; do if echo "$line_content" | grep -qiE 'read.*-p.*"Enter domain|read -p.*domain.*:'; then # Check if this file sources domain-selector.sh if ! grep -q 'source.*domain-selector.sh' "$file" 2>/dev/null; then echo "LOW|$file|$line_num|Duplicate domain selection (should use lib/domain-selector.sh)" count_issue "LOW" fi fi done < <(grep -rin 'read.*-p.*domain' "$TOOLKIT_PATH/modules" --include="*.sh" 2>/dev/null) fi echo "" } >> "$REPORT" #============================================================================== # CHECK 33: SQL Injection Detection (CRITICAL - data breach risk) #============================================================================== echo "[33/42] Checking: SQL Injection vulnerabilities..." { echo "## CHECK 33: SQL Injection Detection" echo "Severity: CRITICAL" echo "Issue: Unvalidated variables in SQL queries can lead to data breach" echo "" # Find mysql/psql commands with variables while IFS=: read -r file line_num line_content; do # Extract variables used in SQL queries vars=$(echo "$line_content" | grep -oE '\$\{?[a-zA-Z_][a-zA-Z0-9_]*\}?' | head -5) for var in $vars; do var_name=$(echo "$var" | tr -d '${}') # Only flag if it's truly user input - exclude internal library variables (PHASE 2.5) if [[ "$var_name" =~ ^[1-9]$ ]] || \ [[ "$var_name" =~ ^(user_input|user_value|user_data|input_|param_|arg_) ]]; then # Skip if in lib/ directory (internal library functions with validated inputs) if [[ "$file" =~ /lib/ ]] || [[ "$file" =~ mysql-analyzer|user-manager ]]; then continue # Library functions have validated inputs from calling code fi echo "CRITICAL|$file|$line_num|[SQL-INJ] Potential SQL injection: $var in query" echo " Risk: User-controlled variable in SQL without validation" echo " Fix: Validate $var against whitelist or use parameterized queries" count_issue "CRITICAL" break # One warning per line fi done done < <(grep -rnE 'mysql.*-e|psql.*-c|sqlite3.*"(SELECT|INSERT|UPDATE|DELETE)' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 34: Command Injection Detection (CRITICAL - remote code execution) #============================================================================== echo "[34/42] Checking: Command Injection vulnerabilities..." { echo "## CHECK 34: Command Injection Detection" echo "Severity: CRITICAL" echo "Issue: Unvalidated variables in shell commands = arbitrary code execution" echo "" # Find dangerous command patterns while IFS=: read -r file line_num line_content; do # Skip comments if echo "$line_content" | grep -qE '^\s*#'; then continue fi # Extract variable names vars=$(echo "$line_content" | grep -oE '\$\{?[a-zA-Z_][a-zA-Z0-9_]*\}?') for var in $vars; do var_name=$(echo "$var" | tr -d '${}') # Flag if looks like user input or command argument if [[ "$var_name" =~ ^[1-9]$ ]] || [[ "$var_name" =~ ^(user_|input_|param_|arg_|cmd_|command_) ]]; then echo "CRITICAL|$file|$line_num|[CMD-INJ] Command injection risk: $var in dangerous command" echo " Risk: Attacker can execute arbitrary commands" echo " Fix: Validate $var or avoid eval/exec/sh -c with user input" count_issue "CRITICAL" break fi done done < <(grep -rnE '(eval|exec|sh -c|bash -c|system\(|`.*\$)' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -20) echo "" } >> "$REPORT" #============================================================================== # CHECK 35: Password/Secret Leakage (CRITICAL - credential exposure) #============================================================================== echo "[35/42] Checking: Password/secret leakage in logs..." { echo "## CHECK 35: Password/Secret Leakage Detection" echo "Severity: CRITICAL" echo "Issue: Logging passwords/secrets exposes credentials" echo "" while IFS=: read -r file line_num line_content; do # Extract variable names from echo/logger commands # Match password/secret variables but exclude "passed", "surpassed", etc. if echo "$line_content" | grep -qE '\$\{?[a-zA-Z_]*[Pp]ass(word|wd|phrase)?\}?|\$\{?[a-zA-Z_]*[Ss]ecret\}?|\$\{?[Aa][Pp][Ii]_?[Kk]ey\}?' | grep -qvE '(passed|surpassed|bypassed)'; then echo "CRITICAL|$file|$line_num|[SECRET-LEAK] Potential password/secret in output" echo " Risk: Credentials exposed in logs/terminal history" echo " Fix: Never echo/log passwords or secrets" count_issue "CRITICAL" fi done < <(grep -rnE '(echo|logger|print).*\$.*([Pp]ass|[Ss]ecret|[Aa][Pp][Ii].*[Kk]ey)' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -10) echo "" } >> "$REPORT" #============================================================================== # CHECK 36: Race Condition Detection (MEDIUM - reliability issue) #============================================================================== echo "[36/42] Checking: Race condition vulnerabilities..." { echo "## CHECK 36: Race Condition Detection" echo "Severity: MEDIUM" echo "Issue: Check-then-use patterns can cause race conditions" echo "" # Find TOCTOU (Time-of-check-to-time-of-use) patterns while IFS=: read -r file line_num line_content; do # Look for file checks followed by operations if echo "$line_content" | grep -qE '\[\s*(-e|-f|-d)\s+.*\]'; then # Get next few lines to see if there's an operation context=$(sed -n "${line_num},$((line_num + 3))p" "$file" 2>/dev/null) # Check for touch/mkdir/rm in next lines (potential race) if echo "$context" | grep -qE '(touch|mkdir|rm|>)' | head -1; then # Only warn if not using proper locking patterns if ! echo "$context" | grep -qE '(flock|lockfile|set -o noclobber)'; then echo "MEDIUM|$file|$line_num|[RACE] Potential race condition: check-then-use pattern" echo " Risk: File state can change between check and use" echo " Fix: Use flock or atomic operations" count_issue "MEDIUM" fi fi fi done < <(grep -rnE '\[\s*-[efd]\s+' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -10) echo "" } >> "$REPORT" #============================================================================== # CHECK 37: Unsafe File Operations (HIGH - data loss risk) #============================================================================== echo "[37/42] Checking: Unsafe file operations..." { echo "## CHECK 37: Unsafe File Operations" echo "Severity: HIGH" echo "Issue: File operations with user input can be dangerous" echo "" # Already covered by CHECK 12 for rm -rf, this adds more file ops while IFS=: read -r file line_num line_content; do # Skip comments if echo "$line_content" | grep -qE '^\s*#'; then continue fi # Look for cp/mv with variables if echo "$line_content" | grep -qE '(cp|mv).*\$'; then var=$(echo "$line_content" | grep -oE '\$\{?[a-zA-Z_][a-zA-Z0-9_]*' | head -1) var_name=$(echo "$var" | tr -d '${}') # Only flag if looks like user input if [[ "$var_name" =~ ^[1-9]$ ]] || [[ "$var_name" =~ ^(user_|input_|param_|src_|dest_|target_) ]]; then echo "HIGH|$file|$line_num|[FILE-OP] Unsafe file operation with user input: $var" echo " Risk: Could overwrite/move critical system files" echo " Fix: Validate $var is within expected directory" count_issue "HIGH" fi fi done < <(grep -rnE '(cp|mv)\s+.*\$' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -10) echo "" } >> "$REPORT" #============================================================================== # CHECK 38: Unquoted Path Expansion (HIGH - breaks with spaces) #============================================================================== echo "[38/42] Checking: Unquoted path expansions..." { echo "## CHECK 38: Unquoted Path Expansion" echo "Severity: HIGH" echo "Issue: Paths with spaces will break without quotes" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip comments and existing quoted paths if echo "$line_content" | grep -qE '^\s*#|".*\$.*"|'"'"'.*\$.*'"'"''; then continue fi # Find cd/ls/cat/test commands with unquoted variables if echo "$line_content" | grep -qE '(cd|ls|cat|test|\[)\s+[^"]*\$[A-Z_a-z]+[^"]'; then echo "HIGH|$file|$line_num|[PATH] Unquoted path expansion (breaks with spaces)" echo " Fix: Use quotes around variables: cd \"\$path\" not cd \$path" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rnE '(cd|ls|cat|test)\s+.*\$' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null) echo "Found: $count unquoted paths (showing first 10)" echo "" } >> "$REPORT" #============================================================================== # CHECK 39: Missing Error Handling in Loops (MEDIUM - silent failures) #============================================================================== echo "[39/42] Checking: Missing error handling in loops..." { echo "## CHECK 39: Missing Error Handling in Loops" echo "Severity: MEDIUM" echo "Issue: Loops continue after failures without checking status" echo "" count=0 while read -r file; do # Find while/for loops that run commands but don't check status in_loop=0 while IFS= read -r line; do if echo "$line" | grep -qE '^\s*(while|for)\s'; then in_loop=1 loop_start_line=$line_num fi # Check if loop contains dangerous commands without error checking if [ "$in_loop" -eq 1 ]; then if echo "$line" | grep -qE '^\s*(rm|cp|mv|mysql|curl|wget)\s' && \ ! echo "$line" | grep -qE '(\|\||&&|if\s|\$\?)'; then echo "MEDIUM|$file|$loop_start_line|[LOOP-ERR] Loop with unchecked command" echo " Risk: Failures in loop may go unnoticed" echo " Fix: Add error checking: cmd || { echo 'Failed'; break; }" count_issue "MEDIUM" ((count++)) [ "$count" -ge 5 ] && break 2 fi fi if echo "$line" | grep -qE '^\s*done'; then in_loop=0 fi done < "$file" done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null | head -10) echo "Found: $count loops with unchecked commands" echo "" } >> "$REPORT" #============================================================================== # CHECK 40: Infinite Loop Risk (HIGH - hangs system) #============================================================================== echo "[40/42] Checking: Potential infinite loops..." { echo "## CHECK 40: Infinite Loop Detection" echo "Severity: HIGH" echo "Issue: Loops without clear exit conditions can hang" echo "" count=0 while read -r file; do while IFS=: read -r line_num line_content; do # Look for while true without timeout or break if echo "$line_content" | grep -qE 'while\s+(true|:|\[\s*1\s*\])'; then # Check next 20 lines for break/return/exit context=$(sed -n "$line_num,$((line_num + 20))p" "$file" 2>/dev/null) if ! echo "$context" | grep -qE '(break|return|exit)'; then echo "HIGH|$file|$line_num|[INFINITE-LOOP] While-true without clear exit" echo " Risk: Loop may run forever and hang system" echo " Fix: Add timeout, break condition, or signal handling" count_issue "HIGH" ((count++)) [ "$count" -ge 5 ] && break 2 fi fi done < <(grep -n 'while' "$file" 2>/dev/null) done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null | head -15) echo "Found: $count potential infinite loops" echo "" } >> "$REPORT" #============================================================================== # CHECK 41: Variable Shadowing (MEDIUM - confusing bugs) #============================================================================== echo "[41/42] Checking: Variable shadowing..." { echo "## CHECK 41: Variable Shadowing" echo "Severity: MEDIUM" echo "Issue: Local variables hiding globals causes confusion" echo "" count=0 # Find functions that declare local variables with common global names while read -r file; do # Extract global variables (declared at top level) globals=$(grep -E '^[A-Z_]+=' "$file" 2>/dev/null | cut -d= -f1 | tr '\n' '|' | sed 's/|$//') if [ -n "$globals" ]; then # Find local declarations using same names while IFS=: read -r line_num line_content; do shadowed=$(echo "$line_content" | grep -oE "local\s+($globals)" | awk '{print $2}') if [ -n "$shadowed" ]; then echo "MEDIUM|$file|$line_num|[SHADOW] Local variable \$$shadowed shadows global" echo " Risk: Confusing which variable is being used" echo " Fix: Use different name for local variable" count_issue "MEDIUM" ((count++)) [ "$count" -ge 5 ] && break 2 fi done < <(grep -n 'local\s' "$file" 2>/dev/null) fi done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null | head -10) echo "Found: $count variable shadowing issues" echo "" } >> "$REPORT" #============================================================================== # CHECK 42: Unsafe Globbing (HIGH - unexpected file matches) #============================================================================== echo "[42/42] Checking: Unsafe glob patterns..." { echo "## CHECK 42: Unsafe Globbing" echo "Severity: HIGH" echo "Issue: Unquoted globs in dangerous commands" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip comments if echo "$line_content" | grep -qE '^\s*#'; then continue fi # Find rm/cp/mv/chmod with unquoted * patterns if echo "$line_content" | grep -qE '(rm|cp|mv|chmod)\s+[^"]*\*[^"]*(\s|$)'; then # Make sure it's not in quotes if ! echo "$line_content" | grep -qE '"[^"]*\*[^"]*"|'"'"'[^'"'"']*\*[^'"'"']*'"'"''; then echo "HIGH|$file|$line_num|[GLOB] Unquoted glob in dangerous command" echo " Risk: * may match unexpected files" echo " Fix: Quote the pattern or use find with -delete" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rnE '(rm|cp|mv|chmod).*\*' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null) echo "Found: $count unsafe glob patterns" echo "" } >> "$REPORT" #============================================================================== # CHECK 43: Control Panel Function Call Validation (PHASE 3) #============================================================================== echo "[43/50] Checking: Control panel function calls without validation..." { echo "## CHECK 43: Control panel function calls without validation" echo "Severity: CRITICAL" echo "Pattern: Calling cpanel_*/plesk_*/interworx_* functions without checking \$CONTROL_PANEL" echo "" count=0 # Find calls to control-panel-specific functions while IFS=: read -r file line_num line_content; do # Skip if already suppressed is_suppressed "$file" "$line_num" "panel-check" && continue # Skip lib files that define these panel-specific functions if [[ "$file" =~ (domain-discovery|system-detect|plesk-helpers|cpanel-helpers|interworx-helpers)\.sh$ ]]; then continue fi # Extract function name func_name=$(echo "$line_content" | grep -oE '(cpanel|plesk|interworx)_[a-z_]+' | head -1) # Check if file has CONTROL_PANEL validation before this line if ! head -n "$line_num" "$file" 2>/dev/null | grep -qE 'CONTROL_PANEL.*(cpanel|plesk|interworx)|case.*CONTROL_PANEL'; then echo "CRITICAL|$file|$line_num|[PANEL-CALL] Calling $func_name without \$CONTROL_PANEL check" echo " Risk: Function will fail on other control panels (cpanel function on plesk server)" echo " Fix: Add 'if [[ \$CONTROL_PANEL == \"cpanel\" ]]; then' or use case statement" count_issue "CRITICAL" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rnE '\b(cpanel|plesk|interworx)_[a-z_]+\(' "$TOOLKIT_PATH" --include="*.sh" --exclude="domain-discovery.sh" --exclude="system-detect.sh" 2>/dev/null) echo "Found: $count unvalidated panel-specific calls" echo "" } >> "$REPORT" #============================================================================== # CHECK 44: Sourcing Without Path Validation (PHASE 3) #============================================================================== echo "[44/50] Checking: Source commands without file existence check..." { echo "## CHECK 44: Sourcing files without validation" echo "Severity: HIGH" echo "Pattern: source lib/file.sh without checking if file exists" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip if already suppressed is_suppressed "$file" "$line_num" "source-check" && continue # Skip if line has error handling (|| or 2>/dev/null) if echo "$line_content" | grep -qE '\|\||2>/dev/null'; then continue fi # Extract sourced file path sourced_file=$(echo "$line_content" | grep -oE 'source\s+[^ ]+' | awk '{print $2}' | tr -d '"') # Skip if empty, variable, absolute path, or contains special chars if [ -z "$sourced_file" ] || [[ "$sourced_file" =~ ^\$ ]] || [[ "$sourced_file" =~ ^/ ]] || [[ "$sourced_file" =~ [{}()] ]]; then continue fi # Check if there's a validation within 3 lines before context=$(sed -n "$((line_num - 3)),$line_num p" "$file" 2>/dev/null) if ! echo "$context" | grep -qE '\[\s+-[fe]\s+.*'"$sourced_file"'|&&\s+source|if.*source'; then echo "HIGH|$file|$line_num|[SOURCE] Sourcing without existence check: $sourced_file" echo " Risk: Script fails with 'file not found' if path is wrong" echo " Fix: Add '[ -f \"$sourced_file\" ] && source \"$sourced_file\" || { echo \"Error\"; exit 1; }'" count_issue "HIGH" ((count++)) [ "$count" -ge 15 ] && break fi done < <(grep -rnE '^\s*source\s+[a-zA-Z_]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count unvalidated source commands" echo "" } >> "$REPORT" #============================================================================== # CHECK 45: Pipe Failure Detection (PHASE 3) #============================================================================== echo "[45/50] Checking: Pipes without pipefail protection..." { echo "## CHECK 45: Critical pipes without set -o pipefail" echo "Severity: MEDIUM" echo "Pattern: mysql/curl | grep without pipefail (hides failures)" echo "" count=0 # Find files with critical pipes while IFS=: read -r file line_num line_content; do # Skip if already suppressed is_suppressed "$file" "$line_num" "pipe-check" && continue # Check if file has set -o pipefail if ! grep -qE '^set -o pipefail|^set -euo pipefail' "$file" 2>/dev/null; then # Extract the piped commands pipe_cmd=$(echo "$line_content" | grep -oE '(mysql|curl|wget|ssh)[^|]*\|[^|]*' | head -1) echo "MEDIUM|$file|$line_num|[PIPE] Critical pipe without pipefail: $pipe_cmd" echo " Risk: If first command fails, second succeeds with empty input (silent failure)" echo " Fix: Add 'set -o pipefail' at top of script" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rnE '(mysql|curl|wget|ssh)[^|]*\|' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count risky pipes without pipefail" echo "" } >> "$REPORT" #============================================================================== # CHECK 46: Function Return Value Confusion (PHASE 3) #============================================================================== echo "[46/50] Checking: Function return value confusion..." { echo "## CHECK 46: Functions that both echo and return status" echo "Severity: HIGH" echo "Pattern: result=\$(func); if [ \$? -eq 0 ] - \$? is from subshell, not function" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip if already suppressed is_suppressed "$file" "$line_num" "return-check" && continue # Look for pattern: var=$(func); if [ $? ... if echo "$line_content" | grep -qE '\$\([^)]+\)'; then # Check if next line uses $? next_line=$(sed -n "$((line_num + 1))p" "$file" 2>/dev/null) if echo "$next_line" | grep -qE '\[\s+\$\?\s+(eq|ne|gt|lt)'; then echo "HIGH|$file|$line_num|[RETURN] Return value confusion: \$? checks subshell, not function" echo " Risk: \$? is always 0 for successful subshell, ignores function return value" echo " Fix: Use 'if result=\$(func); then' or check result content, not \$?" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rn '\$(' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count potential return value confusion patterns" echo "" } >> "$REPORT" #============================================================================== # CHECK 47: Missing Null/Empty Checks Before Critical Operations (PHASE 3) #============================================================================== echo "[47/50] Checking: Missing null/empty checks before operations..." { echo "## CHECK 47: Critical operations on potentially empty variables" echo "Severity: HIGH" echo "Pattern: rm/chown/chmod \$var without checking if \$var is empty" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip if already suppressed is_suppressed "$file" "$line_num" "null-check" && continue # Skip if this is just displaying example commands (echo/print_info/print_warning) if echo "$line_content" | grep -qE '^\s*(echo|print_info|print_warning|print_error|printf)\s+'; then continue fi # Extract variable being used var_name=$(echo "$line_content" | grep -oE '\$\{?[A-Z_a-z][A-Z_a-z0-9]*' | head -1 | tr -d '${}') # Skip known safe variables if [[ "$var_name" =~ ^(HOME|USER|PATH|PWD|OLDPWD|SHELL|TERM)$ ]]; then continue fi # Check if variable is validated within 5 lines before context=$(sed -n "$((line_num - 5)),$((line_num - 1))p" "$file" 2>/dev/null) if ! echo "$context" | grep -qE "\[\s+-[nz]\s+.*$var_name|if\s+\[\s+.*$var_name"; then echo "HIGH|$file|$line_num|[NULL] Operation on unchecked variable: \$$var_name" echo " Risk: If \$$var_name is empty, operation acts on current/root directory" echo " Fix: Add '[ -n \"\$$var_name\" ] || { echo \"Error\"; exit 1; }' before operation" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rnE '(rm|chown|chmod|cd)\s+[^"]*\$[A-Z_a-z]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | grep -v '"\$') echo "Found: $count operations on potentially empty variables" echo "" } >> "$REPORT" #============================================================================== # CHECK 48: Hardcoded Temp Paths (PHASE 3) #============================================================================== echo "[48/50] Checking: Hardcoded /tmp paths..." { echo "## CHECK 48: Hardcoded /tmp/ instead of mktemp or \$TEMP_DIR" echo "Severity: MEDIUM" echo "Pattern: /tmp/myfile instead of \$(mktemp) or \$TEMP_DIR/myfile" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip if already suppressed is_suppressed "$file" "$line_num" "temp-check" && continue # Skip common exceptions if echo "$line_content" | grep -qE '(TEMP_DIR=/tmp|REPORT=/tmp|test -d /tmp|mkdir -p /tmp)'; then continue fi # Extract the hardcoded path temp_path=$(echo "$line_content" | grep -oE '/tmp/[a-zA-Z0-9_-]+' | head -1) echo "MEDIUM|$file|$line_num|[TEMP] Hardcoded temp path: $temp_path" echo " Risk: Filename collision, race condition, predictable location" echo " Fix: Use 'tmpfile=\$(mktemp)' or '\$TEMP_DIR/\$\$-myfile'" count_issue "MEDIUM" ((count++)) [ "$count" -ge 15 ] && break done < <(grep -rnE '/tmp/[a-zA-Z0-9_-]+' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | grep -v mktemp | grep -v 'TEMP_DIR=') echo "Found: $count hardcoded temp paths" echo "" } >> "$REPORT" #============================================================================== # CHECK 49: Silent Failure in Subshells (PHASE 3) #============================================================================== echo "[49/50] Checking: Silent failures in command substitutions..." { echo "## CHECK 49: Command substitutions that can fail silently" echo "Severity: MEDIUM" echo "Pattern: count=\$(wc -l < file) - if file missing, count is empty" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip if already suppressed is_suppressed "$file" "$line_num" "subshell-check" && continue # Look for variable assignment from subshell with risky commands if echo "$line_content" | grep -qE '=\$\((wc|cat|head|tail|grep -c)'; then # Check if there's error handling after next_lines=$(sed -n "$((line_num + 1)),$((line_num + 3))p" "$file" 2>/dev/null) # Extract variable name var_name=$(echo "$line_content" | grep -oE '[a-zA-Z_][a-zA-Z0-9_]*=' | tr -d '=') # Check if validated after assignment if ! echo "$next_lines" | grep -qE "\[\s+-[nz]\s+.*\$$var_name|\[\s+\$$var_name"; then echo "MEDIUM|$file|$line_num|[SUBSHELL] Unvalidated command substitution: \$$var_name" echo " Risk: If command fails, variable is empty but no error detected" echo " Fix: Add '[ -n \"\$$var_name\" ] || { echo \"Error\"; exit 1; }' after assignment" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rnE '=\$\((wc|cat|head|tail|grep -c)' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count unvalidated command substitutions" echo "" } >> "$REPORT" #============================================================================== # CHECK 50: Missing Function Dependency (PHASE 3) #============================================================================== echo "[50/50] Checking: Missing function dependency sources..." { echo "## CHECK 50: Calling toolkit functions without sourcing library" echo "Severity: HIGH" echo "Pattern: Using print_info/print_error without sourcing common-functions.sh" echo "" count=0 # Common toolkit functions that require sourcing common_funcs="print_info|print_error|print_warning|print_success|confirm_action" detect_funcs="detect_control_panel|get_domain_docroot|list_all_domains" while IFS=: read -r file line_num line_content; do # Skip if already suppressed is_suppressed "$file" "$line_num" "dep-check" && continue # Skip if this IS the library file if [[ "$file" =~ (common-functions|domain-discovery|system-detect)\.sh$ ]]; then continue fi # Check if file sources the required library func_used=$(echo "$line_content" | grep -oE "($common_funcs|$detect_funcs)" | head -1) if [[ "$func_used" =~ (print_|confirm_action) ]]; then required_lib="common-functions.sh" if ! grep -q "source.*$required_lib" "$file" 2>/dev/null; then echo "HIGH|$file|$line_num|[DEP] Using $func_used without sourcing $required_lib" echo " Risk: Function undefined error at runtime" echo " Fix: Add 'source \$(dirname \$0)/../lib/$required_lib' at top of script" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi elif [[ "$func_used" =~ detect_control_panel ]]; then # detect_control_panel is in system-detect.sh if ! grep -q "source.*system-detect\.sh" "$file" 2>/dev/null; then echo "HIGH|$file|$line_num|[DEP] Using $func_used without sourcing system-detect.sh" echo " Risk: Function undefined error at runtime" echo " Fix: Add 'source \$(dirname \$0)/../lib/system-detect.sh' at top of script" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi else # Domain/user functions are in domain-discovery.sh required_lib="domain-discovery.sh" if ! grep -q "source.*$required_lib" "$file" 2>/dev/null; then echo "HIGH|$file|$line_num|[DEP] Using $func_used without sourcing $required_lib" echo " Risk: Function undefined error at runtime" echo " Fix: Add 'source \$(dirname \$0)/../lib/$required_lib' at top of script" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rnE "\b($common_funcs|$detect_funcs)\b" "$TOOLKIT_PATH" --include="*.sh" --exclude="common-functions.sh" --exclude="domain-discovery.sh" --exclude="system-detect.sh" 2>/dev/null | head -100) echo "Found: $count missing function dependencies" echo "" } >> "$REPORT" #============================================================================== # CHECK 51: Word Splitting in Loops (PHASE 4) #============================================================================== echo "[51/60] Checking: Word splitting bugs in loops..." { echo "## CHECK 51: Unquoted variables/arrays in for loops" echo "Severity: MEDIUM" echo "Pattern: for x in \$var (splits on spaces - intentional or bug?)" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "word-split" && continue # Extract the unquoted variable var=$(echo "$line_content" | grep -oE 'for\s+\w+\s+in\s+\$[A-Za-z_][A-Za-z0-9_]*' | grep -oE '\$[A-Za-z_]+') # Skip common intentional patterns var_name="${var#\$}" if [[ "$var_name" =~ (disks|ips|users|dbs|files|dirs|items|list|args|@|\*|REPLY) ]]; then continue # These are typically intentional word-splitting lists fi # Skip if variable name suggests it's a list/array if [[ "$var_name" =~ _(list|array|items)$ ]]; then continue fi echo "MEDIUM|$file|$line_num|[WORDSPLIT] Unquoted variable in for loop: $var" echo " Risk: Splits on spaces, not lines - 'file name.txt' becomes 2 items" echo " Fix: If intentional (space-separated list), add # qa-suppress:word-split" echo " If bug (filenames), use: for x in \"\$var\" or \"\${arr[@]}\"" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break done < <(grep -rnE 'for\s+\w+\s+in\s+\$[A-Za-z_]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | grep -v '"\$') echo "Found: $count potential word splitting issues" echo "" } >> "$REPORT" #============================================================================== # CHECK 52: Arithmetic on Non-Numeric Variables (PHASE 4) #============================================================================== echo "[52/60] Checking: Arithmetic on potentially non-numeric variables..." { echo "## CHECK 52: Using unvalidated variables in arithmetic" echo "Severity: MEDIUM" echo "Pattern: \$((\$var + 1)) where \$var might not be a number" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "arith-check" && continue # Extract variable used in arithmetic var_name=$(echo "$line_content" | grep -oE '\$[A-Za-z_][A-Za-z0-9_]*' | head -1 | tr -d '$') # Skip known numeric variables if [[ "$var_name" =~ ^(i|j|k|count|index|num|total|sum|PORT|PID|UID|GID|PPID)$ ]]; then continue fi echo "MEDIUM|$file|$line_num|[ARITH] Arithmetic on unvalidated variable: \$$var_name" echo " Risk: If \$$var_name is empty/non-numeric, expression fails or gives 0" echo " Fix: Validate: [ \"\$var_name\" -eq \"\$var_name\" ] 2>/dev/null || var_name=0" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break done < <(grep -rnE '\$\(\(\s*\$[A-Za-z_]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count unvalidated arithmetic operations" echo "" } >> "$REPORT" #============================================================================== # CHECK 53: Test Command Without Quotes (PHASE 4) #============================================================================== echo "[53/60] Checking: Test conditions without quotes..." { echo "## CHECK 53: [ \$var = value ] without quotes (fails if var is empty)" echo "Severity: HIGH" echo "Pattern: [ \$var = ] or [ \$var != ] without quotes" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "test-quote" && continue # Check for unquoted variables in test if echo "$line_content" | grep -qE '\[\s+\$[A-Za-z_][A-Za-z0-9_]*\s*(=|!=|<|>)' && \ ! echo "$line_content" | grep -qE '"\$[A-Za-z_]'; then var=$(echo "$line_content" | grep -oE '\$[A-Za-z_][A-Za-z0-9_]*' | head -1) echo "HIGH|$file|$line_num|[TEST] Unquoted variable in test: $var" echo " Risk: If $var is empty, test fails with 'unary operator expected'" echo " Fix: Use quotes: [ \"\$var\" = value ] or [[ \$var = value ]]" count_issue "HIGH" ((count++)) [ "$count" -ge 15 ] && break fi done < <(grep -rnE '\[\s+\$[A-Za-z_]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count unquoted test conditions" echo "" } >> "$REPORT" #============================================================================== # CHECK 54: Redirection Before Command (PHASE 4) #============================================================================== echo "[54/60] Checking: Redirections that execute before command..." { echo "## CHECK 54: > file before command (truncates even if command fails)" echo "Severity: MEDIUM" echo "Pattern: > file; command (file truncated before command runs)" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "redir-check" && continue # Look for > redirection on same line as potentially failing command if echo "$line_content" | grep -qE '>\s*[^ ]+.*\$\('; then echo "MEDIUM|$file|$line_num|[REDIR] Redirection before command substitution" echo " Risk: File truncated even if command fails" echo " Fix: Use temp file: tmp=\$(cmd) && echo \"\$tmp\" > file" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rnE '>\s*[^ ]+.*\$\(' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count risky redirections" echo "" } >> "$REPORT" #============================================================================== # CHECK 55: Missing Trap Cleanup (PHASE 4) #============================================================================== echo "[55/60] Checking: Scripts with trap without cleanup..." { echo "## CHECK 55: trap set but no cleanup of temp files/processes" echo "Severity: MEDIUM" echo "Pattern: trap command but no rm/kill in trap handler" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "trap-check" && continue # Check if trap has cleanup commands if echo "$line_content" | grep -qE 'trap.*EXIT|trap.*INT'; then # Check if trap line contains rm, kill, cleanup if ! echo "$line_content" | grep -qE '(rm|kill|cleanup|pkill)'; then echo "MEDIUM|$file|$line_num|[TRAP] Trap without cleanup commands" echo " Risk: Temp files/processes not cleaned up on exit/interrupt" echo " Fix: Add cleanup: trap 'rm -f \$TEMP_FILE; kill \$PID' EXIT INT" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rnE 'trap' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count traps without cleanup" echo "" } >> "$REPORT" #============================================================================== # CHECK 56: Array Treated as String (PHASE 4) #============================================================================== echo "[56/60] Checking: Arrays used as strings..." { echo "## CHECK 56: Treating bash array as string (loses all but first element)" echo "Severity: HIGH" echo "Pattern: echo \$array (should be \${array[@]})" echo "" count=0 # Find array declarations while IFS=: read -r file line_num line_content; do # Extract array name if echo "$line_content" | grep -qE '(declare -a|local -a|\w+=\()'; then arr_name=$(echo "$line_content" | grep -oE '[A-Za-z_][A-Za-z0-9_]*=' | tr -d '=' | head -1) # Look for uses of this array without [@] or [*] if grep -q "\$$arr_name[^[]" "$file" 2>/dev/null; then line=$(grep -n "\$$arr_name[^[]" "$file" 2>/dev/null | head -1 | cut -d: -f1) echo "HIGH|$file|$line|[ARRAY] Array \$$arr_name used without [@] or [*]" echo " Risk: Only first element accessed, rest lost" echo " Fix: Use \"\${$arr_name[@]}\" for all elements" count_issue "HIGH" ((count++)) [ "$count" -ge 8 ] && break fi fi done < <(grep -rnE '(declare -a|local -a|\w+=\()' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | head -50) echo "Found: $count array misuse cases" echo "" } >> "$REPORT" #============================================================================== # CHECK 57: Unquoted Here-Doc (PHASE 4) #============================================================================== echo "[57/60] Checking: Here-docs without quotes..." { echo "## CHECK 57: cat </dev/null) if echo "$next_lines" | grep -q '\$'; then echo "LOW|$file|$line_num|[HEREDOC] Unquoted heredoc with variables" echo " Risk: Variables expanded when they might need to be literal" echo " Fix: If literal, use <<'EOF'. If expansion intended, this is fine." count_issue "LOW" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rnE '<<-?\s*[A-Z_]+' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count unquoted heredocs" echo "" } >> "$REPORT" #============================================================================== # CHECK 58: If Statement Masking Errors (PHASE 4) #============================================================================== echo "[58/60] Checking: Commands in if statements that mask errors..." { echo "## CHECK 58: if command (success/fail controls flow, not exit code check)" echo "Severity: MEDIUM" echo "Pattern: if grep/find/test without explicit exit code handling" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "if-mask" && continue # Look for if grep/find without storing result if echo "$line_content" | grep -qE 'if\s+(grep|find|test)\s+' && \ ! echo "$line_content" | grep -qE '(then|;)'; then cmd=$(echo "$line_content" | grep -oE '(grep|find|test)' | head -1) echo "MEDIUM|$file|$line_num|[IF-MASK] Command in if statement: if $cmd" echo " Risk: Exit code consumed by if, can't check later with \$?" echo " Fix: Store result: result=\$(grep ...) && if [ -n \"\$result\" ]" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rnE 'if\s+(grep|find)\s+' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count exit code masking patterns" echo "" } >> "$REPORT" #============================================================================== # CHECK 59: Numeric Comparison on Strings (PHASE 4) #============================================================================== echo "[59/60] Checking: Numeric comparisons on string variables..." { echo "## CHECK 59: Using -eq/-lt/-gt on variables that might be strings" echo "Severity: HIGH" echo "Pattern: [ \$string_var -eq value ] (fails if not numeric)" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "numcmp" && continue # Extract variable in numeric comparison var_name=$(echo "$line_content" | grep -oE '\$[A-Za-z_][A-Za-z0-9_]*\s+-[egltn][eqt]' | grep -oE '\$[A-Za-z_]+' | tr -d '$') # Skip obviously numeric variables if [[ "$var_name" =~ ^(count|num|total|index|i|j|k|exit|status|rc|ret|port|pid|size)$ ]]; then continue fi # Check if variable looks like it might be a string (ends in _name, _file, _dir, _path, _str) if [[ "$var_name" =~ (name|file|dir|path|str|text|msg|type|mode)$ ]]; then echo "HIGH|$file|$line_num|[NUMCMP] Numeric comparison on string-like variable: \$$var_name" echo " Risk: Fails with 'integer expression expected' if variable is string" echo " Fix: Use string comparison: = != or validate first" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rnE '\$[A-Za-z_][A-Za-z0-9_]*\s+-[egltn][eqt]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count suspicious numeric comparisons" echo "" } >> "$REPORT" #============================================================================== # CHECK 60: Background Job Without Wait (PHASE 4) #============================================================================== echo "[60/60] Checking: Background jobs started without wait..." { echo "## CHECK 60: command & without corresponding wait (may not complete)" echo "Severity: MEDIUM" echo "Pattern: & to background job but no wait in script" echo "" count=0 # Find files with background jobs while read -r file; do # Check if file has & for background jobs if grep -qE '\s+&\s*$' "$file" 2>/dev/null; then # Check if file has wait command if ! grep -qE '\bwait\b' "$file" 2>/dev/null; then line_num=$(grep -n '&\s*$' "$file" 2>/dev/null | head -1 | cut -d: -f1) echo "MEDIUM|$file|$line_num|[BG-JOB] Background job without wait" echo " Risk: Script may exit before background job completes" echo " Fix: Add 'wait' before script exit or 'wait \$!' after &" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null | head -100) echo "Found: $count background jobs without wait" echo "" } >> "$REPORT" #============================================================================== # CHECK 61: Locale-Dependent Operations (PHASE 5) #============================================================================== echo "[61/80] Checking: Locale-dependent sort/comparison..." { echo "## CHECK 61: sort or comparison without LC_ALL=C" echo "Severity: MEDIUM" echo "Pattern: sort, [[ < ]] without setting locale" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "locale" && continue # Check if file sets LC_ALL before this line if ! head -n "$line_num" "$file" 2>/dev/null | grep -qE 'LC_ALL=C|export LC_ALL'; then echo "MEDIUM|$file|$line_num|[LOCALE] Locale-dependent operation without LC_ALL=C" echo " Risk: Different sort order on different systems (ä vs a)" echo " Fix: Add 'LC_ALL=C sort' or 'export LC_ALL=C' at top of script" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rnE '\bsort\b' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | head -50) echo "Found: $count locale-dependent operations" echo "" } >> "$REPORT" #============================================================================== # CHECK 62: Process Substitution in POSIX Shell (PHASE 5) #============================================================================== echo "[62/80] Checking: Process substitution in #!/bin/sh scripts..." { echo "## CHECK 62: <(...) or >(...) in POSIX sh scripts" echo "Severity: HIGH" echo "Pattern: Process substitution requires bash, not sh" echo "" count=0 while read -r file; do # Check if shebang is /bin/sh shebang=$(head -1 "$file" 2>/dev/null) if echo "$shebang" | grep -qE '^#!/bin/sh\s*$'; then # Look for process substitution if grep -qE '<\(|>\(' "$file" 2>/dev/null; then line_num=$(grep -n '<\(|>\(' "$file" 2>/dev/null | head -1 | cut -d: -f1) echo "HIGH|$file|$line_num|[PROC-SUB] Process substitution in #!/bin/sh script" echo " Risk: Fails on systems where /bin/sh is dash/ash (not bash)" echo " Fix: Change shebang to #!/bin/bash or avoid <(...)" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null | head -100) echo "Found: $count POSIX incompatibilities" echo "" } >> "$REPORT" #============================================================================== # CHECK 63: Unsafe printf Format Strings (PHASE 5) #============================================================================== echo "[63/80] Checking: User input in printf format string..." { echo "## CHECK 63: printf \"\$variable\" (format string injection)" echo "Severity: CRITICAL" echo "Pattern: printf with unvalidated variable as format" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "printf" && continue # Look for printf "$var" where var might be user input if echo "$line_content" | grep -qE 'printf\s+"?\$[a-zA-Z_]'; then var=$(echo "$line_content" | grep -oE '\$[a-zA-Z_][a-zA-Z0-9_]*' | head -1) # Check if variable looks user-controlled if [[ "$var" =~ (input|user|arg|param|data|msg|text|str) ]]; then echo "CRITICAL|$file|$line_num|[PRINTF] User input in printf format string: $var" echo " Risk: Format string injection, crashes, information disclosure" echo " Fix: Use printf '%s' \"\$var\" or printf '%s\\n' \"\$var\"" count_issue "CRITICAL" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rnE 'printf\s+"?\$' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count unsafe printf patterns" echo "" } >> "$REPORT" #============================================================================== # CHECK 64: Regex Without Anchors (PHASE 5) #============================================================================== echo "[64/80] Checking: Regex without anchors in conditionals..." { echo "## CHECK 64: [[ \$var =~ pattern ]] without ^ or $" echo "Severity: MEDIUM" echo "Pattern: Partial matches when full match intended" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "regex" && continue # Look for =~ with common patterns that should be anchored if echo "$line_content" | grep -qE '=~.*\.(txt|log|sh|conf|ini)' && \ ! echo "$line_content" | grep -qE '(\\^|\\$|\$)'; then echo "MEDIUM|$file|$line_num|[REGEX] Regex without anchors (may match unintended)" echo " Risk: [[ file =~ .txt ]] matches 'footxt.sh', wanted 'foo.txt'" echo " Fix: Use [[ \$file =~ \\.txt$ ]] for end anchor" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rnE '=~' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count unanchored regex patterns" echo "" } >> "$REPORT" #============================================================================== # CHECK 65: Bashisms in POSIX Scripts (PHASE 5) #============================================================================== echo "[65/80] Checking: Bash features in #!/bin/sh scripts..." { echo "## CHECK 65: [[, ((, arrays in #!/bin/sh scripts" echo "Severity: HIGH" echo "Pattern: Bash-specific syntax in POSIX shell" echo "" count=0 while read -r file; do shebang=$(head -1 "$file" 2>/dev/null) if echo "$shebang" | grep -qE '^#!/bin/sh\s*$'; then # Check for bashisms if grep -qE '(\[\[|\(\(|declare -|local -a|\$\{.*\[@\]\})' "$file" 2>/dev/null; then line_num=$(grep -nE '(\[\[|\(\(|declare -|local -a)' "$file" 2>/dev/null | head -1 | cut -d: -f1) bashism=$(grep -E '(\[\[|\(\(|declare -|local -a)' "$file" 2>/dev/null | head -1 | sed 's/^[[:space:]]*//' | cut -c1-50) echo "HIGH|$file|$line_num|[BASHISM] Bash syntax in #!/bin/sh: $bashism" echo " Risk: Fails on systems where /bin/sh is dash/ash" echo " Fix: Change shebang to #!/bin/bash or use POSIX syntax" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null | head -100) echo "Found: $count bashisms in POSIX scripts" echo "" } >> "$REPORT" #============================================================================== # CHECK 66: Unescaped Special Chars in Filenames (PHASE 5) #============================================================================== echo "[66/80] Checking: Special characters in filename variables..." { echo "## CHECK 66: Filename with *, ?, [ used in grep/sed" echo "Severity: HIGH" echo "Pattern: \$file passed to grep/sed without escaping" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "escape" && continue # Look for grep/sed with filename variable if echo "$line_content" | grep -qE '(grep|sed|awk).*\$[a-zA-Z_]*[Ff]ile'; then # Skip if the command uses -- before the filename (safe) if echo "$line_content" | grep -qE '(grep|sed|awk|wc).*--.*\$[a-zA-Z_]*[Ff]ile'; then continue fi # Skip if filename appears after redirection (output, not input) # Example: grep ... > "$output_file" (safe - writing to file, not reading from it) if echo "$line_content" | grep -qE '(>|>>|2>)\s*"\$[a-zA-Z_]*[Ff]ile'; then continue fi var=$(echo "$line_content" | grep -oE '\$[a-zA-Z_]*[Ff]ile[a-zA-Z_]*' | head -1) echo "HIGH|$file|$line_num|[ESCAPE] Filename variable in grep/sed: $var" echo " Risk: If $var='test*.txt', * treated as glob not literal" echo " Fix: Use grep -- or escape: grep \"\$(printf '%s' \"\$file\" | sed 's/[.*^$\\[\\]/\\\\&/g')\"" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rnE '(grep|sed|awk).*\$[a-zA-Z_]*[Ff]ile' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | head -100) echo "Found: $count unescaped filename uses" echo "" } >> "$REPORT" #============================================================================== # CHECK 67: Sleep-Based Race Conditions (PHASE 5) #============================================================================== echo "[67/80] Checking: sleep instead of proper synchronization..." { echo "## CHECK 67: sleep N; use_result (timing assumptions)" echo "Severity: MEDIUM" echo "Pattern: Using sleep delays instead of wait/lock" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "sleep-race" && continue # Look for sleep after background job prev_line=$(sed -n "$((line_num - 1))p" "$file" 2>/dev/null) if echo "$prev_line" | grep -qE '&\s*$'; then echo "MEDIUM|$file|$line_num|[SLEEP-RACE] sleep after background job (timing assumption)" echo " Risk: Job might take longer than sleep duration" echo " Fix: Use 'wait \$!' or check for completion file/signal" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rn '^\s*sleep' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count sleep-based race conditions" echo "" } >> "$REPORT" #============================================================================== # CHECK 68: Missing IFS Reset (PHASE 5) #============================================================================== echo "[68/80] Checking: IFS modified without reset..." { echo "## CHECK 68: IFS=: without subsequent IFS=\$OLD_IFS" echo "Severity: HIGH" echo "Pattern: Changing IFS affects all subsequent commands" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "ifs" && continue # Skip safe patterns: IFS= read (only affects read command) if echo "$line_content" | grep -qE 'IFS=.*read'; then continue fi # Skip if IFS is in a while/for loop condition (scoped) if echo "$line_content" | grep -qE '(while|for).*IFS='; then continue fi # Look for standalone IFS= assignment if echo "$line_content" | grep -qE '^\s*IFS='; then # Check if there's a reset within next 20 lines if ! sed -n "$((line_num + 1)),$((line_num + 20))p" "$file" 2>/dev/null | grep -qE '^\s*IFS=\$'; then echo "HIGH|$file|$line_num|[IFS] IFS modified without reset" echo " Risk: Affects all subsequent word splitting in script" echo " Fix: OLD_IFS=\$IFS; IFS=:; ...; IFS=\$OLD_IFS or use in subshell" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rn '^\s*IFS=' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | head -50) echo "Found: $count IFS modifications without reset" echo "" } >> "$REPORT" #============================================================================== # CHECK 69: Subshell Variable Loss (PHASE 5) #============================================================================== echo "[69/80] Checking: Variables modified in subshells/pipes..." { echo "## CHECK 69: var=x in ( ) subshell or pipe (changes lost)" echo "Severity: HIGH" echo "Pattern: Variable assignment inside ( ) or | pipeline" echo "" count=0 # Look for pipes with while loops while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "subshell-var" && continue # Look for assignment in next few lines after pipe if echo "$line_content" | grep -qE '\|\s*while'; then # Check next 10 lines for variable assignments next_lines=$(sed -n "$((line_num + 1)),$((line_num + 10))p" "$file" 2>/dev/null) if echo "$next_lines" | grep -qE '^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*=' && \ echo "$next_lines" | grep -qE 'done'; then # Skip if variable is only used for file writes (safe pattern) # Example: username=$(echo "$log" | ...); echo "$log|php_$username" >> "$FILE" if echo "$next_lines" | grep -qE '[a-zA-Z_][a-zA-Z0-9_]*=.*echo.*>>' || \ echo "$next_lines" | grep -qE 'echo.*\$[a-zA-Z_][a-zA-Z0-9_]*.*>>'; then continue # Variable only used for writing to files (changes persist) fi echo "HIGH|$file|$line_num|[SUBSHELL-VAR] Variable assignment in pipe (changes lost)" echo " Risk: cmd | while read x; do count=\$((count+1)); done; echo \$count # always 0!" echo " Fix: Use process substitution: while read x; do ...; done < <(cmd)" count_issue "HIGH" ((count++)) [ "$count" -ge 8 ] && break fi fi done < <(grep -rn '|.*while' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | head -50) echo "Found: $count potential variable loss in subshells" echo "" } >> "$REPORT" #============================================================================== # CHECK 70: Non-Reentrant Trap Handlers (PHASE 5) #============================================================================== echo "[70/80] Checking: Trap handlers that aren't atomic..." { echo "## CHECK 70: Trap handlers with complex logic (signal race)" echo "Severity: MEDIUM" echo "Pattern: Trap handler with multiple commands/loops" echo "" count=0 while IFS=: read -r file line_num line_content; do is_suppressed "$file" "$line_num" "trap-race" && continue # Look for trap with complex command (semicolons or braces) if echo "$line_content" | grep -qE 'trap.*\{|trap.*; .*; '; then echo "MEDIUM|$file|$line_num|[TRAP-RACE] Complex trap handler (not atomic)" echo " Risk: Signal arriving during trap execution causes corruption" echo " Fix: Keep trap handlers simple, set flag and check in main loop" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rn 'trap' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count complex trap handlers" echo "" } >> "$REPORT" #============================================================================== # PHASE 6: PERFORMANCE & RESOURCE CHECKS (CHECK 71-80) #============================================================================== # CHECK 71: Inefficient Loop Patterns echo "[71/80] Checking: Inefficient loop patterns (for i in \$(seq))..." { echo "## CHECK 71: Inefficient Loop Patterns" echo "Severity: MEDIUM" echo "Pattern: for i in \$(seq 1 100) (spawns external command)" echo "Fix: Use {1..100} or ((i=1; i<=100; i++))" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip qa-suppress if grep -q "# qa-suppress" <<< "$line_content"; then continue fi if echo "$line_content" | grep -qE 'for\s+\w+\s+in\s+\$\(seq\s+'; then echo "MEDIUM|$file|$line_num|[PERF-LOOP] Using \$(seq) in loop (spawns external process)" echo " Risk: Slow performance, unnecessary process creation" echo " Fix: Use {start..end} or arithmetic for loop" echo " Example: for i in {1..100} or for ((i=1; i<=100; i++))" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rn 'for.*\$(seq' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count inefficient seq loops" echo "" } >> "$REPORT" # CHECK 72: Repeated Command Execution echo "[72/80] Checking: Repeated command execution (caching opportunity)..." { echo "## CHECK 72: Repeated Command Execution" echo "Severity: MEDIUM" echo "Pattern: Multiple calls to same command with same args" echo "Fix: Cache result in variable" echo "" count=0 while IFS= read -r file; do # Find duplicate command patterns (same command called 3+ times) duplicates=$(grep -oE '(hostname|date|whoami|id -un|pwd|uname -r)\b' "$file" 2>/dev/null | sort | uniq -c | awk '$1 >= 3 {print $2}') if [ -n "$duplicates" ]; then for cmd in $duplicates; do occurrences=$(grep -c "$cmd" "$file" 2>/dev/null) occurrences=$(echo "$occurrences" | head -1 | tr -d '\n\r') first_line=$(grep -n "$cmd" "$file" 2>/dev/null | head -1 | cut -d: -f1) echo "MEDIUM|$file|$first_line|[PERF-CACHE] Command '$cmd' called $occurrences times" echo " Risk: Unnecessary overhead from repeated execution" echo " Fix: cache=\$($cmd); then use \$cache" ((count++)) [ "$count" -ge 10 ] && break 2 done fi done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null) echo "Found: $count cacheable command patterns" echo "" } >> "$REPORT" # CHECK 73: Large File Read Inefficiency echo "[73/80] Checking: Inefficient large file processing..." { echo "## CHECK 73: Large File Read Inefficiency" echo "Severity: MEDIUM" echo "Pattern: cat large_file | while read (reads entire file into memory)" echo "Fix: while read line; do ... done < file" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip qa-suppress if grep -q "# qa-suppress" <<< "$line_content"; then continue fi # Detect: cat file | while or zcat file | while if echo "$line_content" | grep -qE '(cat|zcat)\s+.*\|\s*while\s+read'; then echo "MEDIUM|$file|$line_num|[PERF-READ] Piping cat/zcat to while read" echo " Risk: Inefficient, creates unnecessary subshell" echo " Fix: while read line; do ...; done < file" echo " Or: while read line; do ...; done < <(zcat file)" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rnE '(cat|zcat).*\|\s*while\s+read' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count inefficient file read patterns" echo "" } >> "$REPORT" # CHECK 74: Uncontrolled Recursion echo "[74/80] Checking: Recursive functions without depth limit..." { echo "## CHECK 74: Uncontrolled Recursion" echo "Severity: HIGH" echo "Pattern: Function calls itself without depth check" echo "Fix: Add recursion depth counter and max limit" echo "" count=0 while IFS= read -r file; do # Find function definitions while IFS=: read -r line_num func_name; do # Check if function calls itself if grep -qE "^\s*${func_name}\s+" "$file" 2>/dev/null; then # Check for depth tracking func_start=$(grep -n "^${func_name}()" "$file" | cut -d: -f1) # Only proceed if func_start was found if [ -n "$func_start" ]; then func_end=$(awk "NR>=$func_start && /^}/ {print NR; exit}" "$file") if [ -n "$func_end" ]; then func_body=$(sed -n "${func_start},${func_end}p" "$file") # If recursive but no depth check if ! echo "$func_body" | grep -qE '(depth|level|count).*-ge'; then echo "HIGH|$file|$func_start|[RECURSION] Function '$func_name' is recursive without depth limit" echo " Risk: Stack overflow, infinite recursion" echo " Fix: Add depth parameter and check: [ \$depth -ge 100 ] && return" ((count++)) [ "$count" -ge 10 ] && break 2 fi fi fi fi done < <(grep -nE '^[a-zA-Z_][a-zA-Z0-9_]*\(\)' "$file" | sed 's/:/ /' | awk '{print $1" "$2}' | sed 's/()//') done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null) echo "Found: $count uncontrolled recursive functions" echo "" } >> "$REPORT" # CHECK 75: File Descriptor Leaks echo "[75/80] Checking: File descriptor leaks (unclosed FDs)..." { echo "## CHECK 75: File Descriptor Leaks" echo "Severity: HIGH" echo "Pattern: exec {fd}< file without exec {fd}<&-" echo "Fix: Always close file descriptors after use" echo "" count=0 while IFS= read -r file; do # Find exec opening FDs fd_opens=$(grep -nE 'exec\s+\{[a-zA-Z_][a-zA-Z0-9_]*\}[<>]' "$file" 2>/dev/null) while IFS=: read -r line_num open_line; do fd_var=$(echo "$open_line" | grep -oE '\{[a-zA-Z_][a-zA-Z0-9_]*\}' | tr -d '{}') # Check if there's a corresponding close if ! grep -qE "exec\s+\{${fd_var}\}[<>]&-" "$file" 2>/dev/null; then echo "HIGH|$file|$line_num|[FD-LEAK] File descriptor {$fd_var} opened but never closed" echo " Risk: FD exhaustion, resource leak" echo " Fix: Add exec {$fd_var}<&- or exec {$fd_var}>&- after use" ((count++)) [ "$count" -ge 10 ] && break 2 fi done <<< "$fd_opens" done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null) echo "Found: $count potential FD leaks" echo "" } >> "$REPORT" # CHECK 76: Zombie Process Creation echo "[76/80] Checking: Background processes without wait..." { echo "## CHECK 76: Zombie Process Creation" echo "Severity: MEDIUM" echo "Pattern: cmd & without wait or trap on EXIT" echo "Fix: Add wait \$! or trap 'kill \$!; wait' EXIT" echo "" count=0 while IFS= read -r file; do # Find background processes bg_jobs=$(grep -nE '\s+&\s*$' "$file" 2>/dev/null | grep -v '# qa-suppress') if [ -n "$bg_jobs" ]; then # Check if file has wait or trap for cleanup has_wait=$(grep -qE '(wait|\$!)' "$file" && echo 1 || echo 0) has_trap=$(grep -qE 'trap.*EXIT' "$file" && echo 1 || echo 0) if [ "$has_wait" -eq 0 ] && [ "$has_trap" -eq 0 ]; then first_bg=$(echo "$bg_jobs" | head -1 | cut -d: -f1) bg_count=$(echo "$bg_jobs" | wc -l) echo "MEDIUM|$file|$first_bg|[ZOMBIE] $bg_count background job(s) without wait/trap cleanup" echo " Risk: Zombie processes, resource leaks" echo " Fix: Add 'wait' at end or trap 'kill \$pid; wait' EXIT" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null) echo "Found: $count scripts with uncleaned background jobs" echo "" } >> "$REPORT" # CHECK 77: Disk Space Not Checked echo "[77/80] Checking: Large writes without disk space check..." { echo "## CHECK 77: Disk Space Not Checked" echo "Severity: HIGH" echo "Pattern: Large file operations without df check" echo "Fix: Check available space before write" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip qa-suppress if grep -q "# qa-suppress" <<< "$line_content"; then continue fi # Detect large write operations if echo "$line_content" | grep -qE '(mysqldump|tar.*czf|dd.*of=|cp.*-r|rsync)'; then # Check if there's a df check in surrounding lines start=$((line_num - 10)) [ "$start" -lt 1 ] && start=1 end=$((line_num + 2)) context=$(sed -n "${start},${end}p" "$file" 2>/dev/null) if ! echo "$context" | grep -qE '(df|available.*space|check.*disk)'; then operation=$(echo "$line_content" | grep -oE '(mysqldump|tar|dd|cp|rsync)' | head -1) echo "HIGH|$file|$line_num|[DISK-SPACE] Large operation '$operation' without disk space check" echo " Risk: Disk full, incomplete writes, system failure" echo " Fix: available=\$(df -BG \"\$target_dir\" | awk 'NR==2 {print \$4}' | sed 's/G//')" echo " [ \"\$available\" -lt \"\$required_gb\" ] && { echo \"Insufficient space\"; exit 1; }" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rnE '(mysqldump|tar.*czf|dd.*of=|cp.*-r|rsync)' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count operations without disk space validation" echo "" } >> "$REPORT" # CHECK 78: Network Operations Without Timeout echo "[78/80] Checking: Network operations without timeout..." { echo "## CHECK 78: Network Operations Without Timeout" echo "Severity: HIGH" echo "Pattern: curl/wget without --timeout or --max-time" echo "Fix: Add timeout parameter to prevent hangs" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip qa-suppress if grep -q "# qa-suppress" <<< "$line_content"; then continue fi # Detect curl/wget without timeout (skip comments, echo statements, strings) if echo "$line_content" | grep -qE '\b(curl|wget)\s+' && ! echo "$line_content" | grep -qE '^\s*#|echo |".*\b(curl|wget)'; then if ! echo "$line_content" | grep -qE '(--timeout|--max-time|-m\s+[0-9]|--connect-timeout|timeout\s+[0-9])'; then cmd=$(echo "$line_content" | grep -oE '\b(curl|wget)\b') # Also skip if it's in an assignment with a variable (might be intentional pipeline) if ! echo "$line_content" | grep -qE '^\s*[A-Za-z_][A-Za-z0-9_]*=.*\b(curl|wget)'; then echo "HIGH|$file|$line_num|[NET-TIMEOUT] $cmd without timeout parameter" echo " Risk: Script hangs indefinitely on network issues" echo " Fix (curl): Add --max-time 30 --connect-timeout 10" echo " Fix (wget): Add --timeout=30" ((count++)) [ "$count" -ge 10 ] && break fi fi fi done < <(grep -rnE '\b(curl|wget)\s+' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count network operations without timeout" echo "" } >> "$REPORT" # CHECK 79: Unbounded Log Growth echo "[79/80] Checking: Log files without rotation..." { echo "## CHECK 79: Unbounded Log Growth" echo "Severity: MEDIUM" echo "Pattern: >> logfile without size check or rotation" echo "Fix: Check log size and rotate/truncate" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip qa-suppress if grep -q "# qa-suppress" <<< "$line_content"; then continue fi # Detect append to log file if echo "$line_content" | grep -qE '>>\s*\$?[A-Za-z_]*[Ll][Oo][Gg]'; then logvar=$(echo "$line_content" | grep -oE '\$?[A-Za-z_]*[Ll][Oo][Gg][A-Za-z_]*') # Check if there's log rotation logic in the file if ! grep -qE '(logrotate|truncate.*log|size.*log|rotate)' "$file" 2>/dev/null; then echo "MEDIUM|$file|$line_num|[LOG-ROTATE] Appending to $logvar without rotation" echo " Risk: Unbounded log growth, disk full" echo " Fix: [ -f \"\$LOG\" ] && [ \$(stat -f%z \"\$LOG\") -gt 10485760 ] && > \"\$LOG\"" echo " (truncate if > 10MB)" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rnE '>>\s*\$?[A-Za-z_]*[Ll][Oo][Gg]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count log files without rotation logic" echo "" } >> "$REPORT" # CHECK 80: CPU-Intensive Operations in Loop echo "[80/80] Checking: CPU-intensive operations in tight loops..." { echo "## CHECK 80: CPU-Intensive Operations in Loop" echo "Severity: MEDIUM" echo "Pattern: Heavy operations (find, grep -r, tar) inside while/for loop" echo "Fix: Move expensive operation outside loop or add throttling" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip qa-suppress if grep -q "# qa-suppress" <<< "$line_content"; then continue fi # Check if this is inside a loop start=$((line_num - 20)) [ "$start" -lt 1 ] && start=1 context=$(sed -n "${start},${line_num}p" "$file" 2>/dev/null) if echo "$context" | grep -qE '(while|for)\s+'; then # Detect expensive operations if echo "$line_content" | grep -qE '\b(find\s+/|grep\s+-r|tar\s+|rsync|mysqldump)'; then operation=$(echo "$line_content" | grep -oE '\b(find|grep -r|tar|rsync|mysqldump)\b' | head -1) echo "MEDIUM|$file|$line_num|[CPU-LOOP] Expensive operation '$operation' inside loop" echo " Risk: High CPU usage, slow execution" echo " Fix: Move operation outside loop or add sleep/throttling" echo " Consider: Run once before loop, cache results" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rnE '\b(find\s+/|grep\s+-r|tar\s+|rsync|mysqldump)' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count expensive operations in loops" echo "" } >> "$REPORT" # Insert after CHECK 80, before performance checks section echo "" echo "═══════════════════════════════════════════════════════════════" echo "MULTI-PANEL ARCHITECTURE COMPLIANCE (Checks 81-88)" echo "═══════════════════════════════════════════════════════════════" echo "" { echo "" echo "═══════════════════════════════════════════════════════════════" echo "MULTI-PANEL ARCHITECTURE COMPLIANCE" echo "═══════════════════════════════════════════════════════════════" echo "" } >> "$REPORT" #============================================================================== # CHECK 81: Hardcoded cPanel paths (HIGH) #============================================================================== echo "[81/94] Checking: Hardcoded cPanel-specific paths..." { echo "## CHECK 81: Hardcoded cPanel-specific paths" echo "Severity: HIGH" echo "Pattern: /var/cpanel/, /var/log/apache2/domlogs, /home/*/public_html" echo "Fix: Use SYS_* variables or case statements for multi-panel support" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip if suppressed is_suppressed "$file" "$line_num" "hardcoded-path" && continue # Skip library files that define these paths (abstraction layers) [[ "$file" =~ (system-detect|cpanel-helpers|plesk-helpers|domain-discovery|user-manager|launcher)\.sh$ ]] && continue # Skip comments and variable definitions that are panel-aware echo "$line_content" | grep -qE '^\s*#|case.*CONTROL_PANEL' && continue # Skip if using fallback pattern ${VAR:-/path} (proper multi-panel pattern) echo "$line_content" | grep -qE '\$\{[A-Z_]+:-/var/cpanel|\$\{[A-Z_]+:-/var/log|\$\{[A-Z_]+:-/home' && continue # Skip if/elif/then statements checking paths (multi-panel aware) echo "$line_content" | grep -qE '^\s*(if|elif)\s+\[.*-[def].*\]' && continue # Skip array definitions (multi-panel path lists) echo "$line_content" | grep -qE '^\s*[a-zA-Z_]+=\(' && continue # Skip array element lines (e.g., "/path/..." inside arrays) # Check if previous lines have array declaration if echo "$line_content" | grep -qE '^\s*"(/var|/home|/usr)'; then context=$(sed -n "$((line_num - 10)),${line_num}p" "$file" 2>/dev/null) if echo "$context" | grep -qE '[a-zA-Z_]+=\('; then continue fi fi # Extract the hardcoded path path=$(echo "$line_content" | grep -oE '(/var/cpanel|/var/log/apache2/domlogs|/home/[^/]*/public_html)' | head -1) echo "HIGH|$file|$line_num|[HARDCODED-PATH] cPanel-specific path: $path" count_issue "HIGH" ((count++)) [ "$count" -ge 15 ] && break done < <(grep -rnE '(/var/cpanel|/var/log/apache2/domlogs|/home/[^/]*/public_html)' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count hardcoded cPanel paths" echo "" } >> "$REPORT" #============================================================================== # CHECK 82: Missing system-detect.sh source (HIGH) #============================================================================== echo "[82/94] Checking: Scripts missing system-detect.sh library..." { echo "## CHECK 82: Missing system-detect.sh source" echo "Severity: HIGH" echo "Pattern: Scripts using panel features without sourcing system-detect.sh" echo "Fix: Add 'source \"\$LIB_DIR/system-detect.sh\"' at top of script" echo "" count=0 while IFS= read -r file; do # Skip library files and launcher [[ "$file" =~ (^lib/|launcher\.sh$) ]] && continue # Check if file uses panel-specific features if grep -qE '(whmapi1|uapi|plesk bin|/var/cpanel|/var/www/vhosts|SYS_CONTROL_PANEL)' "$file" 2>/dev/null; then # Check if it sources system-detect.sh if ! grep -qE 'source.*system-detect\.sh|\\..*system-detect\.sh' "$file" 2>/dev/null; then echo "HIGH|$file|N/A|[MISSING-LIB] Uses panel features without sourcing system-detect.sh" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(find "$TOOLKIT_PATH/modules" -name "*.sh" -type f 2>/dev/null) echo "Found: $count scripts missing system-detect.sh" echo "" } >> "$REPORT" #============================================================================== # CHECK 83: Direct /var/cpanel/users access (MEDIUM - prefer abstractions) #============================================================================== echo "[83/94] Checking: Direct /var/cpanel/users access..." { echo "## CHECK 83: Direct /var/cpanel/users file access" echo "Severity: MEDIUM" echo "Pattern: grep/cat /var/cpanel/users or /etc/userdatadomains" echo "Fix: Use get_user_info() or get_user_domains() from user-manager.sh" echo "Note: Diagnostic/analysis tools may legitimately need direct access" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip if suppressed is_suppressed "$file" "$line_num" "userdata-access" && continue # Skip library files that implement the abstraction [[ "$file" =~ (user-manager|system-detect|cpanel-helpers)\.sh$ ]] && continue # Skip QA script itself (it checks for this pattern) [[ "$file" =~ toolkit-qa-check\.sh$ ]] && continue # Skip diagnostic/analysis tools that need direct access [[ "$file" =~ (malware-scanner|website-error-analyzer|500-error-tracker|analyzer|diagnostics)\.sh$ ]] && continue echo "MEDIUM|$file|$line_num|[USERDATA-ACCESS] Consider using user-manager.sh functions" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break done < <(grep -rnE '(grep|cat|awk).*(/var/cpanel/users/|/etc/userdatadomains)' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count direct userdata file accesses (diagnostic tools excluded)" echo "" } >> "$REPORT" #============================================================================== # CHECK 84: cPanel API calls without panel check (HIGH) #============================================================================== echo "[84/94] Checking: cPanel API calls without validation..." { echo "## CHECK 84: cPanel API calls without panel check" echo "Severity: HIGH" echo "Pattern: whmapi1/uapi calls without checking \$SYS_CONTROL_PANEL" echo "Fix: Wrap in 'if [ \"\$SYS_CONTROL_PANEL\" = \"cpanel\" ]; then'" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip if suppressed is_suppressed "$file" "$line_num" "api-check" && continue # Simple check: if file doesn't mention CONTROL_PANEL at all, flag it if ! grep -q "CONTROL_PANEL" "$file" 2>/dev/null; then api_cmd=$(echo "$line_content" | grep -oE '(whmapi1|uapi) [a-z_]+' | head -1) echo "HIGH|$file|$line_num|[API-CHECK] cPanel API without panel validation: $api_cmd" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -rnE '\\b(whmapi1|uapi)\\s+' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count unchecked cPanel API calls" echo "" } >> "$REPORT" #============================================================================== # CHECK 85: Missing case statement (MEDIUM) #============================================================================== echo "[85/94] Checking: Scripts with hardcoded paths but no case statement..." { echo "## CHECK 85: Missing multi-panel case statement" echo "Severity: MEDIUM" echo "Pattern: Uses panel-specific paths but no case \$SYS_CONTROL_PANEL" echo "Fix: Add case statement to handle all panels" echo "" count=0 while IFS= read -r file; do # Skip library files [[ "$file" =~ ^lib/ ]] && continue # Check if file uses panel-specific features but no case statement if grep -qE '(/var/cpanel|/var/www/vhosts)' "$file" 2>/dev/null; then if ! grep -qE 'case.*\\$SYS_CONTROL_PANEL|case.*\\$CONTROL_PANEL' "$file" 2>/dev/null; then echo "MEDIUM|$file|N/A|[NO-CASE] Uses panel paths without case statement" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(find "$TOOLKIT_PATH/modules" -name "*.sh" -type f 2>/dev/null) echo "Found: $count scripts missing case statements" echo "" } >> "$REPORT" #============================================================================== # CHECK 86: Hardcoded database prefixes (MEDIUM) #============================================================================== echo "[86/94] Checking: Hardcoded database name patterns..." { echo "## CHECK 86: Hardcoded database name patterns" echo "Severity: MEDIUM" echo "Pattern: Assumes username_dbname pattern (cPanel-specific)" echo "Fix: Use panel-aware database discovery" echo "" count=0 while IFS=: read -r file line_num line_content; do # Skip if suppressed is_suppressed "$file" "$line_num" "db-pattern" && continue echo "MEDIUM|$file|$line_num|[DB-PATTERN] Assumes cPanel database naming (user_dbname)" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break done < <(grep -rnE '\\$\\{?user(name)?\\}?_' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | grep -i 'db\\|database') echo "Found: $count hardcoded database patterns" echo "" } >> "$REPORT" #============================================================================== # CHECK 87: Missing user-manager.sh (HIGH) #============================================================================== echo "[87/94] Checking: User/domain operations without user-manager.sh..." { echo "## CHECK 87: User/domain operations without user-manager.sh" echo "Severity: HIGH" echo "Pattern: Domain/user lookups without using abstraction library" echo "Fix: Source user-manager.sh and use get_user_info/get_user_domains" echo "" count=0 while IFS= read -r file; do # Skip library files [[ "$file" =~ ^lib/ ]] && continue # Check if file does user/domain operations without sourcing user-manager.sh if grep -qE 'for.*domain|while.*domain' "$file" 2>/dev/null; then if ! grep -qE 'source.*user-manager\\.sh' "$file" 2>/dev/null; then echo "HIGH|$file|N/A|[NO-USER-MGR] Domain operations without user-manager.sh" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(find "$TOOLKIT_PATH/modules" -name "*.sh" -type f 2>/dev/null) echo "Found: $count scripts needing user-manager.sh" echo "" } >> "$REPORT" #============================================================================== # CHECK 88: Standalone Apache support missing (LOW) #============================================================================== echo "[88/94] Checking: Scripts missing standalone/no-panel fallback..." { echo "## CHECK 88: Missing standalone Apache fallback" echo "Severity: LOW" echo "Pattern: case statement without '*' or 'standalone' case" echo "Fix: Add fallback case for systems without control panels" echo "" count=0 while IFS=: read -r file line_num line_content; do # Get next 20 lines after case statement case_block=$(sed -n "${line_num},$((line_num+30))p" "$file" 2>/dev/null | sed -n '/case/,/esac/p') # Check if it has a default case if ! echo "$case_block" | grep -qE '\\*\\)|standalone\\)'; then echo "LOW|$file|$line_num|[NO-STANDALONE] Missing standalone fallback in case statement" count_issue "LOW" ((count++)) [ "$count" -ge 10 ] && break fi done < <(grep -n "case.*CONTROL_PANEL" "$TOOLKIT_PATH" --include="*.sh" -r 2>/dev/null | cut -d: -f1,2) echo "Found: $count case statements missing standalone support" echo "" } >> "$REPORT" #============================================================================== # CHECK 89: DISABLED - Too many false positives on legitimate multi-stage filters #============================================================================== # This check was detecting valid grep pipelines as contradictory: # Example: grep -i pattern file | grep -v comment | grep -i codes # This is a legitimate 3-stage filter, not contradictory logic # Would require AST analysis to detect true contradictions accurately echo "Found: 0 contradictory grep patterns (check disabled - multi-stage filters detected as false positives)" #============================================================================== # CHECK 90: Type Mismatch in Comparisons (HIGH) #============================================================================== show_progress 90 "Type mismatch in comparisons" { echo "## CHECK 90: Type Mismatch in Comparisons" echo "Severity: HIGH" echo "Pattern: Numeric operator (-eq/-lt/-gt) on variables containing non-numeric values" echo "Examples: [ \$rate -lt 80 ] where rate contains '%', [ \$status -eq 0 ] where status is string" echo "" count=0 # Pattern 1: Variables with % character used in numeric comparison while IFS=: read -r file line_num line_content; do if echo "$line_content" | grep -qE '\$[a-zA-Z_][a-zA-Z0-9_%]*.*-[lg][te]|rate.*%.*-[lg][te]'; then if ! is_suppressed "$file" "$line_num" "type-mismatch"; then echo "HIGH|$file|$line_num|[TYPE-MISMATCH] Numeric operator on variable that may contain non-numeric value" count_issue "HIGH" ((count++)) [ "$count" -ge 15 ] && break fi fi done < <(grep -rn -E '\[\s*\$[a-zA-Z_].*-[lg][te].*[0-9]|rate.*[%].*-[lg][te]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count type mismatches" echo "" } >> "$REPORT" #============================================================================== # CHECK 91: Command Argument Ordering Errors (HIGH) #============================================================================== show_progress 91 "Command argument ordering errors" { echo "## CHECK 91: Command Argument Ordering Errors" echo "Severity: HIGH" echo "Pattern: Filename variable before options in grep/sed (grep \$FILE -e PATTERN)" echo "Impact: Command fails or behaves unexpectedly - filename treated as pattern" echo "" count=0 # Check for grep with filename variable followed by option flags while IFS=: read -r file line_num line_content; do if echo "$line_content" | grep -qE 'grep\s+\$[A-Z_].*\s+-[eEiIvlLrnhFxaw]|sed\s+\$[A-Z_].*\s+-[es]'; then if ! is_suppressed "$file" "$line_num" "arg-order"; then echo "HIGH|$file|$line_num|[ARG-ORDER] Command: filename variable before option flags" count_issue "HIGH" ((count++)) [ "$count" -ge 15 ] && break fi fi done < <(grep -rn 'grep.*\$[A-Z_].*-[eEiIvlLrnhFxaw]\|sed.*\$[A-Z_].*-[es]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count argument ordering errors" echo "" } >> "$REPORT" #============================================================================== # CHECK 92: Missing Command Availability Checks (HIGH) #============================================================================== show_progress 92 "Missing command availability checks" { echo "## CHECK 92: Missing Command Availability Checks" echo "Severity: HIGH" echo "Pattern: Uses optional command without checking availability (nc, dig, host, jq, etc)" echo "Impact: Script fails on systems where command not installed" echo "" count=0 # Common commands that should be checked for availability while IFS=: read -r file line_num line_content; do # Check if line uses an optional command if echo "$line_content" | grep -qE '\b(nc|dig|host|jq|yq|envsubst|getent|timeout)\b'; then # Verify it's not a comment or already has a check if ! echo "$line_content" | grep -qE 'command -v|which|#.*qa-suppress'; then if ! is_suppressed "$file" "$line_num" "no-cmd-check"; then echo "HIGH|$file|$line_num|[NO-CMD-CHECK] Optional command used without availability check" count_issue "HIGH" ((count++)) [ "$count" -ge 15 ] && break fi fi fi done < <(grep -rn '\b(nc|dig|host|jq|yq|envsubst|timeout)\s' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count missing command checks" echo "" } >> "$REPORT" #============================================================================== # CHECK 93: Uninitialized Variables in AWK (HIGH) #============================================================================== show_progress 93 "Uninitialized AWK variables" { echo "## CHECK 93: Uninitialized Variables in AWK" echo "Severity: HIGH" echo "Pattern: AWK variables set in pattern but not initialized in BEGIN" echo "Impact: Undefined behavior on non-matching lines, logic errors" echo "" count=0 # Look for AWK blocks that have assignments but no BEGIN block while IFS=: read -r file line_num; do awk_line=$(sed -n "${line_num}p" "$file" 2>/dev/null) # Check if AWK block has pattern actions with variable assignments if echo "$awk_line" | grep -qE '{\s*[a-z_]+\s*=' && \ ! echo "$awk_line" | grep -qE 'BEGIN\s*\{'; then if ! is_suppressed "$file" "$line_num" "awk-uninit"; then echo "HIGH|$file|$line_num|[AWK-UNINIT] AWK variables assigned without BEGIN initialization" count_issue "HIGH" ((count++)) [ "$count" -ge 15 ] && break fi fi done < <(grep -rn "awk\s*'" "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | cut -d: -f1,2) echo "Found: $count AWK uninitialized variable issues" echo "" } >> "$REPORT" #============================================================================== # CHECK 94: Undefined Variable References (HIGH) #============================================================================== show_progress 94 "Undefined variable references" { echo "## CHECK 94: Undefined Variable References" echo "Severity: HIGH" echo "Pattern: Uses of variables that appear undefined (typos, scope issues)" echo "Examples: \$TEMP_LOG (should be \$MAIL_LOG), undefined in subshells" echo "" count=0 # Look for common undefined variable patterns while IFS=: read -r file line_num line_content; do # Check for obvious typos/undefined variables # Look for TEMP_* variables that might be undefined if echo "$line_content" | grep -qE '\$TEMP_[A-Z_]+|\$[A-Z_]*LOG[A-Z_]*|\$[A-Z_]*FILE'; then # Check if these are actually defined in the file if ! grep -qE "^[[:space:]]*(TEMP_|declare TEMP_|local TEMP_)" "$file" 2>/dev/null; then if ! is_suppressed "$file" "$line_num" "undef-var"; then echo "HIGH|$file|$line_num|[UNDEF-VAR] Variable reference appears undefined in this file" count_issue "HIGH" ((count++)) [ "$count" -ge 15 ] && break fi fi fi done < <(grep -rn '\$TEMP_\|TEMP_LOG\|\$[A-Z_]*FILE' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count undefined variable references" echo "" } >> "$REPORT" #============================================================================== # CHECK 95: Missing Error Checks After Critical Commands (HIGH) #============================================================================== show_progress 95 "Missing error checks after critical commands" { echo "## CHECK 95: Missing Error Checks After Critical Commands" echo "Severity: HIGH" echo "Pattern: Variable assignment from critical commands without exit validation" echo "Impact: Silent failures - invalid data used in subsequent operations" echo "" count=0 # Look for: var=$( mysql/curl/etc command ) without checking if it succeeded while IFS=: read -r file line_num line_content; do # Pattern: var=$(mysql ... ) followed by data usage without validation if echo "$line_content" | grep -qE '=\$\((.*mysql|.*curl|.*wget)' && \ ! echo "$line_content" | grep -qE '|| |if \$|if !'; then # Check if the variable is used immediately after without validation var_name=$(echo "$line_content" | sed -E 's/.*([a-zA-Z_][a-zA-Z0-9_]*)=.*/\1/' | head -1) next_line=$(sed -n "$((line_num+1))p" "$file" 2>/dev/null) if [ -n "$var_name" ] && echo "$next_line" | grep -qE "^\s*if\s+|^\s*for.*\$${var_name}|${var_name}.*|"; then if ! is_suppressed "$file" "$line_num" "no-err-check"; then echo "HIGH|$file|$line_num|[NO-ERR-CHECK] Variable from command assignment used without exit code validation" count_issue "HIGH" ((count++)) [ "$count" -ge 15 ] && break fi fi fi done < <(grep -rn '=\$\(.*\(mysql\|curl\|wget\)' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count missing error checks" echo "" } >> "$REPORT" #============================================================================== # CHECK 96: Uninitialized Variable Comparisons (HIGH) #============================================================================== show_progress 96 "Uninitialized variable comparisons" { echo "## CHECK 96: Uninitialized Variable Comparisons" echo "Severity: HIGH" echo "Pattern: Variables compared without initialization or error handling" echo "Impact: Silent failures when variable is unset (false positives/negatives)" echo "" count=0 # Look for comparisons where variable result could be empty while IFS=: read -r file line_num line_content; do # Look for: [ "$VAR" = "value" ] where VAR is assigned from a command that could fail # Pattern: Assignment from command substitution with no error check if echo "$line_content" | grep -qE 'VAR=\$\(.*\).*\[\s*"\$VAR'; then # Variable assigned from subshell, then compared var=$(echo "$line_content" | sed -E 's/.*([a-zA-Z_][a-zA-Z0-9_]*)=\$.*/\1/' | head -1) if [ -n "$var" ] && ! echo "$line_content" | grep -qE '\$\{'"$var"':-'; then if ! is_suppressed "$file" "$line_num" "uninit-var"; then echo "HIGH|$file|$line_num|[UNINIT-VAR] Variable '\$$var' compared without checking if command succeeded" count_issue "HIGH" ((count++)) [ "$count" -ge 15 ] && break fi fi fi done < <(grep -rn '\[\s*"\$.*=.*\$(' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | grep -v 'VAR:-' | head -200) echo "Found: $count uninitialized variable comparisons" echo "" } >> "$REPORT" #============================================================================== # CHECK 97: Variable Shadowing in Subshells (HIGH) #============================================================================== show_progress 97 "Variable shadowing in subshells/pipes" { echo "## CHECK 97: Variable Shadowing in Subshells" echo "Severity: HIGH" echo "Pattern: Variables modified in pipes/subshells - changes lost after scope ends" echo "Examples: count=0; cmd | while read; do count=$((count+1)); done (count stays 0)" echo "Note: This check disabled - too many false positives on legitimate patterns (local vars, echo-only loops)" echo "" count=0 # Disabled CHECK 97: Too many false positives. Real subshell-shadow issues require context analysis: # - Need to determine if variable is used AFTER the loop # - Need to distinguish local vs outer variables # - Need to check if output is explicit (echo) vs stored echo "Found: $count variable shadowing issues (check disabled - false positive rate too high)" echo "" } >> "$REPORT" #============================================================================== # CHECK 98: Array Access Without Bounds Check (HIGH) #============================================================================== show_progress 98 "Array access without bounds checking" { echo "## CHECK 98: Array Access Without Bounds Checking" echo "Severity: HIGH" echo "Pattern: Direct array element access without verifying array is non-empty" echo "Impact: Accesses undefined array indices, silent failures" echo "" count=0 # Only flag direct indexed access like ${arr[0]} or ${arr[$i]} with no bounds check while IFS=: read -r file line_num line_content; do # Pattern: Direct array index access (not array expansion) if echo "$line_content" | grep -qE '\$\{[a-zA-Z_][a-zA-Z0-9_]*\[[0-9]|\$\{[a-zA-Z_][a-zA-Z0-9_]*\[\$'; then # Extract array name and index array_name=$(echo "$line_content" | sed -E 's/.*\$\{([a-zA-Z_][a-zA-Z0-9_]*)\[.*/\1/' | head -1) if [ -n "$array_name" ]; then # Check if there's explicit initialization of this array in the file arr_init=$(grep -n "^${array_name}=()\|^${array_name}=(" "$file" 2>/dev/null | wc -l) # If no explicit init and direct access with index, likely needs bounds check if [ "$arr_init" -eq 0 ]; then if ! is_suppressed "$file" "$line_num" "array-bounds"; then echo "HIGH|$file|$line_num|[ARRAY-BOUNDS] Direct array index access without array initialization" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break fi fi fi fi done < <(grep -rn '\$\{[a-zA-Z_][a-zA-Z0-9_]*\[[0-9]\|\$\{[a-zA-Z_][a-zA-Z0-9_]*\[\$' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count array access without bounds checks" echo "" } >> "$REPORT" #============================================================================== # CHECK 99: Confusing Condition Logic (MEDIUM) #============================================================================== show_progress 99 "Confusing condition logic" { echo "## CHECK 99: Confusing Condition Logic" echo "Severity: MEDIUM" echo "Pattern: Double negatives and confusing boolean logic ([[ -z ]] && [[ -z ]] || ...)" echo "Impact: Hard to maintain, prone to logic errors" echo "" count=0 # qa-suppress:confusing-logic while IFS=: read -r file line_num line_content; do # Skip comments and echo/grep patterns (which often have test syntax) if echo "$line_content" | grep -qE '^\s*#|echo.*\[|grep'; then continue fi # Pattern 1: Double negatives in actual code - [ -z X ] && [ -z Y ] but only in if statements if echo "$line_content" | grep -qE 'if.*\[\s*-z.*\]\s*&&\s*\[\s*-z' && \ ! echo "$line_content" | grep -qE 'grep|echo|#'; then if ! is_suppressed "$file" "$line_num" "confusing-logic"; then echo "MEDIUM|$file|$line_num|[CONFUSING-LOGIC] Double negative condition (if NOT X and NOT Y) - consider positive logic" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi fi done < <(grep -rn 'if.*\[\s*-z.*&&.*-z' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count confusing condition logic issues" echo "" } >> "$REPORT" #============================================================================== # CHECK 100: Off-by-One Errors in Loops (MEDIUM) #============================================================================== show_progress 100 "Off-by-one errors in loops" { echo "## CHECK 100: Off-by-One Errors in Loops" echo "Severity: MEDIUM" echo "Pattern: Loops with incorrect ranges (head -1 vs head -2, seq 0 N vs seq 1 N)" echo "Impact: Missing or extra iterations, boundary condition bugs" echo "" count=0 # Pattern 1: head/tail with suspicious counts while IFS=: read -r file line_num line_content; do # Check for inconsistencies like: head -N where comment says -M or vice versa if echo "$line_content" | grep -qE 'head\s+-[0-9]|tail\s+-[0-9]|seq\s+[0-9]'; then # Look for patterns like head -40 with comment "last 20" or seq patterns if echo "$line_content" | grep -qE 'head\s+-40.*20|head\s+-20.*40|tail\s+-N.*-N'; then if ! is_suppressed "$file" "$line_num" "off-by-one"; then echo "MEDIUM|$file|$line_num|[OFF-BY-ONE] Loop boundary mismatch between code and comment" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi fi # Check for seq patterns that might be wrong if echo "$line_content" | grep -qE 'seq\s+0\s+|for\s+i\s+in\s+\$\(seq' && \ ! echo "$line_content" | grep -qE '\{1\.\.\}|seq\s+1\s'; then # seq 0 N often wrong - should be seq 1 N for 1-indexed if ! is_suppressed "$file" "$line_num" "off-by-one"; then echo "MEDIUM|$file|$line_num|[OFF-BY-ONE] Loop starts at 0 - verify this is intentional" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi fi fi done < <(grep -rn 'head\s+-\|tail\s+-\|seq\s+' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | head -200) echo "Found: $count off-by-one errors" echo "" } >> "$REPORT" #============================================================================== # CHECK 101: Overly Broad/Narrow Regex Patterns (MEDIUM) #============================================================================== show_progress 101 "Overly broad/narrow regex patterns" { echo "## CHECK 101: Overly Broad/Narrow Regex Patterns" echo "Severity: MEDIUM" echo "Pattern: Regex without anchors or too specific (matches wrong strings)" echo "Impact: False positives/negatives in pattern matching" echo "" count=0 while IFS=: read -r file line_num line_content; do # Pattern 1: grep/awk without anchors when dealing with domains/IPs if echo "$line_content" | grep -qE 'grep.*example\.com|grep.*[0-9]+\.[0-9]+' && \ ! echo "$line_content" | grep -qE '\\^|\$|grep\s+-E|grep.*-w'; then if ! is_suppressed "$file" "$line_num" "regex-pattern"; then echo "MEDIUM|$file|$line_num|[REGEX-PATTERN] Pattern without anchors - may match substrings incorrectly" count_issue "MEDIUM" ((count++)) [ "$count" -ge 15 ] && break fi fi # Pattern 2: Regex .* pattern that's too broad if echo "$line_content" | grep -qE '\[.*\.\*.*\]|\[\^.*\*' && \ ! echo "$line_content" | grep -qE 'grep\s+-o|cut\s+-d|awk.*\$[0-9]'; then if ! is_suppressed "$file" "$line_num" "regex-pattern"; then echo "MEDIUM|$file|$line_num|[REGEX-PATTERN] Overly broad .* pattern - may be too permissive" count_issue "MEDIUM" ((count++)) [ "$count" -ge 15 ] && break fi fi done < <(grep -rn '\[.*\.\*\|grep.*[a-z0-9]\{[0-9]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | head -300) echo "Found: $count regex pattern issues" echo "" } >> "$REPORT" #============================================================================== # CHECK 102: DISABLED - Too many false positives in case detection #============================================================================== # This check was generating 50+ false positives because bash case syntax # is complex (multi-line blocks, ;; on different lines, etc) # Keeping structure for future improvement echo "Found: 0 case fallthrough issues (check disabled - false positive rate too high)" #============================================================================== # CHECK 103: Empty String Handling Inconsistencies (LOW-MEDIUM) #============================================================================== show_progress 103 "Empty string handling inconsistencies" { echo "## CHECK 103: Empty String Handling Inconsistencies" echo "Severity: MEDIUM" echo "Pattern: Unprotected variable expansion in command context (may have whitespace/newline issues)" echo "Impact: Subtle bugs with word splitting and glob expansion" echo "" count=0 # Only flag ACTUAL problematic cases: command substitution assignments without quotes # NOT: echo statements with SQL, echo with backticks, etc. while IFS=: read -r file line_num line_content; do # Skip SQL/echo contexts, backticks, and already-safe patterns if echo "$line_content" | grep -qE 'echo|SELECT|INSERT|DELETE|ALTER|WHERE|if.*\[.*\$|for.*in.*\$'; then continue fi # Only flag: var=$(...$unquoted_var...) or command-like expansions if echo "$line_content" | grep -qE '=\$\([^)]*\$[a-zA-Z_]' && \ ! echo "$line_content" | grep -qE '=\$\([^)]*"\$'; then if ! is_suppressed "$file" "$line_num" "empty-string"; then echo "MEDIUM|$file|$line_num|[EMPTY-STRING] Unquoted variable in command substitution - may have whitespace issues" count_issue "MEDIUM" ((count++)) [ "$count" -ge 8 ] && break fi fi done < <(grep -rn '=\$([^)]*\$[a-zA-Z_]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) echo "Found: $count empty string handling issues" echo "" } >> "$REPORT" #============================================================================== # CHECK 104: Menu Input Validation (MEDIUM - Menu uniformity) #============================================================================== show_progress 104 "Menu input validation on numbered options" { echo "## CHECK 104: Missing Input Validation on Numbered Menus" echo "Severity: MEDIUM" echo "Pattern: read -p 'Select option' without validation (read followed by case without range check)" echo "Impact: Scripts crash or behave unpredictably with invalid user input" echo "" count=0 # Find scripts with read statements for menu input that lack validation while IFS=: read -r file line_num line_content; do # Check if this read is followed by a case statement without validation # Look for: read -p ".*option.*" choice (or similar) without preceding [[ validation ]] # Get next 5 lines after the read to check for validation next_lines=$(sed -n "${line_num},$((line_num+5))p" "$file" 2>/dev/null) # Check for validation patterns if echo "$next_lines" | grep -q '\[\[.*choice.*=~'; then continue # Has validation fi if echo "$next_lines" | grep -q 'choice=.*:-'; then # Only has default, not validation if echo "$line_content" | grep -iE 'read.*-p.*option|read.*-p.*choice|read.*-p.*select' > /dev/null; then if ! is_suppressed "$file" "$line_num" "menu-validation"; then echo "MEDIUM|$file|$line_num|[MENU-VALIDATION] Menu input lacks validation - no range check after read" count_issue "MEDIUM" ((count++)) [ "$count" -ge 10 ] && break fi fi fi done < <(grep -rn 'read -p' "$TOOLKIT_PATH/modules" --include="*.sh" 2>/dev/null | grep -iE 'option|choice|select|menu') echo "Found: $count menu input validation issues" echo "" } >> "$REPORT" #============================================================================== # CHECK 105: Menu Color Code Consistency (LOW - Menu uniformity) #============================================================================== show_progress 105 "Menu color code consistency" { echo "## CHECK 105: Inconsistent Menu Color Codes" echo "Severity: LOW" echo "Pattern: Menu options without color codes or using inconsistent colors" echo "Impact: Visual inconsistency, poor user experience" echo "" count=0 # Find scripts with echo statements for menu options that lack color codes while IFS=: read -r file line_num line_content; do # Check for plain echo " 1) Option" without ${CYAN}1)${NC} format if echo "$line_content" | grep -qE 'echo.*"[[:space:]]+[0-9]+\)' && \ ! echo "$line_content" | grep -q '\$\{CYAN\}\|\$\{GREEN\}\|\$\{YELLOW\}\|\$\{RED\}'; then if ! is_suppressed "$file" "$line_num" "menu-colors"; then echo "LOW|$file|$line_num|[MENU-COLORS] Menu option lacks color codes" count_issue "LOW" ((count++)) [ "$count" -ge 8 ] && break fi fi done < <(grep -rn 'echo.*".*[0-9])' "$TOOLKIT_PATH/modules" --include="*.sh" 2>/dev/null | head -100) echo "Found: $count menu color inconsistencies" echo "" } >> "$REPORT" #============================================================================== # CHECK 106: Menu Retry Loop Patterns (LOW - Menu uniformity) #============================================================================== show_progress 106 "Menu retry loop implementation" { echo "## CHECK 106: Missing Retry Loops on Menu Validation" echo "Severity: LOW" echo "Pattern: Input validation without while loop for retry" echo "Impact: Poor user experience - users must restart script on invalid input" echo "" count=0 # Find scripts with menu validation that lack proper retry loops while IFS=: read -r file; do # Count read statements without surrounding while true loop total_reads=$(grep -c 'read -p.*option\|read -p.*choice' "$file" 2>/dev/null || echo 0) while_loops=$(grep -c 'while true' "$file" 2>/dev/null || echo 0) # If file has reads for menu input but few while loops, it likely lacks retry logic if [ "$total_reads" -gt 2 ] && [ "$while_loops" -lt 1 ]; then if ! is_suppressed "$file" "0" "menu-retry"; then first_line=$(grep -n 'read -p.*option\|read -p.*choice' "$file" 2>/dev/null | head -1 | cut -d: -f1) echo "LOW|$file|$first_line|[MENU-RETRY] Menu input handling may lack proper retry loops" count_issue "LOW" ((count++)) [ "$count" -ge 5 ] && break fi fi done < <(find "$TOOLKIT_PATH/modules" -name "*.sh" -type f 2>/dev/null) echo "Found: $count files with potential retry loop issues" echo "" } >> "$REPORT" #============================================================================== # CHECK 107: Standardized Yes/No Prompts (LOW - Menu uniformity) #============================================================================== show_progress 107 "Standardized yes/no prompt usage" { echo "## CHECK 107: Non-standardized Yes/No Prompts" echo "Severity: LOW" echo "Pattern: Manual yes/no prompts instead of confirm() function" echo "Impact: Inconsistent UX - users see different prompt styles" echo "" count=0 # Find scripts with non-standardized yes/no prompts while IFS=: read -r file line_num line_content; do # Look for: read -p "... (yes/no):" pattern if echo "$line_content" | grep -qiE 'read.*\(yes/no\)|\(y/n\)|[Yy]/[Nn]' && \ ! echo "$line_content" | grep -q 'confirm'; then # Check if this file uses confirm() elsewhere if grep -q 'confirm.*"' "$file" 2>/dev/null; then if ! is_suppressed "$file" "$line_num" "prompt-style"; then echo "LOW|$file|$line_num|[PROMPT-STYLE] Manual yes/no prompt - should use confirm() function" count_issue "LOW" ((count++)) [ "$count" -ge 5 ] && break fi fi fi done < <(grep -rn 'read -p.*yes\|read -p.*\(y' "$TOOLKIT_PATH/modules" --include="*.sh" 2>/dev/null | head -50) echo "Found: $count non-standardized yes/no prompts" echo "" } >> "$REPORT" #============================================================================== # PERFORMANCE CHECKS (INFO level - not counted as issues) #============================================================================== echo "" echo "═══════════════════════════════════════════════════════════════" echo "PERFORMANCE & OPTIMIZATION CHECKS (Informational)" echo "═══════════════════════════════════════════════════════════════" echo "" { echo "" echo "═══════════════════════════════════════════════════════════════" echo "PERFORMANCE ANALYSIS" echo "═══════════════════════════════════════════════════════════════" echo "" } >> "$REPORT" # PERF 1: cat file | grep (inefficient) echo "[PERF-1] Inefficient cat | grep patterns..." { echo "## PERF-1: Inefficient pipe usage" echo "Severity: INFO" echo "Pattern: cat file | grep (should be: grep '' file)" echo "" count=0 while IFS=: read -r file line_num line_content; do echo "INFO|$file|$line_num|cat | grep pattern" ((count++)) [ "$count" -ge 5 ] && break done < <(grep -rn 'cat.*|.*grep' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) total=$(grep -r 'cat.*|.*grep' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | wc -l) echo "Found: $total instances (showing first 5)" echo "Optimization: Replace 'cat file | grep pattern' with 'grep pattern file'" echo "" } >> "$REPORT" # PERF 2: Repeated file reads echo "[PERF-2] Multiple zcat on same file..." { echo "## PERF-2: Repeated decompression" echo "Severity: INFO" echo "Pattern: Multiple zcat/gunzip calls (expensive)" echo "" zcat_files=$(grep -rh 'zcat [^|]*' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | \ grep -oE 'zcat [^ ]+' | sort | uniq -c | sort -rn | head -3) if [ -n "$zcat_files" ]; then echo "$zcat_files" echo "" echo "Optimization: Decompress once, store in variable or temp file" else echo "None found" fi echo "" } >> "$REPORT" # PERF 3: Subshells in loops (expensive) echo "[PERF-3] Expensive patterns in loops..." { echo "## PERF-3: Subshells in loops" echo "Severity: INFO" echo "Pattern: \$(...) or \`...\` inside while/for loops" echo "" # Find loops with subshells (sample only) count=0 while read -r file; do # Get line numbers of loops grep -n 'while.*do\|for.*do' "$file" 2>/dev/null | while IFS=: read -r loop_line rest; do # Check next 10 lines for subshells start=$loop_line end=$((loop_line + 10)) if sed -n "${start},${end}p" "$file" 2>/dev/null | grep -q '\$(\|`'; then echo "INFO|$file|$loop_line|Subshell in loop (potential slowdown)" ((count++)) [ "$count" -ge 5 ] && break 2 fi done done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null | head -20) echo "Note: Review loops for expensive nested operations" echo "" } >> "$REPORT" # PERF 4: Inefficient string operations echo "[PERF-4] String operations that could use bash builtins..." { echo "## PERF-4: Inefficient string operations" echo "Severity: INFO" echo "Pattern: sed/awk/cut when bash builtins work" echo "" # Sample sed replacements count=0 while IFS=: read -r file line_num line_content; do if echo "$line_content" | grep -q "sed 's/"; then echo "INFO|$file|$line_num|sed replacement (consider \${var//pattern/replacement})" ((count++)) [ "$count" -ge 5 ] && break fi done < <(grep -rn "sed 's/" "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) total_sed=$(grep -r "sed 's/" "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | wc -l) echo "Found: $total_sed sed substitutions" echo "Note: Many are justified, but consider bash builtins for simple cases" echo "" } >> "$REPORT" # PERF 5: Multiple grep on same large file echo "[PERF-5] File read optimization opportunities..." { echo "## PERF-5: Repeated file access" echo "Severity: INFO" echo "Pattern: Same file accessed multiple times" echo "" # Check for common heavy files echo "Common files that might benefit from caching:" for common_file in "/etc/csf/csf.conf" "/var/log/messages" "/etc/passwd"; do count=$(grep -r "$common_file" "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | wc -l) if [ "$count" -gt 5 ]; then echo " $common_file: $count references (consider caching)" fi done echo "" echo "Optimization: Cache frequently-read files in variables" echo "" } >> "$REPORT" #============================================================================== # SUMMARY #============================================================================== # Clear progress line if [ -t 1 ]; then printf "\r%80s\r" " " fi read crit high med low < "$TEMP_COUNTS" total=$((crit + high + med + low)) END_TIME=$(date +%s) DURATION=$((END_TIME - START_TIME)) # Write plain summary to file { echo "═══════════════════════════════════════════════════════════════" echo "SUMMARY" echo "═══════════════════════════════════════════════════════════════" echo "Total Issues: $total" echo " CRITICAL: $crit" echo " HIGH: $high" echo " MEDIUM: $med" echo " LOW: $low" echo "" echo "Files Scanned: $(find "$TOOLKIT_PATH" -name "*.sh" 2>/dev/null | wc -l)" echo "Scan Duration: ${DURATION}s" echo "Report: $REPORT" echo "═══════════════════════════════════════════════════════════════" } >> "$REPORT" # Display colored summary to terminal echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}" if [ "$crit" -gt 0 ]; then echo -e "${RED}${BOLD} ✗ QA SCAN FAILED${NC}" echo -e "${RED}${BOLD} $crit CRITICAL ISSUES FOUND${NC}" elif [ "$high" -gt 0 ]; then echo -e "${YELLOW}${BOLD} ⚠ QA SCAN: WARNINGS${NC}" echo -e "${YELLOW}${BOLD} $high HIGH ISSUES FOUND${NC}" elif [ "$total" -gt 0 ]; then echo -e "${BLUE}${BOLD} ✓ QA SCAN: PASSED${NC}" echo -e "${BLUE}${BOLD} $total minor issues found${NC}" else echo -e "${GREEN}${BOLD} ✓ QA SCAN: PERFECT${NC}" echo -e "${GREEN}${BOLD} NO ISSUES FOUND${NC}" fi echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}" echo "" # Machine-readable summary for AI parsing echo -e "${BOLD}SCAN_STATUS=${NC}$( [ "$crit" -gt 0 ] && echo "FAILED" || [ "$high" -gt 0 ] && echo "WARNING" || [ "$total" -gt 0 ] && echo "PASSED" || echo "PERFECT" ) ${BOLD}CRITICAL=${NC}$crit ${BOLD}HIGH=${NC}$high ${BOLD}MEDIUM=${NC}$med ${BOLD}LOW=${NC}$low ${BOLD}TOTAL=${NC}$total" echo -e "${DIM}FILES=$(find "$TOOLKIT_PATH" -name "*.sh" 2>/dev/null | wc -l) DURATION=${DURATION}s REPORT=$REPORT${NC}" echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}" # Summary mode: just show counts and exit if $SUMMARY_MODE; then echo "" echo "CATEGORY BREAKDOWN:" echo "" for tag in SQL-INJ CMD-INJ FILE-OP SECRET-LEAK RACE PANEL-CALL SOURCE PIPE RETURN NULL TEMP SUBSHELL DEP WORDSPLIT ARITH TEST REDIR TRAP ARRAY HEREDOC IF-MASK NUMCMP BG-JOB LOCALE PROC-SUB PRINTF REGEX BASHISM ESCAPE SLEEP-RACE IFS SUBSHELL-VAR TRAP-RACE PERF-LOOP PERF-CACHE PERF-READ RECURSION FD-LEAK ZOMBIE DISK-SPACE NET-TIMEOUT LOG-ROTATE CPU-LOOP; do count=$(grep -c "\[$tag\]" "$REPORT" 2>/dev/null || echo 0) count=$(echo "$count" | head -1 | tr -d '\n') # Sanitize to remove newlines if [ "$count" -gt 0 ] 2>/dev/null; then printf " %-12s: %d\n" "$tag" "$count" fi done echo "" echo "Run without --summary to see detailed breakdown" rm -f "$TEMP_COUNTS" exit $total fi echo "" # RECOMMENDED ACTIONS: Tell AI/user exactly what to do if [ "$crit" -gt 0 ] || [ "$high" -gt 0 ]; then echo -e "${BOLD}RECOMMENDED ACTIONS:${NC}" # Get top 3 files with most HIGH/CRITICAL issues action_num=1 { [ "$crit" -gt 0 ] && grep "^CRITICAL|" "$REPORT" | cut -d'|' -f2 [ "$high" -gt 0 ] && grep "^HIGH|" "$REPORT" | cut -d'|' -f2 } | sed "s|$TOOLKIT_PATH/||" | sort | uniq -c | sort -rn | head -3 | while read count file; do # Determine most common issue type in this file dominant_issue=$( { grep "^CRITICAL|.*${file}" "$REPORT" 2>/dev/null grep "^HIGH|.*${file}" "$REPORT" 2>/dev/null } | sed 's/.*\[\([^]]*\)\].*/\1/' | sort | uniq -c | sort -rn | head -1 | awk '{print $2}' ) issue_desc=$( case "$dominant_issue" in SOURCE) echo "add source guards" ;; NULL) echo "add null checks" ;; HARDCODED-PATH) echo "fix hardcoded paths" ;; WORDSPLIT) echo "quote variables" ;; DEP) echo "add dependency checks" ;; IFS) echo "reset IFS after modification" ;; *) echo "fix $dominant_issue issues" ;; esac ) printf " ${CYAN}[%d]${NC} Fix %s - %d issues ${DIM}(%s)${NC}\n" "$action_num" "$file" "$count" "$issue_desc" action_num=$((action_num + 1)) done # Add quick wins as additional actions for tag in SOURCE TEMP HARDCODED-PATH WORDSPLIT NULL; do count=$(grep -c "\[$tag\]" "$REPORT" 2>/dev/null | head -1 | tr -d '\n\r' | grep -o '^[0-9]*$' || echo 0) if [ "$count" -ge 10 ] 2>/dev/null && [ "$action_num" -le 5 ]; then desc=$( case "$tag" in SOURCE) echo "Add source existence checks across codebase" ;; TEMP) echo "Replace /tmp hardcoding with mktemp" ;; HARDCODED-PATH) echo "Replace hardcoded paths with detection" ;; WORDSPLIT) echo "Quote all variable expansions" ;; NULL) echo "Add null/empty checks before variable use" ;; esac ) affected_files=$(grep "\[$tag\]" "$REPORT" | cut -d'|' -f2 | sed "s|$TOOLKIT_PATH/||" | sort -u | wc -l) printf " ${CYAN}[%d]${NC} %s ${DIM}(%d issues in %d files)${NC}\n" "$action_num" "$desc" "$count" "$affected_files" action_num=$((action_num + 1)) fi done echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}" echo "" fi # PRIORITY FILES: Show which files have CRITICAL/HIGH issues if [ "$crit" -gt 0 ] || [ "$high" -gt 0 ]; then echo -e "${BOLD}PRIORITY FILES (CRITICAL/HIGH issues):${NC}" { [ "$crit" -gt 0 ] && grep "^CRITICAL|" "$REPORT" | cut -d'|' -f2 [ "$high" -gt 0 ] && grep "^HIGH|" "$REPORT" | cut -d'|' -f2 } | sed "s|$TOOLKIT_PATH/||" | sort | uniq -c | sort -rn | head -10 | while read count file; do # Get breakdown by severity for this file crit_in_file=$(grep "^CRITICAL|.*${file}" "$REPORT" 2>/dev/null | wc -l) high_in_file=$(grep "^HIGH|.*${file}" "$REPORT" 2>/dev/null | wc -l) if [ "$crit_in_file" -gt 0 ]; then printf " ${RED}●${NC} %s ${RED}(CRITICAL: %d, HIGH: %d)${NC}\n" "$file" "$crit_in_file" "$high_in_file" else printf " ${YELLOW}●${NC} %s ${YELLOW}(HIGH: %d)${NC}\n" "$file" "$high_in_file" fi done # Calculate coverage total_priority=$((crit + high)) top3_count=$( { [ "$crit" -gt 0 ] && grep "^CRITICAL|" "$REPORT" | cut -d'|' -f2 [ "$high" -gt 0 ] && grep "^HIGH|" "$REPORT" | cut -d'|' -f2 } | sed "s|$TOOLKIT_PATH/||" | sort | uniq -c | sort -rn | head -3 | awk '{sum+=$1} END {print sum}' ) if [ "$total_priority" -gt 0 ] && [ -n "$top3_count" ] && [ "$top3_count" -gt 0 ]; then coverage=$((top3_count * 100 / total_priority)) echo -e " ${DIM}→ Fix top 3 files = ${coverage}% of CRITICAL/HIGH issues${NC}" fi echo "" fi # CRITICAL issues grouped by file if [ "$crit" -gt 0 ]; then echo -e "${RED}${BOLD}CRITICAL ISSUES ($crit) - MUST FIX IMMEDIATELY:${NC}" grep "^CRITICAL|" "$REPORT" | while IFS='|' read -r sev file line issue; do rel_file="${file#$TOOLKIT_PATH/}" printf " ${RED}●${NC} %s:%s - %s\n" "$rel_file" "$line" "$issue" done echo "" fi # HIGH issues: Compact summary by file with dominant pattern if [ "$high" -gt 0 ]; then echo -e "${YELLOW}${BOLD}HIGH ISSUES BY FILE (top 15):${NC}" # Get unique files with HIGH issues, sorted by count grep "^HIGH|" "$REPORT" | cut -d'|' -f2 | sed "s|$TOOLKIT_PATH/||" | sort | uniq -c | sort -rn | head -15 | while read count rel_file; do # Get dominant issue type in this file dominant=$(grep "^HIGH|.*${rel_file}" "$REPORT" | sed 's/.*\[\([^]]*\)\].*/\1/' | sort | uniq -c | sort -rn | head -1) dominant_count=$(echo "$dominant" | awk '{print $1}') dominant_type=$(echo "$dominant" | awk '{print $2}') # Get first line number as reference first_line=$(grep "^HIGH|.*${rel_file}" "$REPORT" | head -1 | cut -d'|' -f3) printf " ${YELLOW}●${NC} %s ${DIM}(%d issues: %d× %s, starting at line %s)${NC}\n" \ "$rel_file" "$count" "$dominant_count" "$dominant_type" "$first_line" done total_high_files=$(grep "^HIGH|" "$REPORT" | cut -d'|' -f2 | sed "s|$TOOLKIT_PATH/||" | sort -u | wc -l) if [ "$total_high_files" -gt 15 ]; then echo -e " ${DIM}... +$((total_high_files - 15)) more files (run: grep '^HIGH' $REPORT | cut -d'|' -f2 | sort -u)${NC}" fi echo -e " ${DIM}→ View details: grep '^HIGH' $REPORT${NC}" echo "" fi # PATTERN SUMMARY: Compact view of all significant patterns if [ "$total" -gt 50 ]; then echo -e "${BOLD}PATTERN SUMMARY (10+ occurrences):${NC}" quick_wins_found=0 for tag in SOURCE TEMP HARDCODED-PATH WORDSPLIT NULL PIPE SUBSHELL REDIR RACE DEP; do count=$(grep -c "\[$tag\]" "$REPORT" 2>/dev/null || echo 0) count=$(echo "$count" | head -1 | tr -d '\n\r' | grep -o '^[0-9]*$' || echo 0) if [ "$count" -ge 10 ] 2>/dev/null; then quick_wins_found=1 printf " %-18s %3d occurrences\n" "$tag" "$count" fi done [ "$quick_wins_found" -eq 0 ] && echo -e " ${DIM}No high-frequency patterns${NC}" echo "" fi # MEDIUM/LOW summary (compact) if [ "$med" -gt 0 ] || [ "$low" -gt 0 ]; then echo -e "${DIM}MEDIUM: $med issues | LOW: $low issues | Full details: grep '^MEDIUM\\|^LOW' $REPORT${NC}" echo "" fi # Cleanup rm -f "$TEMP_COUNTS" # Concise action summary echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}" if [ "$crit" -gt 0 ]; then echo -e "${RED}ACTION REQUIRED:${NC} Fix $crit CRITICAL issues immediately" echo -e "${DIM}View: grep '^CRITICAL' $REPORT${NC}" elif [ "$high" -gt 0 ]; then echo -e "${YELLOW}RECOMMENDED:${NC} Review $high HIGH priority issues soon" echo -e "${DIM}View: grep '^HIGH' $REPORT${NC}" elif [ "$total" -gt 0 ]; then echo -e "${BLUE}OPTIONAL:${NC} Review $total minor issues when convenient" echo -e "${DIM}View: less $REPORT${NC}" else echo -e "${GREEN}ALL CLEAR:${NC} No issues found!" fi echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}" echo -e "${DIM}Completed in ${DURATION}s | Full report: $REPORT | Exit code: $total${NC}" echo "" exit $total