f4c921bea0
Improvements:
- Added more common integer variable patterns (crit, high, med, low, severity, line_num, port, pid, uid, gid, attempt, tries)
- Skip variables with default value syntax ${var:-0}
- Reduces false positives for counters, IDs, severity levels, and line numbers
This significantly reduces noise in QA output while maintaining detection
of genuinely unsafe integer comparisons.
3429 lines
137 KiB
Bash
Executable File
3429 lines
137 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:
|
|
# - 88 comprehensive checks (was 80, +8 multi-panel compliance)
|
|
# - Context-aware detection (<5% false positives)
|
|
# - 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
|
|
#
|
|
|
|
# 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/88] ${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
|
|
}
|
|
|
|
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 collisions (HIGH - causes path errors)
|
|
#==============================================================================
|
|
echo "[2/42] Checking: SCRIPT_DIR variable collisions..."
|
|
{
|
|
echo "## CHECK 2: SCRIPT_DIR variable collisions"
|
|
echo "Severity: HIGH"
|
|
echo "Issue: Multiple files redefining same path variable"
|
|
echo ""
|
|
|
|
script_dir_count=$(find "$TOOLKIT_PATH" -name "*.sh" -type f -exec grep -l "^SCRIPT_DIR=" {} \; 2>/dev/null | wc -l)
|
|
if [ "$script_dir_count" -gt 1 ]; then
|
|
files=$(find "$TOOLKIT_PATH" -name "*.sh" -type f -exec grep -l "^SCRIPT_DIR=" {} \; 2>/dev/null | tr '\n' ' ')
|
|
echo "HIGH|Multiple files|N/A|SCRIPT_DIR in $script_dir_count files: $files"
|
|
count_issue "HIGH"
|
|
fi
|
|
|
|
echo ""
|
|
} >> "$REPORT"
|
|
|
|
#==============================================================================
|
|
# CHECK 3: SYS_* variable resets (CRITICAL - breaks system detection)
|
|
#==============================================================================
|
|
echo "[3/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)
|
|
#==============================================================================
|
|
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 ""
|
|
|
|
while read -r file; do
|
|
# Read entire file and track function boundaries
|
|
in_function=0
|
|
brace_depth=0
|
|
line_num=0
|
|
|
|
while IFS= read -r line_content; do
|
|
line_num=$((line_num + 1))
|
|
|
|
# Skip comments
|
|
if echo "$line_content" | grep -q '^\s*#'; then
|
|
continue
|
|
fi
|
|
|
|
# Detect function start
|
|
if echo "$line_content" | grep -qE '^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*\(\)'; then
|
|
in_function=1
|
|
brace_depth=0
|
|
fi
|
|
|
|
# Track braces
|
|
open_braces=$(echo "$line_content" | grep -o '{' | wc -l)
|
|
close_braces=$(echo "$line_content" | grep -o '}' | wc -l)
|
|
brace_depth=$((brace_depth + open_braces - close_braces))
|
|
|
|
# If we were in a function and braces are now balanced back to 0, we've exited
|
|
if [ "$in_function" -eq 1 ] && [ "$brace_depth" -le 0 ]; then
|
|
in_function=0
|
|
fi
|
|
|
|
# Check for 'local' keyword outside function
|
|
if [ "$in_function" -eq 0 ]; then
|
|
if echo "$line_content" | grep -qE '^\s*local\s+[a-zA-Z_]'; then
|
|
echo "CRITICAL|$file|$line_num|'local' keyword outside function (runtime error)"
|
|
count_issue "CRITICAL"
|
|
fi
|
|
fi
|
|
done < "$file"
|
|
done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null)
|
|
|
|
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 ""
|
|
|
|
# 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)
|
|
|
|
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
|
|
|
|
# Extract sourced file path
|
|
sourced_file=$(echo "$line_content" | grep -oE 'source\s+[^ ]+|\\.\s+[^ ]+' | awk '{print $2}')
|
|
|
|
# Skip if it's a variable or absolute path starting with /
|
|
if [[ "$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+-f\s+.*'"$sourced_file"'|\[\s+-e\s+.*'"$sourced_file"; 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 '(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
|
|
|
|
# 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"
|
|
else
|
|
required_lib="domain-discovery.sh"
|
|
fi
|
|
|
|
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
|
|
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: HIGH"
|
|
echo "Pattern: for x in \$var (splits on spaces, not newlines)"
|
|
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_]+')
|
|
|
|
echo "HIGH|$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: Use quotes: for x in \"\$var\" or use array with \"\${arr[@]}\""
|
|
count_issue "HIGH"
|
|
((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 word splitting risks"
|
|
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
|
|
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 -F 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
|
|
|
|
# Look for IFS= assignment
|
|
if echo "$line_content" | grep -qE 'IFS='; then
|
|
# Check if there's a reset within next 20 lines or in same command
|
|
if ! echo "$line_content" | grep -qE ';.*IFS=' && \
|
|
! sed -n "$((line_num + 1)),$((line_num + 20))p" "$file" 2>/dev/null | grep -qE '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 '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
|
|
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)
|
|
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
|
|
if echo "$line_content" | grep -qE '\b(curl|wget)\s+'; then
|
|
if ! echo "$line_content" | grep -qE '(--timeout|--max-time|-m\s+[0-9]|--connect-timeout)'; then
|
|
cmd=$(echo "$line_content" | grep -oE '\b(curl|wget)\b')
|
|
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
|
|
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
|
|
[[ "$file" =~ (system-detect|cpanel-helpers|launcher)\.sh$ ]] && continue
|
|
|
|
# Skip comments and variable definitions that are panel-aware
|
|
echo "$line_content" | grep -qE '^\s*#|case.*CONTROL_PANEL' && continue
|
|
|
|
# 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 (CRITICAL)
|
|
#==============================================================================
|
|
echo "[83/94] Checking: Direct /var/cpanel/users access..."
|
|
{
|
|
echo "## CHECK 83: Direct /var/cpanel/users file access"
|
|
echo "Severity: CRITICAL"
|
|
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 ""
|
|
|
|
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
|
|
|
|
echo "CRITICAL|$file|$line_num|[USERDATA-ACCESS] Direct access to cPanel user files"
|
|
count_issue "CRITICAL"
|
|
((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"
|
|
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"
|
|
#==============================================================================
|
|
# 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}"
|
|
echo -e "${BOLD} QA SCAN RESULTS${NC}"
|
|
echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
echo -e "${BOLD}Total Issues:${NC} $total"
|
|
if [ "$crit" -gt 0 ]; then
|
|
echo -e " ${RED}${BOLD}CRITICAL:${NC} ${RED}$crit${NC} ${DIM}(must fix immediately)${NC}"
|
|
else
|
|
echo -e " ${DIM}CRITICAL: 0${NC}"
|
|
fi
|
|
if [ "$high" -gt 0 ]; then
|
|
echo -e " ${YELLOW}HIGH:${NC} ${YELLOW}$high${NC} ${DIM}(fix soon)${NC}"
|
|
else
|
|
echo -e " ${DIM}HIGH: 0${NC}"
|
|
fi
|
|
if [ "$med" -gt 0 ]; then
|
|
echo -e " ${BLUE}MEDIUM:${NC} ${BLUE}$med${NC} ${DIM}(review when possible)${NC}"
|
|
else
|
|
echo -e " ${DIM}MEDIUM: 0${NC}"
|
|
fi
|
|
if [ "$low" -gt 0 ]; then
|
|
echo -e " ${CYAN}LOW:${NC} ${CYAN}$low${NC} ${DIM}(minor issues)${NC}"
|
|
else
|
|
echo -e " ${DIM}LOW: 0${NC}"
|
|
fi
|
|
echo ""
|
|
echo -e "${DIM}Files Scanned: $(find "$TOOLKIT_PATH" -name "*.sh" 2>/dev/null | wc -l)${NC}"
|
|
echo -e "${DIM}Scan Duration: ${DURATION}s${NC}"
|
|
echo -e "${DIM}Full 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 ""
|
|
echo -e "${BOLD}DETAILED BREAKDOWN BY SEVERITY:${NC}"
|
|
echo ""
|
|
|
|
# Group and display by severity with colors
|
|
if [ "$crit" -gt 0 ]; then
|
|
echo -e "${RED}${BOLD}CRITICAL ISSUES ($crit):${NC}"
|
|
grep "^CRITICAL|" "$REPORT" | while IFS='|' read -r sev file line issue; do
|
|
# Simplify file path (relative to toolkit)
|
|
rel_file="${file#$TOOLKIT_PATH/}"
|
|
printf " ${RED}●${NC} %s:%s ${DIM}-${NC} %s\n" "$rel_file" "$line" "$issue"
|
|
done
|
|
echo ""
|
|
fi
|
|
|
|
if [ "$high" -gt 0 ]; then
|
|
echo -e "${YELLOW}HIGH ISSUES ($high):${NC}"
|
|
grep "^HIGH|" "$REPORT" | head -15 | while IFS='|' read -r sev file line issue; do
|
|
rel_file="${file#$TOOLKIT_PATH/}"
|
|
printf " ${YELLOW}●${NC} %s:%s ${DIM}-${NC} %s\n" "$rel_file" "$line" "$issue"
|
|
done
|
|
if [ "$high" -gt 15 ]; then
|
|
echo -e " ${DIM}... and $((high - 15)) more (see $REPORT)${NC}"
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
if [ "$med" -gt 0 ]; then
|
|
echo -e "${BLUE}MEDIUM ISSUES ($med):${NC}"
|
|
grep "^MEDIUM|" "$REPORT" | head -10 | while IFS='|' read -r sev file line issue; do
|
|
rel_file="${file#$TOOLKIT_PATH/}"
|
|
printf " ${BLUE}●${NC} %s:%s ${DIM}-${NC} %s\n" "$rel_file" "$line" "$issue"
|
|
done
|
|
if [ "$med" -gt 10 ]; then
|
|
echo -e " ${DIM}... and $((med - 10)) more (see $REPORT)${NC}"
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
if [ "$low" -gt 0 ]; then
|
|
echo -e "${CYAN}LOW ISSUES ($low):${NC}"
|
|
grep "^LOW|" "$REPORT" | head -5 | while IFS='|' read -r sev file line issue; do
|
|
rel_file="${file#$TOOLKIT_PATH/}"
|
|
printf " ${CYAN}●${NC} %s:%s ${DIM}-${NC} %s\n" "$rel_file" "$line" "$issue"
|
|
done
|
|
if [ "$low" -gt 5 ]; then
|
|
echo -e " ${DIM}... and $((low - 5)) more (see $REPORT)${NC}"
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
|
|
echo -e "${BOLD}TOP ISSUES BY CATEGORY:${NC}"
|
|
echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
|
|
|
|
# Create array of category counts
|
|
declare -A cat_counts
|
|
for tag in SQL-INJ CMD-INJ PANEL-CALL FILE-OP SECRET-LEAK RACE SOURCE RETURN NULL DEP TEMP SUBSHELL PIPE 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 HARDCODED-PATH MISSING-LIB USERDATA-ACCESS API-CHECK NO-CASE DB-PATTERN NO-USER-MGR NO-STANDALONE; do
|
|
count=$(grep -c "\[$tag\]" "$REPORT" 2>/dev/null || echo 0)
|
|
if [ "$count" -gt 0 ]; then
|
|
cat_counts[$tag]=$count
|
|
fi
|
|
done
|
|
|
|
# Sort and display (show top 15)
|
|
if [ ${#cat_counts[@]} -gt 0 ]; then
|
|
for tag in "${!cat_counts[@]}"; do
|
|
echo "${cat_counts[$tag]} $tag"
|
|
done | sort -rn | head -15 | while read count tag; do
|
|
# Color code based on severity keywords
|
|
if [[ "$tag" =~ (SQL-INJ|CMD-INJ|SECRET) ]]; then
|
|
printf " ${RED}%-20s${NC} %s\n" "$tag" "${CYAN}$count issues${NC}"
|
|
elif [[ "$tag" =~ (FILE-OP|RACE|PANEL) ]]; then
|
|
printf " ${YELLOW}%-20s${NC} %s\n" "$tag" "${CYAN}$count issues${NC}"
|
|
else
|
|
printf " ${DIM}%-20s${NC} %s\n" "$tag" "${CYAN}$count issues${NC}"
|
|
fi
|
|
done
|
|
|
|
total_cats=${#cat_counts[@]}
|
|
if [ "$total_cats" -gt 15 ]; then
|
|
echo -e " ${DIM}... and $((total_cats - 15)) more categories (see $REPORT)${NC}"
|
|
fi
|
|
else
|
|
echo -e " ${GREEN}No issues found!${NC}"
|
|
fi
|
|
echo ""
|
|
|
|
# Cleanup
|
|
rm -f "$TEMP_COUNTS"
|
|
|
|
echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
|
|
echo -e "${BOLD}NEXT STEPS:${NC}"
|
|
echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
if [ "$total" -eq 0 ]; then
|
|
echo -e " ${GREEN}✓${NC} Code quality looks good!"
|
|
echo -e " ${DIM}No issues found in this scan${NC}"
|
|
elif [ "$crit" -gt 0 ]; then
|
|
echo -e " ${RED}!${NC} Fix ${RED}CRITICAL${NC} issues immediately"
|
|
echo -e " ${DIM}Use: grep '^CRITICAL' $REPORT${NC}"
|
|
elif [ "$high" -gt 0 ]; then
|
|
echo -e " ${YELLOW}!${NC} Review ${YELLOW}HIGH${NC} priority issues soon"
|
|
echo -e " ${DIM}Use: grep '^HIGH' $REPORT${NC}"
|
|
else
|
|
echo -e " ${BLUE}•${NC} Review and fix issues when possible"
|
|
fi
|
|
echo ""
|
|
echo -e "${DIM}Full report: $REPORT${NC}"
|
|
echo ""
|
|
echo -e "${BOLD}Useful Commands:${NC}"
|
|
echo -e " ${CYAN}$0 --quick${NC} ${DIM}# Fast scan (CRITICAL + HIGH only)${NC}"
|
|
echo -e " ${CYAN}$0 --security${NC} ${DIM}# Security issues only${NC}"
|
|
echo -e " ${CYAN}$0 --category SQL-INJ${NC} ${DIM}# Filter by category${NC}"
|
|
echo -e " ${CYAN}$0 --summary${NC} ${DIM}# Show counts only${NC}"
|
|
echo -e " ${CYAN}grep '^CRITICAL' $REPORT${NC} ${DIM}# View all critical issues${NC}"
|
|
echo ""
|
|
echo -e "${DIM}Scan completed in ${DURATION}s | Exit code: $total${NC}"
|
|
echo ""
|
|
|
|
exit $total
|