fc5dc18031
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>
4263 lines
175 KiB
Bash
Executable File
4263 lines
175 KiB
Bash
Executable File
#!/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
|