aws-cis-controls-assessment 1.1.0__tar.gz → 1.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. {aws_cis_controls_assessment-1.1.0/aws_cis_controls_assessment.egg-info → aws_cis_controls_assessment-1.1.2}/PKG-INFO +1 -1
  2. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/__init__.py +1 -1
  3. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/reporters/html_reporter.py +201 -268
  4. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2/aws_cis_controls_assessment.egg-info}/PKG-INFO +1 -1
  5. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/LICENSE +0 -0
  6. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/MANIFEST.in +0 -0
  7. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/README.md +0 -0
  8. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/cli/__init__.py +0 -0
  9. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/cli/examples.py +0 -0
  10. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/cli/main.py +0 -0
  11. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/cli/utils.py +0 -0
  12. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/config/__init__.py +0 -0
  13. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/config/config_loader.py +0 -0
  14. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/config/rules/cis_controls_ig1.yaml +0 -0
  15. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/config/rules/cis_controls_ig2.yaml +0 -0
  16. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/config/rules/cis_controls_ig3.yaml +0 -0
  17. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/__init__.py +0 -0
  18. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/base_control.py +0 -0
  19. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/__init__.py +0 -0
  20. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_1_1.py +0 -0
  21. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_2_2.py +0 -0
  22. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_3_3.py +0 -0
  23. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_3_4.py +0 -0
  24. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_4_1.py +0 -0
  25. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_access_keys.py +0 -0
  26. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_advanced_security.py +0 -0
  27. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_aws_backup_service.py +0 -0
  28. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_backup_recovery.py +0 -0
  29. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_cloudtrail_logging.py +0 -0
  30. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_critical_security.py +0 -0
  31. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_data_protection.py +0 -0
  32. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_iam_advanced.py +0 -0
  33. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_iam_governance.py +0 -0
  34. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_iam_policies.py +0 -0
  35. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_instance_optimization.py +0 -0
  36. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_network_enhancements.py +0 -0
  37. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_network_security.py +0 -0
  38. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_s3_enhancements.py +0 -0
  39. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_s3_security.py +0 -0
  40. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig1/control_vpc_security.py +0 -0
  41. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/__init__.py +0 -0
  42. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_3_10.py +0 -0
  43. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_3_11.py +0 -0
  44. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_4_5_6_access_configuration.py +0 -0
  45. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_5_2.py +0 -0
  46. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_8_audit_logging.py +0 -0
  47. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_advanced_encryption.py +0 -0
  48. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_aws_backup_ig2.py +0 -0
  49. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_codebuild_security.py +0 -0
  50. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_encryption_rest.py +0 -0
  51. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_encryption_transit.py +0 -0
  52. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_network_ha.py +0 -0
  53. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_remaining_encryption.py +0 -0
  54. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_remaining_rules.py +0 -0
  55. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig2/control_service_logging.py +0 -0
  56. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig3/__init__.py +0 -0
  57. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig3/control_12_8.py +0 -0
  58. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig3/control_13_1.py +0 -0
  59. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig3/control_3_14.py +0 -0
  60. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/controls/ig3/control_7_1.py +0 -0
  61. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/__init__.py +0 -0
  62. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/accuracy_validator.py +0 -0
  63. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/assessment_engine.py +0 -0
  64. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/audit_trail.py +0 -0
  65. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/aws_client_factory.py +0 -0
  66. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/error_handler.py +0 -0
  67. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/models.py +0 -0
  68. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/core/scoring_engine.py +0 -0
  69. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/reporters/__init__.py +0 -0
  70. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/reporters/base_reporter.py +0 -0
  71. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/reporters/csv_reporter.py +0 -0
  72. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_assessment/reporters/json_reporter.py +0 -0
  73. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_controls_assessment.egg-info/SOURCES.txt +0 -0
  74. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_controls_assessment.egg-info/dependency_links.txt +0 -0
  75. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_controls_assessment.egg-info/entry_points.txt +0 -0
  76. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_controls_assessment.egg-info/requires.txt +0 -0
  77. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/aws_cis_controls_assessment.egg-info/top_level.txt +0 -0
  78. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/deprecation-package/aws_cis_assessment_deprecated/__init__.py +0 -0
  79. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/README.md +0 -0
  80. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/adding-aws-backup-controls.md +0 -0
  81. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/assessment-logic.md +0 -0
  82. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/cli-reference.md +0 -0
  83. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/config-rule-mappings.md +0 -0
  84. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/developer-guide.md +0 -0
  85. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/dual-scoring-implementation.md +0 -0
  86. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/html-report-improvements.md +0 -0
  87. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/installation.md +0 -0
  88. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/scoring-comparison-aws-config.md +0 -0
  89. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/scoring-methodology.md +0 -0
  90. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/troubleshooting.md +0 -0
  91. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/docs/user-guide.md +0 -0
  92. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/pyproject.toml +0 -0
  93. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/pytest.ini +0 -0
  94. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/requirements.txt +0 -0
  95. {aws_cis_controls_assessment-1.1.0 → aws_cis_controls_assessment-1.1.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-cis-controls-assessment
3
- Version: 1.1.0
3
+ Version: 1.1.2
4
4
  Summary: Production-ready AWS CIS Controls compliance assessment framework with 145 comprehensive rules
5
5
  Author-email: AWS CIS Assessment Team <security@example.com>
6
6
  Maintainer-email: AWS CIS Assessment Team <security@example.com>
@@ -6,6 +6,6 @@ CIS Controls Implementation Groups (IG1, IG2, IG3). Implements 163 comprehensive
6
6
  across all implementation groups for complete security compliance assessment.
7
7
  """
8
8
 
9
- __version__ = "1.1.0"
9
+ __version__ = "1.1.2"
10
10
  __author__ = "AWS CIS Assessment Team"
11
11
  __description__ = "Production-ready AWS CIS Controls Compliance Assessment Framework"
@@ -274,6 +274,8 @@ class HTMLReporter(ReportGenerator):
274
274
  def _generate_html_body(self, html_data: Dict[str, Any]) -> str:
275
275
  """Generate HTML body section with content.
276
276
 
277
+ Modified in v1.1.2 to remove Detailed Findings and Remediation sections.
278
+
277
279
  Args:
278
280
  html_data: Enhanced HTML report data
279
281
 
@@ -285,9 +287,7 @@ class HTMLReporter(ReportGenerator):
285
287
  navigation = self._generate_navigation(html_data)
286
288
  executive_dashboard = self._generate_executive_dashboard(html_data)
287
289
  implementation_groups = self._generate_implementation_groups_section(html_data)
288
- detailed_findings = self._generate_detailed_findings_section(html_data)
289
290
  resource_details = self._generate_resource_details_section(html_data)
290
- remediation_section = self._generate_remediation_section(html_data)
291
291
  footer = self._generate_footer(html_data)
292
292
 
293
293
  body_content = f"""<body>
@@ -296,9 +296,7 @@ class HTMLReporter(ReportGenerator):
296
296
  {navigation}
297
297
  {executive_dashboard}
298
298
  {implementation_groups}
299
- {detailed_findings}
300
299
  {resource_details}
301
- {remediation_section}
302
300
  {footer}
303
301
  </div>
304
302
 
@@ -771,6 +769,44 @@ class HTMLReporter(ReportGenerator):
771
769
  background-color: #2c3e50;
772
770
  }
773
771
 
772
+ /* Resource ID column width constraint - increased to 220px in v1.1.2 */
773
+ .resource-table td:first-child {
774
+ max-width: 220px;
775
+ overflow: hidden;
776
+ text-overflow: ellipsis;
777
+ white-space: nowrap;
778
+ }
779
+
780
+ .resource-table td:first-child:hover {
781
+ overflow: visible;
782
+ white-space: normal;
783
+ word-wrap: break-word;
784
+ }
785
+
786
+ /* Resource Type column width constraint - added in v1.1.2 (reduced by 20%) */
787
+ .resource-table td:nth-child(2) {
788
+ max-width: 150px;
789
+ overflow: hidden;
790
+ text-overflow: ellipsis;
791
+ white-space: nowrap;
792
+ }
793
+
794
+ .resource-table td:nth-child(2):hover {
795
+ overflow: visible;
796
+ white-space: normal;
797
+ word-wrap: break-word;
798
+ }
799
+
800
+ /* Visual frames around each resource row */
801
+ .resource-row {
802
+ border: 1px solid #e0e0e0;
803
+ border-radius: 4px;
804
+ }
805
+
806
+ .resource-row:hover {
807
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
808
+ }
809
+
774
810
  .resource-row.compliant {
775
811
  background-color: #f8fff8;
776
812
  }
@@ -1262,56 +1298,6 @@ class HTMLReporter(ReportGenerator):
1262
1298
  return;
1263
1299
  }}
1264
1300
 
1265
- // Implementation Groups Compliance Chart
1266
- const igChartCtx = document.getElementById('igComplianceChart');
1267
- if (igChartCtx) {{
1268
- new Chart(igChartCtx, {{
1269
- type: 'doughnut',
1270
- data: chartData.igCompliance,
1271
- options: {{
1272
- responsive: true,
1273
- maintainAspectRatio: false,
1274
- plugins: {{
1275
- legend: {{
1276
- position: 'bottom'
1277
- }},
1278
- title: {{
1279
- display: true,
1280
- text: 'Implementation Groups Compliance'
1281
- }}
1282
- }}
1283
- }}
1284
- }});
1285
- }}
1286
-
1287
- // Overall Compliance Trend Chart
1288
- const trendChartCtx = document.getElementById('complianceTrendChart');
1289
- if (trendChartCtx) {{
1290
- new Chart(trendChartCtx, {{
1291
- type: 'bar',
1292
- data: chartData.complianceTrend,
1293
- options: {{
1294
- responsive: true,
1295
- maintainAspectRatio: false,
1296
- scales: {{
1297
- y: {{
1298
- beginAtZero: true,
1299
- max: 100
1300
- }}
1301
- }},
1302
- plugins: {{
1303
- legend: {{
1304
- display: false
1305
- }},
1306
- title: {{
1307
- display: true,
1308
- text: 'Compliance by Implementation Group'
1309
- }}
1310
- }}
1311
- }}
1312
- }});
1313
- }}
1314
-
1315
1301
  // Risk Distribution Chart
1316
1302
  const riskChartCtx = document.getElementById('riskDistributionChart');
1317
1303
  if (riskChartCtx) {{
@@ -1474,17 +1460,34 @@ class HTMLReporter(ReportGenerator):
1474
1460
  function exportToCSV() {{
1475
1461
  const tables = document.querySelectorAll('.findings-table');
1476
1462
  let csvContent = '';
1463
+ let headersAdded = false;
1477
1464
 
1478
1465
  tables.forEach(function(table) {{
1479
1466
  const rows = table.querySelectorAll('tr');
1480
- rows.forEach(function(row) {{
1481
- const cells = row.querySelectorAll('th, td');
1482
- const rowData = Array.from(cells).map(cell =>
1483
- '"' + cell.textContent.replace(/"/g, '""') + '"'
1484
- ).join(',');
1485
- csvContent += rowData + '\\n';
1467
+ rows.forEach(function(row, index) {{
1468
+ // Add headers only once (from first table)
1469
+ if (index === 0) {{
1470
+ if (!headersAdded) {{
1471
+ const cells = row.querySelectorAll('th');
1472
+ if (cells.length > 0) {{
1473
+ const rowData = Array.from(cells).map(cell =>
1474
+ '"' + cell.textContent.replace(/"/g, '""') + '"'
1475
+ ).join(',');
1476
+ csvContent += rowData + '\\n';
1477
+ headersAdded = true;
1478
+ }}
1479
+ }}
1480
+ }} else {{
1481
+ // Add data rows (skip header rows from subsequent tables)
1482
+ const cells = row.querySelectorAll('td');
1483
+ if (cells.length > 0) {{
1484
+ const rowData = Array.from(cells).map(cell =>
1485
+ '"' + cell.textContent.replace(/"/g, '""') + '"'
1486
+ ).join(',');
1487
+ csvContent += rowData + '\\n';
1488
+ }}
1489
+ }}
1486
1490
  }});
1487
- csvContent += '\\n';
1488
1491
  }});
1489
1492
 
1490
1493
  const blob = new Blob([csvContent], {{ type: 'text/csv' }});
@@ -1496,11 +1499,12 @@ class HTMLReporter(ReportGenerator):
1496
1499
  window.URL.revokeObjectURL(url);
1497
1500
  }}
1498
1501
 
1499
- // Resource filtering functionality
1502
+ // Resource filtering functionality (updated in v1.1.2 to support Control filter)
1500
1503
  function filterResources() {{
1501
1504
  const searchTerm = document.getElementById('resourceSearch').value.toLowerCase();
1502
1505
  const statusFilter = document.getElementById('statusFilter').value;
1503
1506
  const typeFilter = document.getElementById('typeFilter').value;
1507
+ const controlFilter = document.getElementById('controlFilter').value;
1504
1508
  const rows = document.querySelectorAll('#resourceTable tbody tr');
1505
1509
 
1506
1510
  rows.forEach(function(row) {{
@@ -1509,6 +1513,7 @@ class HTMLReporter(ReportGenerator):
1509
1513
  const resourceType = cells[1].textContent;
1510
1514
  const status = cells[3].textContent.includes('COMPLIANT') ?
1511
1515
  (cells[3].textContent.includes('NON_COMPLIANT') ? 'NON_COMPLIANT' : 'COMPLIANT') : 'NON_COMPLIANT';
1516
+ const controlId = cells[4].textContent;
1512
1517
  const evaluationReason = cells[6].textContent.toLowerCase();
1513
1518
 
1514
1519
  const matchesSearch = resourceId.includes(searchTerm) ||
@@ -1516,8 +1521,9 @@ class HTMLReporter(ReportGenerator):
1516
1521
  evaluationReason.includes(searchTerm);
1517
1522
  const matchesStatus = !statusFilter || status === statusFilter;
1518
1523
  const matchesType = !typeFilter || resourceType === typeFilter;
1524
+ const matchesControl = !controlFilter || controlId === controlFilter;
1519
1525
 
1520
- row.style.display = (matchesSearch && matchesStatus && matchesType) ? '' : 'none';
1526
+ row.style.display = (matchesSearch && matchesStatus && matchesType && matchesControl) ? '' : 'none';
1521
1527
  }});
1522
1528
  }}
1523
1529
 
@@ -1547,18 +1553,74 @@ class HTMLReporter(ReportGenerator):
1547
1553
  }});
1548
1554
  }}
1549
1555
 
1550
- // Export resources to CSV
1556
+ // Export resources to CSV (updated in v1.1.2 with ARN, aggregate filtering, and port truncation)
1551
1557
  function exportResourcesToCSV() {{
1552
1558
  const table = document.getElementById('resourceTable');
1553
1559
  const rows = table.querySelectorAll('tr');
1554
1560
  let csvContent = '';
1555
1561
 
1556
- rows.forEach(function(row) {{
1562
+ // Excluded aggregate row IDs (v1.1.2)
1563
+ const excludedIds = ['5631', '6460', '629'];
1564
+
1565
+ // Helper function to truncate port lists (v1.1.2)
1566
+ function truncatePortList(text) {{
1567
+ // Match port list patterns like [0, 1, 2, 3, ...]
1568
+ const portListRegex = /\\[(\\d+(?:,\\s*\\d+)*)\\]/g;
1569
+
1570
+ return text.replace(portListRegex, function(match, ports) {{
1571
+ const portArray = ports.split(',').map(p => p.trim());
1572
+ if (portArray.length > 10) {{
1573
+ const truncated = portArray.slice(0, 10).join(', ');
1574
+ return `[${{truncated}}, ...]`;
1575
+ }}
1576
+ return match;
1577
+ }});
1578
+ }}
1579
+
1580
+ rows.forEach(function(row, index) {{
1557
1581
  const cells = row.querySelectorAll('th, td');
1558
- const rowData = Array.from(cells).map(cell =>
1559
- '"' + cell.textContent.replace(/"/g, '""').replace(/\\s+/g, ' ').trim() + '"'
1560
- ).join(',');
1561
- csvContent += rowData + '\\n';
1582
+
1583
+ // Handle header row - change "Resource ID" to "Resource ARN"
1584
+ if (index === 0) {{
1585
+ const headerData = Array.from(cells).map((cell, cellIndex) => {{
1586
+ let headerText = cell.textContent.replace(/\\s+/g, ' ').trim();
1587
+ // Replace "Resource ID" with "Resource ARN" in header
1588
+ if (cellIndex === 0 && headerText.includes('Resource ID')) {{
1589
+ headerText = headerText.replace('Resource ID', 'Resource ARN');
1590
+ }}
1591
+ return '"' + headerText.replace(/"/g, '""') + '"';
1592
+ }}).join(',');
1593
+ csvContent += headerData + '\\n';
1594
+ }} else if (cells.length > 0) {{
1595
+ // Get resource ID to check if it should be excluded
1596
+ const resourceIdCell = cells[0];
1597
+ const resourceId = resourceIdCell.textContent.replace(/\\s+/g, ' ').trim();
1598
+
1599
+ // Skip aggregate rows (v1.1.2)
1600
+ if (excludedIds.includes(resourceId)) {{
1601
+ return;
1602
+ }}
1603
+
1604
+ // Get ARN from data attribute or fall back to resource ID
1605
+ const resourceArn = resourceIdCell.getAttribute('data-arn') || resourceId;
1606
+
1607
+ const rowData = Array.from(cells).map((cell, cellIndex) => {{
1608
+ let cellText = cell.textContent.replace(/\\s+/g, ' ').trim();
1609
+
1610
+ // Use ARN for first column instead of Resource ID (v1.1.2)
1611
+ if (cellIndex === 0) {{
1612
+ cellText = resourceArn;
1613
+ }}
1614
+
1615
+ // Apply port list truncation to evaluation reason column (v1.1.2)
1616
+ if (cellIndex === 6) {{
1617
+ cellText = truncatePortList(cellText);
1618
+ }}
1619
+
1620
+ return '"' + cellText.replace(/"/g, '""') + '"';
1621
+ }}).join(',');
1622
+ csvContent += rowData + '\\n';
1623
+ }}
1562
1624
  }});
1563
1625
 
1564
1626
  const blob = new Blob([csvContent], {{ type: 'text/csv' }});
@@ -1657,6 +1719,9 @@ class HTMLReporter(ReportGenerator):
1657
1719
  def _generate_executive_dashboard(self, html_data: Dict[str, Any]) -> str:
1658
1720
  """Generate executive dashboard section.
1659
1721
 
1722
+ Modified in v1.1.1 to remove pie chart (igComplianceChart) and bar chart
1723
+ (complianceTrendChart), keeping only risk distribution chart.
1724
+
1660
1725
  Args:
1661
1726
  html_data: Enhanced HTML report data
1662
1727
 
@@ -1731,12 +1796,6 @@ class HTMLReporter(ReportGenerator):
1731
1796
  if self.include_charts:
1732
1797
  charts_section = f"""
1733
1798
  <div class="charts-section">
1734
- <div class="chart-container">
1735
- <canvas id="igComplianceChart"></canvas>
1736
- </div>
1737
- <div class="chart-container">
1738
- <canvas id="complianceTrendChart"></canvas>
1739
- </div>
1740
1799
  <div class="chart-container">
1741
1800
  <canvas id="riskDistributionChart"></canvas>
1742
1801
  </div>
@@ -1861,161 +1920,7 @@ class HTMLReporter(ReportGenerator):
1861
1920
  </section>
1862
1921
  """
1863
1922
 
1864
- def _generate_detailed_findings_section(self, html_data: Dict[str, Any]) -> str:
1865
- """Generate detailed findings section.
1866
-
1867
- This method consolidates findings by control ID only (not by IG) to eliminate
1868
- duplication. Each control appears once with IG membership indicators.
1869
-
1870
- Args:
1871
- html_data: Enhanced HTML report data
1872
-
1873
- Returns:
1874
- Detailed findings HTML as string
1875
- """
1876
- # Consolidate findings by control ID (deduplicated across IGs)
1877
- consolidated_findings = self._consolidate_findings_by_control(
1878
- html_data.get("implementation_groups", {})
1879
- )
1880
-
1881
- findings_content = ""
1882
-
1883
- # Generate findings grouped by control ID only (sorted alphanumerically)
1884
- for control_id, control_data in consolidated_findings.items():
1885
- findings = control_data.get('findings', [])
1886
-
1887
- # Skip if no non-compliant findings
1888
- if not findings:
1889
- continue
1890
-
1891
- # Get control metadata
1892
- config_rule_name = control_data.get('config_rule_name', '')
1893
- title = control_data.get('title', f'CIS Control {control_id}')
1894
- member_igs = control_data.get('member_igs', [])
1895
-
1896
- # Format display name for collapsible header
1897
- display_name = self._format_control_display_name(control_id, config_rule_name, title)
1898
-
1899
- # Generate IG membership badges
1900
- ig_badges_html = ""
1901
- for ig in member_igs:
1902
- badge_class = self._get_ig_badge_class(ig)
1903
- ig_badges_html += f'<span class="ig-membership-badge {badge_class}">{ig}</span>'
1904
-
1905
- # Generate findings table rows
1906
- findings_rows = ""
1907
- for finding in findings:
1908
- if finding.get("compliance_status") == "NON_COMPLIANT":
1909
- findings_rows += f"""
1910
- <tr>
1911
- <td>{finding.get('resource_id', 'N/A')}</td>
1912
- <td>{finding.get('resource_type', 'N/A')}</td>
1913
- <td>{finding.get('region', 'N/A')}</td>
1914
- <td><span class="badge {finding.get('compliance_status', '').lower()}">{finding.get('compliance_status', 'UNKNOWN')}</span></td>
1915
- <td>{finding.get('evaluation_reason', 'N/A')}</td>
1916
- <td>{finding.get('config_rule_name', config_rule_name)}</td>
1917
- </tr>
1918
- """
1919
-
1920
- # Only add control section if there are non-compliant findings
1921
- if findings_rows:
1922
- non_compliant_count = len([f for f in findings if f.get('compliance_status') == 'NON_COMPLIANT'])
1923
-
1924
- findings_content += f"""
1925
- <div class="control-findings-section">
1926
- <button class="collapsible">
1927
- <span class="control-display-name">{display_name}</span>
1928
- <span class="findings-count"> - Non-Compliant Resources ({non_compliant_count} items)</span>
1929
- </button>
1930
- <div class="collapsible-content">
1931
- <div class="ig-membership-badges" style="margin-bottom: 15px;">
1932
- <strong>Implementation Groups:</strong> {ig_badges_html}
1933
- </div>
1934
- <table class="findings-table">
1935
- <thead>
1936
- <tr>
1937
- <th>Resource ID</th>
1938
- <th>Resource Type</th>
1939
- <th>Region</th>
1940
- <th>Compliance Status</th>
1941
- <th>Reason</th>
1942
- <th>Config Rule</th>
1943
- </tr>
1944
- </thead>
1945
- <tbody>
1946
- {findings_rows}
1947
- </tbody>
1948
- </table>
1949
- </div>
1950
- </div>
1951
- """
1952
-
1953
- return f"""
1954
- <section id="detailed-findings" class="detailed-findings">
1955
- <h2>Detailed Findings</h2>
1956
- <p class="section-description">
1957
- Findings are grouped by control ID and deduplicated across Implementation Groups.
1958
- Each control shows which IGs include it.
1959
- </p>
1960
- <div class="search-container">
1961
- <input type="text" placeholder="Search findings..." onkeyup="searchFindings(this.value)" style="width: 100%; padding: 10px; margin-bottom: 20px; border: 1px solid #ddd; border-radius: 5px;">
1962
- </div>
1963
- {findings_content if findings_content else '<p>No non-compliant findings to display.</p>'}
1964
- </section>
1965
- """
1966
1923
 
1967
- def _generate_remediation_section(self, html_data: Dict[str, Any]) -> str:
1968
- """Generate remediation section.
1969
-
1970
- Args:
1971
- html_data: Enhanced HTML report data
1972
-
1973
- Returns:
1974
- Remediation HTML as string
1975
- """
1976
- remediation_items = ""
1977
-
1978
- for remediation in html_data["remediation_priorities"]:
1979
- steps_html = ""
1980
- for step in remediation["remediation_steps"]:
1981
- steps_html += f"<li>{step}</li>"
1982
-
1983
- # Normalize priority and effort text to proper capitalization
1984
- priority_text = remediation['priority'].capitalize()
1985
- effort_text = remediation['estimated_effort'].capitalize()
1986
-
1987
- remediation_items += f"""
1988
- <div class="remediation-item">
1989
- <div class="remediation-header">
1990
- <div>
1991
- <h4>{remediation['control_id']} - {remediation['config_rule_name']}</h4>
1992
- </div>
1993
- <div class="remediation-badges">
1994
- <span class="badge {remediation['priority_badge']}">{priority_text}</span>
1995
- <span class="badge {remediation['effort_badge']}">{effort_text}</span>
1996
- </div>
1997
- </div>
1998
- <div class="remediation-content">
1999
- <h5>Remediation Steps:</h5>
2000
- <ol>
2001
- {steps_html}
2002
- </ol>
2003
- <p><strong>Documentation:</strong> <a href="{remediation['aws_documentation_link']}" target="_blank">AWS Documentation</a></p>
2004
- </div>
2005
- </div>
2006
- """
2007
-
2008
- return f"""
2009
- <section id="remediation" class="remediation">
2010
- <h2>Remediation Priorities</h2>
2011
- <div class="remediation-list">
2012
- {remediation_items}
2013
- </div>
2014
- <div class="export-actions">
2015
- <button onclick="exportToCSV()" class="export-btn">Export Findings to CSV</button>
2016
- </div>
2017
- </section>
2018
- """
2019
1924
 
2020
1925
  def _generate_footer(self, html_data: Dict[str, Any]) -> str:
2021
1926
  """Generate footer section.
@@ -2121,6 +2026,8 @@ class HTMLReporter(ReportGenerator):
2121
2026
  def _build_navigation_structure(self, html_data: Dict[str, Any]) -> Dict[str, Any]:
2122
2027
  """Build navigation structure for the report.
2123
2028
 
2029
+ Modified in v1.1.2 to remove Detailed Findings and Remediation sections.
2030
+
2124
2031
  Args:
2125
2032
  html_data: Enhanced HTML report data
2126
2033
 
@@ -2131,9 +2038,7 @@ class HTMLReporter(ReportGenerator):
2131
2038
  "sections": [
2132
2039
  {"id": "dashboard", "title": "Dashboard"},
2133
2040
  {"id": "implementation-groups", "title": "Implementation Groups"},
2134
- {"id": "detailed-findings", "title": "Detailed Findings"},
2135
- {"id": "resource-details", "title": "Resource Details"},
2136
- {"id": "remediation", "title": "Remediation"}
2041
+ {"id": "resource-details", "title": "Resource Details"}
2137
2042
  ]
2138
2043
  }
