#!/bin/bash # # Server Toolkit QA Checker - Optimized for AI Assistant Development # Designed to help Claude track bugs during development with minimal computation # # Features: # - Structured output (easy to parse) # - Fixed subshell counter bug # - Groups by severity # - Concise summaries # - Quick file:line references # TOOLKIT_PATH="${1:-/root/server-toolkit}" if [ ! -d "$TOOLKIT_PATH" ]; then echo "ERROR: $TOOLKIT_PATH not found" exit 1 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" # 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" } echo "═══════════════════════════════════════════════════════════════" echo "SERVER TOOLKIT QA SCAN" echo "Path: $TOOLKIT_PATH" 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) #============================================================================== echo "[1/30] Checking: 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 collisions (HIGH - causes path errors) #============================================================================== echo "[2/30] Checking: SCRIPT_DIR variable collisions..." { echo "## CHECK 2: SCRIPT_DIR variable collisions" echo "Severity: HIGH" echo "Issue: Multiple files redefining same path variable" echo "" script_dir_count=$(grep -l "^SCRIPT_DIR=" "$TOOLKIT_PATH"/**/*.sh 2>/dev/null | wc -l) if [ "$script_dir_count" -gt 1 ]; then files=$(grep -l "^SCRIPT_DIR=" "$TOOLKIT_PATH"/**/*.sh 2>/dev/null | tr '\n' ' ') echo "HIGH|Multiple files|N/A|SCRIPT_DIR in $script_dir_count files: $files" count_issue "HIGH" fi echo "" } >> "$REPORT" #============================================================================== # CHECK 3: SYS_* variable resets (CRITICAL - breaks system detection) #============================================================================== echo "[3/30] 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/30] 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/30] 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 "" 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) echo "HIGH|$file|$line_num|Integer comparison: $var" 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/30] 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 < <(grep -l 'cecho\|print_info\|print_warning\|print_error' "$TOOLKIT_PATH"/modules/**/*.sh 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 7: exit in libraries (HIGH - terminates parent script) #============================================================================== echo "[7/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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 # Skip if it has proper validation ([ -n "$var" ] && rm ...) if ! echo "$line_content" | grep -q '\[\s*-[nz]'; then echo "CRITICAL|$file|$line_num|Dangerous rm -rf with unvalidated variable" count_issue "CRITICAL" 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/30] 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/30] 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/30] 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/30] 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/30] 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 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 < <(grep -rh '^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*()' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | \ sed 's/^\s*//; s/(.*$//' | sort | uniq -d | \ while read func; do grep -rl "^[[:space:]]*$func()" "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | sed "s|$|:$func|"; done) echo "" } >> "$REPORT" #============================================================================== # CHECK 18: Missing input validation (HIGH - security/reliability risk) #============================================================================== echo "[18/30] Checking: Missing input validation..." { echo "## CHECK 18: Functions without parameter validation" echo "Severity: HIGH" echo "Issue: Functions accepting parameters without validation" echo "" count=0 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) # Get function body and filter out awk/sed commands before checking for $1-9 func_body=$(grep -A 20 "^[[:space:]]*$func_name()" "$file" 2>/dev/null | grep -v 'awk\|sed' || true) 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) # If function only uses params in echo/print statements, it's safe 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) echo "Found: $count issues (showing first 10)" echo "" } >> "$REPORT" #============================================================================== # CHECK 19: Long functions (MEDIUM - maintainability issue) #============================================================================== echo "[19/30] 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 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 echo "" } >> "$REPORT" #============================================================================== # CHECK 20: ShellCheck integration (if available) #============================================================================== echo "[20/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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" #============================================================================== # 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 #============================================================================== read crit high med low < "$TEMP_COUNTS" total=$((crit + high + med + low)) { 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 "Report: $REPORT" echo "═══════════════════════════════════════════════════════════════" } | tee -a "$REPORT" echo "" echo "DETAILED BREAKDOWN BY SEVERITY:" echo "" # Group and display by severity echo "CRITICAL ISSUES ($crit):" grep "^CRITICAL|" "$REPORT" | while IFS='|' read -r sev file line issue; do printf " %s:%s - %s\n" "$file" "$line" "$issue" done echo "" echo "HIGH ISSUES ($high):" grep "^HIGH|" "$REPORT" | head -15 | while IFS='|' read -r sev file line issue; do printf " %s:%s - %s\n" "$file" "$line" "$issue" done if [ "$high" -gt 15 ]; then echo " ... and $((high - 15)) more (see $REPORT)" fi echo "" echo "MEDIUM ISSUES ($med):" grep "^MEDIUM|" "$REPORT" | while IFS='|' read -r sev file line issue; do printf " %s:%s - %s\n" "$file" "$line" "$issue" done echo "" echo "LOW ISSUES ($low):" grep "^LOW|" "$REPORT" | while IFS='|' read -r sev file line issue; do printf " %s:%s - %s\n" "$file" "$line" "$issue" done # Cleanup rm -f "$TEMP_COUNTS" echo "" echo "Full report saved to: $REPORT" echo "Exit code: $total" exit $total