aws-security-mcp 0.6.2 → 0.7.0

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.
@@ -19,6 +19,7 @@ interface Finding {
19
19
  module?: string;
20
20
  accountId?: string;
21
21
  accountAlias?: string;
22
+ source?: string;
22
23
  }
23
24
  interface ScanResult {
24
25
  module: string;
package/dist/src/index.js CHANGED
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import { z } from "zod";
5
5
 
6
6
  // src/version.ts
7
- var VERSION = "0.6.2";
7
+ var VERSION = "0.7.0";
8
8
 
9
9
  // src/utils/aws-client.ts
10
10
  import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
@@ -2639,6 +2639,22 @@ import {
2639
2639
  SecurityHubClient as SecurityHubClient2,
2640
2640
  GetFindingsCommand
2641
2641
  } from "@aws-sdk/client-securityhub";
2642
+
2643
+ // src/utils/sh-source.ts
2644
+ function getSecurityHubSource(finding) {
2645
+ const impact = finding.impact ?? "";
2646
+ const match = impact.match(/^Source:\s*([^(]+)/);
2647
+ if (!match) return "Other";
2648
+ const product = match[1].trim();
2649
+ if (product === "Security Hub" || product.includes("Foundational")) return "FSBP";
2650
+ if (product === "Inspector" || product.includes("Inspector")) return "Inspector";
2651
+ if (product === "GuardDuty" || product.includes("GuardDuty")) return "GuardDuty";
2652
+ if (product === "Config" || product.includes("Config")) return "Config";
2653
+ if (product === "IAM Access Analyzer" || product.includes("Access Analyzer")) return "Access Analyzer";
2654
+ return "Other";
2655
+ }
2656
+
2657
+ // src/scanners/security-hub-findings.ts
2642
2658
  function shSeverityToScore(label) {
2643
2659
  switch (label) {
2644
2660
  case "CRITICAL":
@@ -2708,7 +2724,7 @@ var SecurityHubFindingsScanner = class {
2708
2724
  if (recText && !["See References", "None Provided", ""].includes(recText.trim())) {
2709
2725
  remediationSteps.push(recText);
2710
2726
  }
2711
- findings.push({
2727
+ const finding = {
2712
2728
  severity,
2713
2729
  title: f.Title ?? "Security Hub Finding",
2714
2730
  resourceType,
@@ -2722,7 +2738,9 @@ var SecurityHubFindingsScanner = class {
2722
2738
  priority: priorityFromSeverity(severity),
2723
2739
  module: this.moduleName,
2724
2740
  accountId: f.AwsAccountId ?? accountId
2725
- });
2741
+ };
2742
+ finding.source = getSecurityHubSource(finding);
2743
+ findings.push(finding);
2726
2744
  }
2727
2745
  nextToken = resp.NextToken;
2728
2746
  } while (nextToken);
@@ -3646,6 +3664,12 @@ var zhI18n = {
3646
3664
  trendTitle: "30\u65E5\u8D8B\u52BF",
3647
3665
  findingsBySeverity: "\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0",
3648
3666
  showMoreCount: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u2026`,
3667
+ // Filter toolbar
3668
+ filterSeverity: "\u4E25\u91CD\u6027\uFF1A",
3669
+ filterModule: "\u6A21\u5757\uFF1A",
3670
+ filterAll: "\u5168\u90E8",
3671
+ filterAllModules: "\u5168\u90E8\u6A21\u5757",
3672
+ filterCountTpl: "\u663E\u793A {shown} / {total} \u4E2A\u53D1\u73B0",
3649
3673
  // Extended — MLPS extras
3650
3674
  // Markdown report
3651
3675
  executiveSummary: "\u6267\u884C\u6458\u8981",
@@ -3916,6 +3940,12 @@ var enI18n = {
3916
3940
  trendTitle: "30-Day Trends",
3917
3941
  findingsBySeverity: "Findings by Severity",
3918
3942
  showMoreCount: (n) => `Show ${n} more\u2026`,
3943
+ // Filter toolbar
3944
+ filterSeverity: "Severity:",
3945
+ filterModule: "Module:",
3946
+ filterAll: "All",
3947
+ filterAllModules: "All Modules",
3948
+ filterCountTpl: "Showing {shown} / {total} findings",
3919
3949
  // Extended \u2014 MLPS extras
3920
3950
  // Markdown report
3921
3951
  executiveSummary: "Executive Summary",
@@ -7034,18 +7064,6 @@ var SEVERITY_ORDER2 = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
7034
7064
  function getRecommendationTemplate(rem) {
7035
7065
  return rem.replace(/\b(i-[0-9a-f]+)\b/g, "{instance}").replace(/\b(vol-[0-9a-f]+)\b/g, "{volume}").replace(/\b(sg-[0-9a-f]+)\b/g, "{sg}").replace(/\b(eipalloc-[0-9a-f]+)\b/g, "{eip}").replace(/\b(arn:aws[-\w]*:[^"\s]+)\b/g, "{arn}").replace(/"[^"]+"/g, "{name}").replace(/bucket \S+/g, "bucket {name}").replace(/instance \S+/g, "instance {id}").replace(/volume \S+/g, "volume {id}").replace(/rule \S+/g, "rule {name}");
7036
7066
  }
7037
- function getSecurityHubSource(finding) {
7038
- const impact = finding.impact ?? "";
7039
- const match = impact.match(/^Source:\s*([^(]+)/);
7040
- if (!match) return "Other";
7041
- const product = match[1].trim();
7042
- if (product === "Security Hub" || product.includes("Foundational")) return "FSBP";
7043
- if (product === "Inspector" || product.includes("Inspector")) return "Inspector";
7044
- if (product === "GuardDuty" || product.includes("GuardDuty")) return "GuardDuty";
7045
- if (product === "Config" || product.includes("Config")) return "Config";
7046
- if (product === "IAM Access Analyzer" || product.includes("Access Analyzer")) return "Access Analyzer";
7047
- return "Other";
7048
- }
7049
7067
  var SECURITY_HUB_SUB_CAT_ORDER = ["FSBP", "Inspector", "GuardDuty", "Config", "Access Analyzer", "Other"];
7050
7068
  function scoreColor(score) {
7051
7069
  if (score >= 80) return "#22c55e";
@@ -7231,7 +7249,17 @@ function sharedCss() {
7231
7249
  .rec-body ol{padding-left:24px}
7232
7250
  .rec-body li{margin-bottom:8px;color:#cbd5e1;font-size:13px}
7233
7251
  .rec-body .badge{margin-right:6px;vertical-align:middle}
7252
+ .filter-toolbar{display:flex;flex-wrap:wrap;gap:16px;align-items:center;margin-bottom:20px;padding:12px 16px;background:#1e293b;border:1px solid #334155;border-radius:8px}
7253
+ .filter-group{display:flex;align-items:center;gap:8px}
7254
+ .filter-label{color:#94a3b8;font-size:13px}
7255
+ .filter-btn{padding:4px 12px;border-radius:4px;border:1px solid #475569;background:transparent;color:#cbd5e1;cursor:pointer;font-size:13px}
7256
+ .filter-btn:hover{background:#334155}
7257
+ .filter-btn.active{background:#3b82f6;border-color:#3b82f6;color:#fff}
7258
+ .filter-select{padding:4px 8px;border-radius:4px;border:1px solid #475569;background:#0f172a;color:#cbd5e1;font-size:13px}
7259
+ .filter-count{color:#64748b;font-size:13px;margin-left:auto}
7234
7260
  @media print{
7261
+ .filter-toolbar{display:none !important}
7262
+ .finding-card,.module-fold{display:block !important}
7235
7263
  body{background:#fff;color:#1e293b;-webkit-print-color-adjust:exact;print-color-adjust:exact}
7236
7264
  .container{max-width:100%;padding:20px}
7237
7265
  .card,.score-card,.stat-card,.chart-box,.finding-fold,.top5-card,.trend-chart,.category-fold,.module-fold,.finding-card,.rec-fold{background:#fff;border:1px solid #e2e8f0}
@@ -7490,13 +7518,14 @@ function generateHtmlReport(scanResults, history, lang) {
7490
7518
  </section>`;
7491
7519
  }
7492
7520
  let findingsHtml;
7521
+ let filterToolbarHtml = "";
7493
7522
  if (summary.totalFindings === 0) {
7494
7523
  findingsHtml = `<div class="no-findings">${esc(t.noIssuesFound)}</div>`;
7495
7524
  } else {
7496
7525
  const FOLD_THRESHOLD = 20;
7497
- const renderCard = (f) => {
7526
+ const renderCard = (f, moduleKey) => {
7498
7527
  const sev = f.severity.toLowerCase();
7499
- return `<div class="finding-card sev-${esc(sev)}">
7528
+ return `<div class="finding-card sev-${esc(sev)}" data-severity="${esc(f.severity)}" data-module="${esc(moduleKey)}">
7500
7529
  <span class="badge badge-${esc(sev)}">${esc(f.severity)}</span>
7501
7530
  <span class="finding-title-text">${esc(f.title)}</span>
7502
7531
  <span class="finding-resource">${esc(f.resourceArn || f.resourceId)}</span>
@@ -7507,12 +7536,12 @@ function generateHtmlReport(scanResults, history, lang) {
7507
7536
  </div></details>
7508
7537
  </div>`;
7509
7538
  };
7510
- const renderCards = (findings) => {
7539
+ const renderCards = (findings, moduleKey) => {
7511
7540
  if (findings.length <= FOLD_THRESHOLD) {
7512
- return findings.map(renderCard).join("\n");
7541
+ return findings.map((f) => renderCard(f, moduleKey)).join("\n");
7513
7542
  }
7514
- const first = findings.slice(0, FOLD_THRESHOLD).map(renderCard).join("\n");
7515
- const rest = findings.slice(FOLD_THRESHOLD).map(renderCard).join("\n");
7543
+ const first = findings.slice(0, FOLD_THRESHOLD).map((f) => renderCard(f, moduleKey)).join("\n");
7544
+ const rest = findings.slice(FOLD_THRESHOLD).map((f) => renderCard(f, moduleKey)).join("\n");
7516
7545
  return `${first}
7517
7546
  <details><summary>${t.showRemainingFindings(findings.length - FOLD_THRESHOLD)}</summary>
7518
7547
  ${rest}
@@ -7547,7 +7576,7 @@ ${rest}
7547
7576
  if (aHasCritHigh !== bHasCritHigh) return aHasCritHigh ? -1 : 1;
7548
7577
  return b[1].length - a[1].length;
7549
7578
  });
7550
- const renderSeverityGroups = (findings) => {
7579
+ const renderSeverityGroups = (findings, moduleKey) => {
7551
7580
  return SEVERITY_ORDER2.map((sev) => {
7552
7581
  const sevFindings = findings.filter((f) => f.severity === sev);
7553
7582
  if (sevFindings.length === 0) return "";
@@ -7556,7 +7585,7 @@ ${rest}
7556
7585
  const label = sev.charAt(0) + sev.slice(1).toLowerCase();
7557
7586
  return `<details class="severity-group-fold">
7558
7587
  <summary><h4>${emoji} ${label} (${sevFindings.length})</h4></summary>
7559
- ${renderCards(sevFindings)}
7588
+ ${renderCards(sevFindings, moduleKey)}
7560
7589
  </details>`;
7561
7590
  }).filter(Boolean).join("\n");
7562
7591
  };
@@ -7568,16 +7597,38 @@ ${rest}
7568
7597
  findingsHtml = moduleEntries.map(([modName, modFindings, subCatLabel]) => {
7569
7598
  const badges = renderModuleBadges(modFindings);
7570
7599
  const displayName = subCatLabel ?? (t.moduleNames[modName] ?? modName);
7571
- return `<details class="module-fold">
7600
+ return `<details class="module-fold" data-module="${esc(modName)}">
7572
7601
  <summary>
7573
7602
  <h3>&#128274; ${esc(displayName)} (${modFindings.length})</h3>
7574
7603
  <span class="module-badges">${badges}</span>
7575
7604
  </summary>
7576
7605
  <div class="module-body">
7577
- ${renderSeverityGroups(modFindings)}
7606
+ ${renderSeverityGroups(modFindings, modName)}
7578
7607
  </div>
7579
7608
  </details>`;
7580
7609
  }).join("\n");
7610
+ const moduleOptions = moduleEntries.map(([modKey, , subCatLabel]) => {
7611
+ const label = subCatLabel ?? (t.moduleNames[modKey] ?? modKey);
7612
+ return `<option value="${esc(modKey)}">${esc(label)}</option>`;
7613
+ }).join("\n ");
7614
+ filterToolbarHtml = `<div class="filter-toolbar" id="filterBar">
7615
+ <div class="filter-group">
7616
+ <span class="filter-label">${esc(t.filterSeverity)}</span>
7617
+ <button class="filter-btn active" data-severity="ALL">${esc(t.filterAll)}</button>
7618
+ <button class="filter-btn" data-severity="CRITICAL">Critical</button>
7619
+ <button class="filter-btn" data-severity="HIGH">High</button>
7620
+ <button class="filter-btn" data-severity="MEDIUM">Medium</button>
7621
+ <button class="filter-btn" data-severity="LOW">Low</button>
7622
+ </div>
7623
+ <div class="filter-group">
7624
+ <span class="filter-label">${esc(t.filterModule)}</span>
7625
+ <select class="filter-select" id="moduleFilter">
7626
+ <option value="ALL">${esc(t.filterAllModules)}</option>
7627
+ ${moduleOptions}
7628
+ </select>
7629
+ </div>
7630
+ <div class="filter-count" id="filterCount" data-tpl="${esc(t.filterCountTpl)}"></div>
7631
+ </div>`;
7581
7632
  }
7582
7633
  let trendHtml = "";
7583
7634
  if (history && history.length >= 2) {
@@ -7733,6 +7784,44 @@ ${remaining.map(renderRec).join("\n")}
7733
7784
  </div>
7734
7785
  </details>`;
7735
7786
  }
7787
+ const filterScript = summary.totalFindings > 0 ? `<script>
7788
+ (function(){
7789
+ var activeSev='ALL',activeMod='ALL';
7790
+ var countEl=document.getElementById('filterCount');
7791
+ var tpl=countEl?countEl.getAttribute('data-tpl'):'';
7792
+ function apply(){
7793
+ var cards=document.querySelectorAll('.finding-card[data-severity]');
7794
+ var shown=0,total=cards.length;
7795
+ cards.forEach(function(c){
7796
+ var sevOk=activeSev==='ALL'||c.getAttribute('data-severity')===activeSev;
7797
+ var modOk=activeMod==='ALL'||c.getAttribute('data-module')===activeMod;
7798
+ c.style.display=(sevOk&&modOk)?'':'none';
7799
+ if(sevOk&&modOk)shown++;
7800
+ });
7801
+ if(countEl)countEl.textContent=tpl.replace('{shown}',shown).replace('{total}',total);
7802
+ document.querySelectorAll('.module-fold').forEach(function(f){
7803
+ var mod=f.getAttribute('data-module');
7804
+ if(activeMod!=='ALL'&&mod!==activeMod){f.style.display='none';return;}
7805
+ var hasVisible=f.querySelectorAll('.finding-card:not([style*="display: none"])').length>0;
7806
+ f.style.display=hasVisible?'':'none';
7807
+ });
7808
+ document.querySelectorAll('.severity-group-fold').forEach(function(g){
7809
+ g.style.display=g.querySelectorAll('.finding-card:not([style*="display: none"])').length?'':'none';
7810
+ });
7811
+ }
7812
+ document.querySelectorAll('.filter-btn[data-severity]').forEach(function(b){
7813
+ b.addEventListener('click',function(){
7814
+ document.querySelectorAll('.filter-btn[data-severity]').forEach(function(x){x.classList.remove('active')});
7815
+ b.classList.add('active');
7816
+ activeSev=b.getAttribute('data-severity');
7817
+ apply();
7818
+ });
7819
+ });
7820
+ var sel=document.getElementById('moduleFilter');
7821
+ if(sel)sel.addEventListener('change',function(){activeMod=sel.value;apply();});
7822
+ apply();
7823
+ })();
7824
+ </script>` : "";
7736
7825
  return `<!DOCTYPE html>
7737
7826
  <html lang="${htmlLang}">
7738
7827
  <head>
@@ -7789,6 +7878,7 @@ ${buildServiceReminderHtml(modules, lang)}
7789
7878
 
7790
7879
  <section>
7791
7880
  <h2>${esc(t.allFindings)}</h2>
7881
+ ${filterToolbarHtml}
7792
7882
  ${findingsHtml}
7793
7883
  </section>
7794
7884
 
@@ -7800,6 +7890,7 @@ ${recsHtml}
7800
7890
  </footer>
7801
7891
 
7802
7892
  </div>
7893
+ ${filterScript}
7803
7894
  </body>
7804
7895
  </html>`;
7805
7896
  }