2139
2044
 
@@ -2196,8 +2101,33 @@ class HTMLReporter(ReportGenerator):
2196
2101
  return "low"
2197
2102
 
2198
2103
  def _get_priority_badge(self, priority: str) -> str:
2199
- """Get priority badge class."""
2200
- return priority.lower()
2104
+ """Get priority badge class ensuring single value.
2105
+
2106
+ Modified in v1.1.1 to normalize priority values and handle duplicates.
2107
+ Fixes issues like "High High" → "high" and "High Medium" → "high".
2108
+
2109
+ Args:
2110
+ priority: Priority string (may contain multiple values like "High High" or "High Medium")
2111
+
2112
+ Returns:
2113
+ Single priority class: 'high', 'medium', or 'low'
2114
+ """
2115
+ # Extract first priority if multiple exist
2116
+ priority_lower = priority.lower().strip()
2117
+
2118
+ # Handle multiple priorities (take first one)
2119
+ if ' ' in priority_lower:
2120
+ priority_lower = priority_lower.split()[0]
2121
+
2122
+ # Normalize to standard values
2123
+ if 'high' in priority_lower:
2124
+ return 'high'
2125
+ elif 'medium' in priority_lower or 'med' in priority_lower:
2126
+ return 'medium'
2127
+ elif 'low' in priority_lower:
2128
+ return 'low'
2129
+ else:
2130
+ return 'medium' # Default fallback
2201
2131
 
