Files
Linux-Server-Management-Too…/tools/toolkit-qa-check.sh
T
cschantz fc5dc18031 Add menu uniformity checks to QA script (CHECK 104-107)
NEW CHECKS ADDED:

CHECK 104: Menu Input Validation (MEDIUM)
- Detects: read statements for menu input without validation
- Pattern: read -p 'Select option' without range checks
- Impact: Scripts crash with invalid input
- Fix: Add [[ "$choice" =~ ^[0-9]+$ ]] validation

CHECK 105: Menu Color Code Consistency (LOW)
- Detects: Menu options without color codes
- Pattern: echo "  1) Option" without ${CYAN}1)${NC}
- Impact: Visual inconsistency, poor UX
- Fix: Use ${CYAN}1)${NC} format for consistency

CHECK 106: Menu Retry Loop Implementation (LOW)
- Detects: Input validation without proper retry loops
- Pattern: Validation without 'while true' loop
- Impact: Users must restart script on invalid input
- Fix: Wrap validation in while true; do ... done

CHECK 107: Standardized Yes/No Prompts (LOW)
- Detects: Non-standard yes/no prompts
- Pattern: read -p "... (yes/no):" instead of confirm()
- Impact: Inconsistent UX
- Fix: Use confirm() library function

METRICS UPDATED:
- Total checks: 111 (was 101)
- Progress display: [%2d/107] (was [%2d/88])
- New phase: Phase 11 - Menu uniformity validation

These checks validate the menu standards documented in REFDB_FORMAT.txt
and can be used to audit any script with menu-driven interfaces.

Usage:
  bash toolkit-qa-check.sh /path/to/script
  grep 'MENU-VALIDATION\|MENU-COLORS\|MENU-RETRY\|PROMPT-STYLE' /tmp/qa-report.txt

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-02-17 18:52:10 -05:00

4263 lines
175 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 '\<exit\>' "$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*#|<password>|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 <<EOF without quotes (variables expanded unexpectedly)"
echo "Severity: LOW"
echo "Pattern: <<EOF with \$ variables (should be <<'EOF' if literal)"
echo ""
count=0
while IFS=: read -r file line_num line_content; do
is_suppressed "$file" "$line_num" "heredoc-check" && continue
delimiter=$(echo "$line_content" | grep -oE '<<-?\s*[A-Z_]+' | awk '{print $NF}')
# Check if heredoc body has variables but delimiter isn't quoted
if ! echo "$line_content" | grep -qE "<<-?\s*['\"]"; then
# Read next few lines to see if there are variables
next_lines=$(sed -n "$((line_num + 1)),$((line_num + 5))p" "$file" 2>/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