diff --git a/tools/toolkit-qa-check.sh b/tools/toolkit-qa-check.sh index f8af384..56ce7cc 100755 --- a/tools/toolkit-qa-check.sh +++ b/tools/toolkit-qa-check.sh @@ -14,7 +14,7 @@ # --summary Summary mode (counts only, no details) # # Features: -# - 80 comprehensive checks (was 32) +# - 88 comprehensive checks (was 80, +8 multi-panel compliance) # - Context-aware detection (<5% false positives) # - Smart categorization with tags # - Suppress annotations support (# qa-suppress) @@ -22,6 +22,7 @@ # - 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 @@ -178,9 +179,9 @@ echo "Severity: HIGH" echo "Issue: Multiple files redefining same path variable" echo "" -script_dir_count=$(grep -l "^SCRIPT_DIR=" "$TOOLKIT_PATH"/**/*.sh 2>/dev/null | wc -l) +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=$(grep -l "^SCRIPT_DIR=" "$TOOLKIT_PATH"/**/*.sh 2>/dev/null | tr '\n' ' ') + 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 @@ -301,7 +302,7 @@ while read -r file; do echo "HIGH|$file|N/A|Uses common functions without sourcing" count_issue "HIGH" fi -done < <(grep -l 'cecho\|print_info\|print_warning\|print_error' "$TOOLKIT_PATH"/modules/**/*.sh 2>/dev/null) +done < <(find "$TOOLKIT_PATH/modules" -name "*.sh" -type f -exec grep -l 'cecho\|print_info\|print_warning\|print_error' {} \; 2>/dev/null) echo "" } >> "$REPORT" @@ -574,7 +575,7 @@ echo "Severity: MEDIUM" echo "Issue: Same function in multiple files causes unpredictable behavior" echo "" -# Extract all function names and find duplicates +# 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 @@ -583,9 +584,8 @@ while IFS=: read -r file func_name; do else func_files[$func_name]="$file" fi -done < <(grep -rh '^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*()' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | \ - sed 's/^\s*//; s/(.*$//' | sort | uniq -d | \ - while read func; do grep -rl "^[[:space:]]*$func()" "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | sed "s|$|:$func|"; done) +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" @@ -601,6 +601,10 @@ 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 @@ -683,6 +687,7 @@ while read -r file; do 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 "" @@ -699,6 +704,9 @@ 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 ' @@ -729,6 +737,7 @@ done < <(find "$TOOLKIT_PATH" -name "*.sh" -not -name "toolkit-qa-check.sh" 2>/d ((count++)) [ "$count" -ge 10 ] && break done +fi # End summary mode skip echo "" } >> "$REPORT" @@ -2795,7 +2804,264 @@ done < <(grep -rnE '\b(find\s+/|grep\s+-r|tar\s+|rsync|mysqldump)' "$TOOLKIT_PAT 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) #============================================================================== @@ -3008,7 +3274,7 @@ echo "" echo "═══════════════════════════════════════════════════════════════" echo "CATEGORY BREAKDOWN (Top Issues by Type):" echo "═══════════════════════════════════════════════════════════════" -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; do +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 printf " %-12s: %d issues\n" "$tag" "$count"