2202
2132
  def _get_effort_badge(self, effort: str) -> str:
2203
2133
  """Get effort badge class."""
@@ -2238,40 +2168,27 @@ class HTMLReporter(ReportGenerator):
2238
2168
  score_diff: float) -> str:
2239
2169
  """Generate scoring methodology comparison section.
2240
2170
 
2171
+ Modified in v1.1.1 to remove "our approach" phrase, "Reflects actual security
2172
+ posture" text, and score difference warning for cleaner presentation.
2173
+
2241
2174
  Args:
2242
- weighted_score: Our weighted compliance score
2175
+ weighted_score: Weighted compliance score
2243
2176
  aws_config_score: AWS Config Conformance Pack style score
2244
- score_diff: Difference between the two scores
2177
+ score_diff: Difference between the two scores (not displayed)
2245
2178
 
2246
2179
  Returns:
2247
2180
  HTML section comparing the two scoring approaches
2248
2181
  """
2249
- # Determine interpretation based on difference
2250
- if abs(score_diff) < 1.0:
2251
- interpretation = "Both scoring approaches show similar results, indicating balanced compliance across all control priorities."
2252
- icon = "ℹ️"
2253
- diff_class = "neutral"
2254
- elif score_diff < 0:
2255
- # Weighted score is lower
2256
- interpretation = f"The weighted score is {abs(score_diff):.1f}% lower, indicating critical security controls need attention despite good overall resource compliance."
2257
- icon = "⚠️"
2258
- diff_class = "warning"
2259
- else:
2260
- # Weighted score is higher
2261
- interpretation = f"The weighted score is {score_diff:.1f}% higher, indicating strong performance in critical security controls despite some gaps in less critical areas."
2262
- icon = "✓"
2263
- diff_class = "positive"
2264
-
2265
2182
  return f"""
2266
2183
  <div class="score-comparison-section">
2267
2184
  <h3>Scoring Methodology Comparison</h3>
2268
2185
  <div class="comparison-grid">
2269
2186
  <div class="comparison-card">
2270
- <h4>Weighted Score (Our Approach)</h4>
2187
+ <h4>Weighted Score</h4>
2271
2188
  <div class="comparison-value">{weighted_score:.1f}%</div>
2272
2189
  <p class="comparison-description">
2273
2190
  Uses risk-based weighting where critical controls (encryption, access control)
2274
- have higher impact on the overall score. Reflects actual security posture.
2191
+ have higher impact on the overall score.
2275
2192
  </p>
2276
2193
  <ul class="comparison-features">
2277
2194
  <li>✓ Prioritizes critical security controls</li>
@@ -2294,16 +2211,6 @@ class HTMLReporter(ReportGenerator):
2294
2211
  </ul>
2295
2212
  </div>
2296
2213
  </div>
2297
-
2298
- <div class="score-difference {diff_class}">
2299
- <span class="diff-icon">{icon}</span>
2300
- <div class="diff-content">
2301
- <strong>Score Difference: {score_diff:+.1f}%</strong>
2302
- <p>{interpretation}</p>
2303
- </div>
2304
- </div>
2305
-
2306
-
2307
2214
  </div>
2308
2215
  """
