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.
@@ -237,7 +237,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
237
237
  import { z } from "zod";
238
238
 
239
239
  // src/version.ts
240
- var VERSION = "0.6.2";
240
+ var VERSION = "0.7.0";
241
241
 
242
242
  // src/utils/aws-client.ts
243
243
  import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
@@ -2867,6 +2867,22 @@ import {
2867
2867
  SecurityHubClient as SecurityHubClient2,
2868
2868
  GetFindingsCommand
2869
2869
  } from "@aws-sdk/client-securityhub";
2870
+
2871
+ // src/utils/sh-source.ts
2872
+ function getSecurityHubSource(finding) {
2873
+ const impact = finding.impact ?? "";
2874
+ const match = impact.match(/^Source:\s*([^(]+)/);
2875
+ if (!match) return "Other";
2876
+ const product = match[1].trim();
2877
+ if (product === "Security Hub" || product.includes("Foundational")) return "FSBP";
2878
+ if (product === "Inspector" || product.includes("Inspector")) return "Inspector";
2879
+ if (product === "GuardDuty" || product.includes("GuardDuty")) return "GuardDuty";
2880
+ if (product === "Config" || product.includes("Config")) return "Config";
2881
+ if (product === "IAM Access Analyzer" || product.includes("Access Analyzer")) return "Access Analyzer";
2882
+ return "Other";
2883
+ }
2884
+
2885
+ // src/scanners/security-hub-findings.ts
2870
2886
  function shSeverityToScore(label) {
2871
2887
  switch (label) {
2872
2888
  case "CRITICAL":
@@ -2936,7 +2952,7 @@ var SecurityHubFindingsScanner = class {
2936
2952
  if (recText && !["See References", "None Provided", ""].includes(recText.trim())) {
2937
2953
  remediationSteps.push(recText);
2938
2954
  }
2939
- findings.push({
2955
+ const finding = {
2940
2956
  severity,
2941
2957
  title: f.Title ?? "Security Hub Finding",
2942
2958
  resourceType,
@@ -2950,7 +2966,9 @@ var SecurityHubFindingsScanner = class {
2950
2966
  priority: priorityFromSeverity(severity),
2951
2967
  module: this.moduleName,
2952
2968
  accountId: f.AwsAccountId ?? accountId
2953
- });
2969
+ };
2970
+ finding.source = getSecurityHubSource(finding);
2971
+ findings.push(finding);
2954
2972
  }
2955
2973
  nextToken = resp.NextToken;
2956
2974
  } while (nextToken);
@@ -3874,6 +3892,12 @@ var zhI18n = {
3874
3892
  trendTitle: "30\u65E5\u8D8B\u52BF",
3875
3893
  findingsBySeverity: "\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0",
3876
3894
  showMoreCount: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u2026`,
3895
+ // Filter toolbar
3896
+ filterSeverity: "\u4E25\u91CD\u6027\uFF1A",
3897
+ filterModule: "\u6A21\u5757\uFF1A",
3898
+ filterAll: "\u5168\u90E8",
3899
+ filterAllModules: "\u5168\u90E8\u6A21\u5757",
3900
+ filterCountTpl: "\u663E\u793A {shown} / {total} \u4E2A\u53D1\u73B0",
3877
3901
  // Extended — MLPS extras
3878
3902
  // Markdown report
3879
3903
  executiveSummary: "\u6267\u884C\u6458\u8981",
@@ -4144,6 +4168,12 @@ var enI18n = {
4144
4168
  trendTitle: "30-Day Trends",
4145
4169
  findingsBySeverity: "Findings by Severity",
4146
4170
  showMoreCount: (n) => `Show ${n} more\u2026`,
4171
+ // Filter toolbar
4172
+ filterSeverity: "Severity:",
4173
+ filterModule: "Module:",
4174
+ filterAll: "All",
4175
+ filterAllModules: "All Modules",
4176
+ filterCountTpl: "Showing {shown} / {total} findings",
4147
4177
  // Extended \u2014 MLPS extras
4148
4178
  // Markdown report
4149
4179
  executiveSummary: "Executive Summary",
@@ -7262,18 +7292,6 @@ var SEVERITY_ORDER2 = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
7262
7292
  function getRecommendationTemplate(rem) {
7263
7293
  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}");
7264
7294
  }
7265
- function getSecurityHubSource(finding) {
7266
- const impact = finding.impact ?? "";
7267
- const match = impact.match(/^Source:\s*([^(]+)/);
7268
- if (!match) return "Other";
7269
- const product = match[1].trim();
7270
- if (product === "Security Hub" || product.includes("Foundational")) return "FSBP";
7271
- if (product === "Inspector" || product.includes("Inspector")) return "Inspector";
7272
- if (product === "GuardDuty" || product.includes("GuardDuty")) return "GuardDuty";
7273
- if (product === "Config" || product.includes("Config")) return "Config";
7274
- if (product === "IAM Access Analyzer" || product.includes("Access Analyzer")) return "Access Analyzer";
7275
- return "Other";
7276
- }
7277
7295
  var SECURITY_HUB_SUB_CAT_ORDER = ["FSBP", "Inspector", "GuardDuty", "Config", "Access Analyzer", "Other"];
7278
7296
  function scoreColor(score) {
7279
7297
  if (score >= 80) return "#22c55e";
@@ -7459,7 +7477,17 @@ function sharedCss() {
7459
7477
  .rec-body ol{padding-left:24px}
7460
7478
  .rec-body li{margin-bottom:8px;color:#cbd5e1;font-size:13px}
7461
7479
  .rec-body .badge{margin-right:6px;vertical-align:middle}
7480
+ .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}
7481
+ .filter-group{display:flex;align-items:center;gap:8px}
7482
+ .filter-label{color:#94a3b8;font-size:13px}
7483
+ .filter-btn{padding:4px 12px;border-radius:4px;border:1px solid #475569;background:transparent;color:#cbd5e1;cursor:pointer;font-size:13px}
7484
+ .filter-btn:hover{background:#334155}
7485
+ .filter-btn.active{background:#3b82f6;border-color:#3b82f6;color:#fff}
7486
+ .filter-select{padding:4px 8px;border-radius:4px;border:1px solid #475569;background:#0f172a;color:#cbd5e1;font-size:13px}
7487
+ .filter-count{color:#64748b;font-size:13px;margin-left:auto}
7462
7488
  @media print{
7489
+ .filter-toolbar{display:none !important}
7490
+ .finding-card,.module-fold{display:block !important}
7463
7491
  body{background:#fff;color:#1e293b;-webkit-print-color-adjust:exact;print-color-adjust:exact}
7464
7492
  .container{max-width:100%;padding:20px}
7465
7493
  .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}
@@ -7718,13 +7746,14 @@ function generateHtmlReport(scanResults, history, lang) {
7718
7746
  </section>`;
7719
7747
  }
7720
7748
  let findingsHtml;
7749
+ let filterToolbarHtml = "";
7721
7750
  if (summary.totalFindings === 0) {
7722
7751
  findingsHtml = `<div class="no-findings">${esc(t.noIssuesFound)}</div>`;
7723
7752
  } else {
7724
7753
  const FOLD_THRESHOLD = 20;
7725
- const renderCard = (f) => {
7754
+ const renderCard = (f, moduleKey) => {
7726
7755
  const sev = f.severity.toLowerCase();
7727
- return `<div class="finding-card sev-${esc(sev)}">
7756
+ return `<div class="finding-card sev-${esc(sev)}" data-severity="${esc(f.severity)}" data-module="${esc(moduleKey)}">
7728
7757
  <span class="badge badge-${esc(sev)}">${esc(f.severity)}</span>
7729
7758
  <span class="finding-title-text">${esc(f.title)}</span>
7730
7759
  <span class="finding-resource">${esc(f.resourceArn || f.resourceId)}</span>
@@ -7735,12 +7764,12 @@ function generateHtmlReport(scanResults, history, lang) {
7735
7764
  </div></details>
7736
7765
  </div>`;
7737
7766
  };
7738
- const renderCards = (findings) => {
7767
+ const renderCards = (findings, moduleKey) => {
7739
7768
  if (findings.length <= FOLD_THRESHOLD) {
7740
- return findings.map(renderCard).join("\n");
7769
+ return findings.map((f) => renderCard(f, moduleKey)).join("\n");
7741
7770
  }
7742
- const first = findings.slice(0, FOLD_THRESHOLD).map(renderCard).join("\n");
7743
- const rest = findings.slice(FOLD_THRESHOLD).map(renderCard).join("\n");
7771
+ const first = findings.slice(0, FOLD_THRESHOLD).map((f) => renderCard(f, moduleKey)).join("\n");
7772
+ const rest = findings.slice(FOLD_THRESHOLD).map((f) => renderCard(f, moduleKey)).join("\n");
7744
7773
  return `${first}
7745
7774
  <details><summary>${t.showRemainingFindings(findings.length - FOLD_THRESHOLD)}</summary>
7746
7775
  ${rest}
@@ -7775,7 +7804,7 @@ ${rest}
7775
7804
  if (aHasCritHigh !== bHasCritHigh) return aHasCritHigh ? -1 : 1;
7776
7805
  return b[1].length - a[1].length;
7777
7806
  });
7778
- const renderSeverityGroups = (findings) => {
7807
+ const renderSeverityGroups = (findings, moduleKey) => {
7779
7808
  return SEVERITY_ORDER2.map((sev) => {
7780
7809
  const sevFindings = findings.filter((f) => f.severity === sev);
7781
7810
  if (sevFindings.length === 0) return "";
@@ -7784,7 +7813,7 @@ ${rest}
7784
7813
  const label = sev.charAt(0) + sev.slice(1).toLowerCase();
7785
7814
  return `<details class="severity-group-fold">
7786
7815
  <summary><h4>${emoji} ${label} (${sevFindings.length})</h4></summary>
7787
- ${renderCards(sevFindings)}
7816
+ ${renderCards(sevFindings, moduleKey)}
7788
7817
  </details>`;
7789
7818
  }).filter(Boolean).join("\n");
7790
7819
  };
@@ -7796,16 +7825,38 @@ ${rest}
7796
7825
  findingsHtml = moduleEntries.map(([modName, modFindings, subCatLabel]) => {
7797
7826
  const badges = renderModuleBadges(modFindings);
7798
7827
  const displayName = subCatLabel ?? (t.moduleNames[modName] ?? modName);
7799
- return `<details class="module-fold">
7828
+ return `<details class="module-fold" data-module="${esc(modName)}">
7800
7829
  <summary>
7801
7830
  <h3>&#128274; ${esc(displayName)} (${modFindings.length})</h3>
7802
7831
  <span class="module-badges">${badges}</span>
7803
7832
  </summary>
7804
7833
  <div class="module-body">
7805
- ${renderSeverityGroups(modFindings)}
7834
+ ${renderSeverityGroups(modFindings, modName)}
7806
7835
  </div>
7807
7836
  </details>`;
7808
7837
  }).join("\n");
7838
+ const moduleOptions = moduleEntries.map(([modKey, , subCatLabel]) => {
7839
+ const label = subCatLabel ?? (t.moduleNames[modKey] ?? modKey);
7840
+ return `<option value="${esc(modKey)}">${esc(label)}</option>`;
7841
+ }).join("\n ");
7842
+ filterToolbarHtml = `<div class="filter-toolbar" id="filterBar">
7843
+ <div class="filter-group">
7844
+ <span class="filter-label">${esc(t.filterSeverity)}</span>
7845
+ <button class="filter-btn active" data-severity="ALL">${esc(t.filterAll)}</button>
7846
+ <button class="filter-btn" data-severity="CRITICAL">Critical</button>
7847
+ <button class="filter-btn" data-severity="HIGH">High</button>
7848
+ <button class="filter-btn" data-severity="MEDIUM">Medium</button>
7849
+ <button class="filter-btn" data-severity="LOW">Low</button>
7850
+ </div>
7851
+ <div class="filter-group">
7852
+ <span class="filter-label">${esc(t.filterModule)}</span>
7853
+ <select class="filter-select" id="moduleFilter">
7854
+ <option value="ALL">${esc(t.filterAllModules)}</option>
7855
+ ${moduleOptions}
7856
+ </select>
7857
+ </div>
7858
+ <div class="filter-count" id="filterCount" data-tpl="${esc(t.filterCountTpl)}"></div>
7859
+ </div>`;
7809
7860
  }
7810
7861
  let trendHtml = "";
7811
7862
  if (history && history.length >= 2) {
@@ -7961,6 +8012,44 @@ ${remaining.map(renderRec).join("\n")}
7961
8012
  </div>
7962
8013
  </details>`;
7963
8014
  }
8015
+ const filterScript = summary.totalFindings > 0 ? `<script>
8016
+ (function(){
8017
+ var activeSev='ALL',activeMod='ALL';
8018
+ var countEl=document.getElementById('filterCount');
8019
+ var tpl=countEl?countEl.getAttribute('data-tpl'):'';
8020
+ function apply(){
8021
+ var cards=document.querySelectorAll('.finding-card[data-severity]');
8022
+ var shown=0,total=cards.length;
8023
+ cards.forEach(function(c){
8024
+ var sevOk=activeSev==='ALL'||c.getAttribute('data-severity')===activeSev;
8025
+ var modOk=activeMod==='ALL'||c.getAttribute('data-module')===activeMod;
8026
+ c.style.display=(sevOk&&modOk)?'':'none';
8027
+ if(sevOk&&modOk)shown++;
8028
+ });
8029
+ if(countEl)countEl.textContent=tpl.replace('{shown}',shown).replace('{total}',total);
8030
+ document.querySelectorAll('.module-fold').forEach(function(f){
8031
+ var mod=f.getAttribute('data-module');
8032
+ if(activeMod!=='ALL'&&mod!==activeMod){f.style.display='none';return;}
8033
+ var hasVisible=f.querySelectorAll('.finding-card:not([style*="display: none"])').length>0;
8034
+ f.style.display=hasVisible?'':'none';
8035
+ });
8036
+ document.querySelectorAll('.severity-group-fold').forEach(function(g){
8037
+ g.style.display=g.querySelectorAll('.finding-card:not([style*="display: none"])').length?'':'none';
8038
+ });
8039
+ }
8040
+ document.querySelectorAll('.filter-btn[data-severity]').forEach(function(b){
8041
+ b.addEventListener('click',function(){
8042
+ document.querySelectorAll('.filter-btn[data-severity]').forEach(function(x){x.classList.remove('active')});
8043
+ b.classList.add('active');
8044
+ activeSev=b.getAttribute('data-severity');
8045
+ apply();
8046
+ });
8047
+ });
8048
+ var sel=document.getElementById('moduleFilter');
8049
+ if(sel)sel.addEventListener('change',function(){activeMod=sel.value;apply();});
8050
+ apply();
8051
+ })();
8052
+ </script>` : "";
7964
8053
  return `<!DOCTYPE html>
7965
8054
  <html lang="${htmlLang}">
7966
8055
  <head>
@@ -8017,6 +8106,7 @@ ${buildServiceReminderHtml(modules, lang)}
8017
8106
 
8018
8107
  <section>
8019
8108
  <h2>${esc(t.allFindings)}</h2>
8109
+ ${filterToolbarHtml}
8020
8110
  ${findingsHtml}
8021
8111
  </section>
8022
8112
 
@@ -8028,6 +8118,7 @@ ${recsHtml}
8028
8118
  </footer>
8029
8119
 
8030
8120
  </div>
8121
+ ${filterScript}
8031
8122
  </body>
8032
8123
  </html>`;
8033
8124
  }