diff --git a/NessusActionPlanner.py b/NessusActionPlanner.py index beb9d5d..cba116c 100644 --- a/NessusActionPlanner.py +++ b/NessusActionPlanner.py @@ -256,6 +256,96 @@ class ActionPlanGenerator: print(f"\n✓ Action plan exported to: {output_file}") print(f" Total action items: {len(self.action_plans)}") + + def export_summary_csv(self, output_file: str): + """Export summary CSV with actions aggregated across all systems""" + if not self.action_plans: + print("No action plans to export. Run generate_plans() first.") + return + + # Aggregate by action + action_summary = defaultdict(lambda: { + 'systems': set(), + 'cve_count': 0, + 'critical_total': 0, + 'high_total': 0 + }) + + for plan in self.action_plans: + action = plan.action + action_summary[action]['systems'].add(plan.systems[0] if plan.systems else 'Unknown') + action_summary[action]['cve_count'] = plan.cve_reduction_count # Same for all systems with this action + action_summary[action]['critical_total'] += plan.critical_reduction + action_summary[action]['high_total'] += plan.high_reduction + + # Calculate totals for percentages + total_cve = len(set(plan.cve_reduction_count for plan in self.action_plans if plan.cve_reduction_count > 0)) + # Better approach: get unique CVEs across ALL actions + all_unique_cves = set() + action_cve_map = {} + for plan in self.action_plans: + if plan.action not in action_cve_map: + action_cve_map[plan.action] = plan.cve_reduction_count + all_unique_cves.add(plan.action) # Use action as proxy since CVE count is per action + + # Recalculate: sum unique CVEs across all actions + total_cve = sum(set(action_cve_map.values())) + total_critical = sum(plan.critical_reduction for plan in self.action_plans) + total_high = sum(plan.high_reduction for plan in self.action_plans) + + # Create summary list + summary_list = [] + for action, data in action_summary.items(): + summary_list.append({ + 'action': action, + 'system_count': len(data['systems']), + 'cve_reduction': data['cve_count'], + 'cve_percent': (data['cve_count'] / total_cve * 100) if total_cve > 0 else 0, + 'critical_reduction': data['critical_total'], + 'critical_percent': (data['critical_total'] / total_critical * 100) if total_critical > 0 else 0, + 'high_reduction': data['high_total'], + 'high_percent': (data['high_total'] / total_high * 100) if total_high > 0 else 0 + }) + + # Sort by total impact (critical + high) + summary_list.sort( + key=lambda x: (x['critical_reduction'], x['high_reduction'], x['cve_reduction']), + reverse=True + ) + + # Write summary CSV + with open(output_file, 'w', newline='', encoding='utf-8') as csvfile: + fieldnames = [ + 'Action', + 'System Count', + 'CVE Reduction', + '% of Total CVE Reduction', + 'Critical Reduction', + '% of Total Critical Reduction', + 'High Reduction', + '% of Total High Reduction' + ] + + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + writer.writeheader() + + for item in summary_list: + writer.writerow({ + 'Action': item['action'], + 'System Count': item['system_count'], + 'CVE Reduction': item['cve_reduction'], + '% of Total CVE Reduction': f"{item['cve_percent']:.1f}%", + 'Critical Reduction': item['critical_reduction'], + '% of Total Critical Reduction': f"{item['critical_percent']:.1f}%", + 'High Reduction': item['high_reduction'], + '% of Total High Reduction': f"{item['high_percent']:.1f}%" + }) + + print(f"\n✓ Summary exported to: {output_file}") + print(f" Total unique actions: {len(summary_list)}") + print(f" Total CVEs addressed: {total_cve}") + print(f" Total Critical vulnerabilities: {total_critical}") + print(f" Total High vulnerabilities: {total_high}") def print_summary(self): @@ -296,6 +386,7 @@ def main(): Examples: %(prog)s scan1.nessus scan2.nessus -o action_plan.csv %(prog)s *.nessus -o remediation_plan.csv + %(prog)s scan1.nessus --summary-only -o summary.csv """ ) @@ -311,6 +402,18 @@ Examples: help='Output CSV file (default: vulnerability_action_plan.csv)' ) + parser.add_argument( + '--summary', + action='store_true', + help='Also generate a summary CSV file (appends _summary to filename)' + ) + + parser.add_argument( + '--summary-only', + action='store_true', + help='Generate only the summary CSV file, skip detailed plan' + ) + args = parser.parse_args() # Validate input files @@ -338,12 +441,27 @@ Examples: # Display summary plan_generator.print_summary() - # Export to CSV - plan_generator.export_to_csv(args.output) + # Determine what to export + if args.summary_only: + # Only export summary + plan_generator.export_summary_csv(args.output) + elif args.summary: + # Export both detailed and summary + plan_generator.export_to_csv(args.output) + + # Generate summary filename + output_path = Path(args.output) + summary_filename = output_path.stem + '_summary' + output_path.suffix + summary_path = output_path.parent / summary_filename + plan_generator.export_summary_csv(str(summary_path)) + else: + # Default: only export detailed plan + plan_generator.export_to_csv(args.output) + print("\nTip: Use --summary flag to also generate a summary CSV") print("\n✓ Complete!") return 0 if __name__ == '__main__': - exit(main()) + exit(main()) \ No newline at end of file