2309
2216
 
@@ -2604,9 +2511,14 @@ class HTMLReporter(ReportGenerator):
2604
2511
  status_class = "compliant" if resource["compliance_status"] == "COMPLIANT" else "non_compliant"
2605
2512
  status_icon = "✓" if resource["compliance_status"] == "COMPLIANT" else "✗"
2606
2513
 
2514
+ # Construct pseudo-ARN for CSV export (v1.1.2)
2515
+ # Format: arn:aws:service:region:account:resource-type/resource-id
2516
+ # Since we don't have account ID in resource data, we'll use a placeholder
2517
+ resource_arn = f"arn:aws:{resource['resource_type'].split('::')[1].lower() if '::' in resource['resource_type'] else 'unknown'}:{resource['region']}:*:{resource['resource_id']}"
2518
+
2607
2519
  resource_rows += f"""
2608
2520
  <tr class="resource-row {status_class}">
2609
- <td><code>{resource['resource_id']}</code></td>
2521
+ <td data-arn="{resource_arn}"><code>{resource['resource_id']}</code></td>
2610
2522
  <td>{resource['resource_type']}</td>
2611
2523
  <td>{resource['region']}</td>
2612
2524
  <td>
@@ -2626,6 +2538,12 @@ class HTMLReporter(ReportGenerator):
2626
2538
  non_compliant_resources = total_resources - compliant_resources
2627
2539
  compliance_percentage = (compliant_resources / total_resources * 100) if total_resources > 0 else 0
2628
2540
 
2541
+ # Extract unique Control IDs for filter dropdown (v1.1.2)
2542
+ unique_control_ids = sorted(set(r["control_id"] for r in all_resources), key=self._sort_control_id)
2543
+ control_filter_options = ""
2544
+ for control_id in unique_control_ids:
2545
+ control_filter_options += f'<option value="{control_id}">{control_id}</option>'
2546
+
2629
2547
  # Generate resource type breakdown
2630
2548
  resource_type_stats = {}
2631
2549
  for resource in all_resources:
@@ -2699,6 +2617,10 @@ class HTMLReporter(ReportGenerator):
2699
2617
  <option value="">All Types</option>
2700
2618
  {self._generate_resource_type_options(resource_type_stats)}
2701
2619
  </select>
2620
+ <select id="controlFilter" onchange="filterResources()" class="filter-select">
2621
+ <option value="">All Controls</option>
2622
+ {control_filter_options}
2623
+ </select>
2702
2624
  </div>
2703
2625
 
2704
2626
  <table class="findings-table resource-table" id="resourceTable">
@@ -2869,6 +2791,8 @@ class HTMLReporter(ReportGenerator):
2869
2791
  Enriches control data with additional fields needed for improved display,
2870
2792
  including formatted names, IG membership badges, and truncation indicators.
2871
2793
 
2794
+ Modified in v1.1.2 to remove duplicate Control ID prefix from display names.
2795
+
2872
2796
  Args:
2873
2797
  control_data: Existing control data dictionary
2874
2798
  control_id: Control identifier (e.g., "1.5")
@@ -2877,7 +2801,7 @@ class HTMLReporter(ReportGenerator):
2877
2801
 
2878
2802
  Returns:
2879
2803
  Enhanced control data with additional fields:
2880
- - display_name: Formatted name combining control ID and rule name
2804
+ - display_name: Formatted name without duplicate Control ID prefix
2881
2805
  - originating_ig: Which IG introduced this control (IG1, IG2, or IG3)
2882
2806
  - ig_badge_class: CSS class for IG badge styling
2883
2807
  - needs_truncation: Boolean indicating if display name exceeds 50 characters
@@ -2894,7 +2818,7 @@ class HTMLReporter(ReportGenerator):
2894
2818
  Output enriched data (includes all input fields plus):
2895
2819
  {
2896
2820
  ...original fields...,
2897
- 'display_name': '1.5: root-account-hardware-mfa-enabled',
2821
+ 'display_name': 'root-account-hardware-mfa-enabled', # No "1.5:" prefix
2898
2822
  'originating_ig': 'IG1',
2899
2823
  'ig_badge_class': 'ig-badge-1',
2900
2824
  'needs_truncation': False
@@ -2905,16 +2829,25 @@ class HTMLReporter(ReportGenerator):
2905
2829
  - Truncation threshold is 50 characters
2906
2830
  - Gracefully handles missing config_rule_name
2907
2831
  - Originating IG is determined by checking IG1, IG2, IG3 in order
2832
+ - v1.1.2: Removes duplicate Control ID prefix from display names
2908
2833
  """
