diff --git a/scripts/metrics/analyze_plugins.py b/scripts/metrics/analyze_plugins.py new file mode 100644 index 0000000..1651802 --- /dev/null +++ b/scripts/metrics/analyze_plugins.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +"""Analyze WordPress plugins for security vulnerabilities using security ratings.""" +import argparse +import json +import sys +from datetime import datetime, timezone +from pathlib import Path + +# Import security_rating functions +from security_rating import get_plugin_info, calculate_security_score + + +def parse_args() -> argparse.Namespace: + """Parse command-line arguments. + + Returns: + Parsed arguments namespace. + """ + parser = argparse.ArgumentParser( + description="Analyze WordPress plugins for security issues using security ratings." + ) + parser.add_argument( + "--input", + "-i", + required=True, + help="Path to input JSON file (from output_recursive_plugins)" + ) + parser.add_argument( + "--output", + "-o", + help="Path to output JSON file (if not specified, prints to stdout)", + default=None + ) + parser.add_argument( + "--detector-name", + "-d", + help="Name of detector to use in output (default: Security Rating Analyzer)", + default="Security Rating Analyzer" + ) + return parser.parse_args() + + +def analyze_plugin(slug: str) -> dict: + """Analyze a single plugin and return its status. + + Args: + slug: Plugin slug to analyze. + + Returns: + Dict with status and reason fields. + """ + plugin_info = get_plugin_info(slug) + + if not plugin_info: + return { + "status": "unknown", + "reason": f"Could not fetch plugin information from WordPress.org API" + } + + score = calculate_security_score(plugin_info) + + # Determine status based on score + # Safe: score >= 65 (Excellent or Good) + # Vulnerable: score < 65 (Moderate or Poor) + if score >= 85: + status = "safe" + reason = f"Excellent security rating ({score}/100) — Actively maintained and trusted" + elif score >= 65: + status = "safe" + reason = f"Good security rating ({score}/100) — Generally safe but monitor updates" + elif score >= 45: + status = "vulnerable" + reason = f"Moderate security rating ({score}/100) — May be outdated or less popular" + else: + status = "vulnerable" + reason = f"Poor security rating ({score}/100) — High risk of security issues" + + result = { + "status": status, + "reason": reason, + "score": score + } + + # Add version if available + version = plugin_info.get("version") + if version: + result["version"] = version + + return result + + +def analyze_directory(directory_name: str, plugins_dict: dict, detector_name: str) -> dict: + """Analyze all plugins in a directory. + + Args: + directory_name: Name of the directory being analyzed. + plugins_dict: Dict mapping plugin slug to absolute path. + detector_name: Name of the detector. + + Returns: + Analysis result in the specified format. + """ + timestamp = datetime.now(timezone.utc).isoformat() + + analyzed_plugins = {} + + print(f"\nAnalyzing plugins for directory: {directory_name}") + print(f"Found {len(plugins_dict)} plugins to analyze") + + for slug, path in plugins_dict.items(): + print(f" Analyzing {slug}...", end=" ", flush=True) + result = analyze_plugin(slug) + analyzed_plugins[slug] = result + print(f"{result['status']}") + + return { + "directory": directory_name, + "wordpress": True, + "name_of_detector": detector_name, + "timestamp": timestamp, + "plugins": analyzed_plugins + } + + +def main(): + """Main entry point for the plugin analyzer.""" + args = parse_args() + + # Load input JSON + try: + with open(args.input, "r", encoding="utf-8") as f: + input_data = json.load(f) + except FileNotFoundError: + print(f"Error: Input file '{args.input}' not found.", file=sys.stderr) + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error: Invalid JSON in '{args.input}': {e}", file=sys.stderr) + sys.exit(1) + + # Analyze each directory + results = [] + + for directory_name, plugins_dict in input_data.items(): + if not plugins_dict: + print(f"Skipping {directory_name} (no plugins found)") + continue + + analysis = analyze_directory(directory_name, plugins_dict, args.detector_name) + results.append(analysis) + + # Output results + if args.output: + with open(args.output, "w", encoding="utf-8") as f: + json.dump(results, f, ensure_ascii=False, indent=2) + print(f"\nWrote analysis results to {args.output}") + else: + print("\nAnalysis results:") + print(json.dumps(results, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main()