2909
2834
  enriched = control_data.copy()
2910
2835
 
2911
2836
  # Format display name
2912
- enriched['display_name'] = self._format_control_display_name(
2837
+ display_name = self._format_control_display_name(
2913
2838
  control_id,
2914
2839
  control_data.get('config_rule_name', ''),
2915
2840
  control_data.get('title')
2916
2841
  )
2917
2842
 
2843
+ # Remove duplicate Control ID prefix if present (v1.1.2 improvement)
2844
+ # Check if display_name starts with "control_id: "
2845
+ prefix = f"{control_id}: "
2846
+ if display_name.startswith(prefix):
2847
+ display_name = display_name[len(prefix):]
2848
+
2849
+ enriched['display_name'] = display_name
2850
+
2918
2851
  # Determine originating IG (which IG introduced this control)
2919
2852
  originating_ig = self._determine_originating_ig(control_id, all_igs)
2920
2853
  enriched['originating_ig'] = originating_ig
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-cis-controls-assessment
3
- Version: 1.1.0
3
+ Version: 1.1.2
4
4
  Summary: Production-ready AWS CIS Controls compliance assessment framework with 145 comprehensive rules
5
5
  Author-email: AWS CIS Assessment Team <security@example.com>
6
6
  Maintainer-email: AWS CIS Assessment Team <security@example.com>