aws-security-mcp 0.7.5 → 0.7.6

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.
@@ -49,6 +49,8 @@ interface FullScanResult {
49
49
  region: string;
50
50
  accountId: string;
51
51
  modules: ScanResult[];
52
+ /** Optional pre-generated AI executive summary (client AI supplies; server never calls an LLM). */
53
+ aiSummary?: string;
52
54
  summary: {
53
55
  totalFindings: number;
54
56
  critical: number;
@@ -81,6 +83,8 @@ interface DashboardData {
81
83
  status: string;
82
84
  }>;
83
85
  findings: Finding[];
86
+ /** Optional pre-generated AI executive summary. */
87
+ aiSummary?: string;
84
88
  };
85
89
  history: DashboardHistoryEntry[];
86
90
  meta: {
@@ -124,15 +128,25 @@ declare function listOrgAccounts(region: string): Promise<OrgAccount[]>;
124
128
 
125
129
  declare function generateMarkdownReport(scanResults: FullScanResult, lang?: Lang): string;
126
130
 
131
+ declare function generateMlps3Report(scanResults: FullScanResult, lang?: Lang): string;
132
+
127
133
  declare function generateHtmlReport(scanResults: FullScanResult, history?: DashboardHistoryEntry[], lang?: Lang): string;
128
134
  declare function generateMlps3HtmlReport(scanResults: FullScanResult, history?: DashboardHistoryEntry[], lang?: Lang): string;
129
135
 
130
136
  declare function generateHwDefenseHtmlReport(scanResults: FullScanResult, lang?: Lang): string;
131
137
 
138
+ type ReportType = "dashboard" | "html" | "hw_defense" | "mlps3";
139
+ /**
140
+ * Build a ready-to-run prompt for the calling client AI to produce a
141
+ * report-type-tailored AI summary. The server never calls an LLM itself —
142
+ * it only assembles this prompt + a grounded findings digest.
143
+ */
144
+ declare function buildAiSummaryPrompt(type: ReportType, scan: FullScanResult, lang: Lang): string;
145
+
132
146
  declare function calculateScore(summary: FullScanResult["summary"]): number;
133
147
  declare function saveResults(scanResults: FullScanResult, outputDir?: string): string;
134
148
 
135
149
  declare function createServer(defaultRegion: string): McpServer;
136
150
  declare function startServer(defaultRegion: string): Promise<void>;
137
151
 
138
- export { type DashboardData, type DashboardHistoryEntry, type Finding, type FullScanResult, type Lang, type OrgAccount, type Priority, type ScanContext, type ScanResult, type Scanner, type Severity, assumeRole, buildRoleArn, calculateScore, createServer, generateHtmlReport, generateHwDefenseHtmlReport, generateMarkdownReport, generateMlps3HtmlReport, getCurrentAccountId, listOrgAccounts, runAllScanners, runMultiAccountScanners, saveResults, startServer };
152
+ export { type DashboardData, type DashboardHistoryEntry, type Finding, type FullScanResult, type Lang, type OrgAccount, type Priority, type ReportType, type ScanContext, type ScanResult, type Scanner, type Severity, assumeRole, buildAiSummaryPrompt, buildRoleArn, calculateScore, createServer, generateHtmlReport, generateHwDefenseHtmlReport, generateMarkdownReport, generateMlps3HtmlReport, generateMlps3Report, getCurrentAccountId, listOrgAccounts, runAllScanners, runMultiAccountScanners, saveResults, startServer };
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.7.5";
7
+ var VERSION = "0.7.6";
8
8
 
9
9
  // src/utils/aws-client.ts
10
10
  import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
@@ -84,8 +84,7 @@ async function listOrgAccounts(region) {
84
84
  return accounts;
85
85
  }
86
86
 
87
- // src/scanners/runner.ts
88
- var DEFAULT_CONCURRENCY = 5;
87
+ // src/utils/concurrency.ts
89
88
  async function runWithConcurrency(tasks, limit) {
90
89
  const results = [];
91
90
  const executing = /* @__PURE__ */ new Set();
@@ -104,6 +103,9 @@ async function runWithConcurrency(tasks, limit) {
104
103
  await Promise.all(executing);
105
104
  return results;
106
105
  }
106
+
107
+ // src/scanners/runner.ts
108
+ var DEFAULT_CONCURRENCY = 5;
107
109
  var AGGREGATION_MODULES = /* @__PURE__ */ new Set([
108
110
  "security_hub_findings",
109
111
  "guardduty_findings",
@@ -674,24 +676,6 @@ import {
674
676
  DescribeInstanceAttributeCommand
675
677
  } from "@aws-sdk/client-ec2";
676
678
  var USERDATA_CONCURRENCY = 5;
677
- async function runWithConcurrency2(tasks, limit) {
678
- const results = [];
679
- const executing = /* @__PURE__ */ new Set();
680
- for (let i = 0; i < tasks.length; i++) {
681
- const idx = i;
682
- const p = tasks[idx]().then((value) => {
683
- results[idx] = { status: "fulfilled", value };
684
- }).catch((reason) => {
685
- results[idx] = { status: "rejected", reason };
686
- }).finally(() => {
687
- executing.delete(p);
688
- });
689
- executing.add(p);
690
- if (executing.size >= limit) await Promise.race(executing);
691
- }
692
- await Promise.all(executing);
693
- return results;
694
- }
695
679
  var SECRET_PATTERNS = [
696
680
  { name: "AWS Access Key", pattern: /AKIA[0-9A-Z]{16}/, matchType: "value" },
697
681
  { name: "Private Key", pattern: /-----BEGIN.*PRIVATE KEY-----/, matchType: "value" },
@@ -802,7 +786,7 @@ var SecretExposureScanner = class {
802
786
  const raw = attrResp.UserData?.Value;
803
787
  return { instId, userData: raw ? Buffer.from(raw, "base64").toString("utf-8") : void 0 };
804
788
  });
805
- const settled = await runWithConcurrency2(userDataTasks, USERDATA_CONCURRENCY);
789
+ const settled = await runWithConcurrency(userDataTasks, USERDATA_CONCURRENCY);
806
790
  for (let i = 0; i < instances.length; i++) {
807
791
  const result = settled[i];
808
792
  const inst = instances[i];
@@ -1782,7 +1766,8 @@ async function getBucketRegion(client, bucketName, defaultRegion, warnings) {
1782
1766
  const resp = await client.send(
1783
1767
  new GetBucketLocationCommand({ Bucket: bucketName })
1784
1768
  );
1785
- const loc = String(resp.LocationConstraint ?? "") || "us-east-1";
1769
+ const rawLoc = String(resp.LocationConstraint ?? "") || "us-east-1";
1770
+ const loc = rawLoc === "EU" ? "eu-west-1" : rawLoc;
1786
1771
  return loc;
1787
1772
  } catch (e) {
1788
1773
  const msg = e instanceof Error ? e.message : String(e);
@@ -3738,6 +3723,7 @@ var zhI18n = {
3738
3723
  // Extended — MLPS extras
3739
3724
  // Markdown report
3740
3725
  executiveSummary: "\u6267\u884C\u6458\u8981",
3726
+ aiSummaryTitle: "AI \u5B89\u5168\u6001\u52BF\u603B\u7ED3",
3741
3727
  totalFindingsLabel: "\u53D1\u73B0\u603B\u6570",
3742
3728
  description: "\u63CF\u8FF0",
3743
3729
  priority: "\u4F18\u5148\u7EA7",
@@ -4047,6 +4033,7 @@ var enI18n = {
4047
4033
  // Extended \u2014 MLPS extras
4048
4034
  // Markdown report
4049
4035
  executiveSummary: "Executive Summary",
4036
+ aiSummaryTitle: "AI Security Summary",
4050
4037
  totalFindingsLabel: "Total Findings",
4051
4038
  description: "Description",
4052
4039
  priority: "Priority",
@@ -4308,6 +4295,14 @@ function generateMarkdownReport(scanResults, lang) {
4308
4295
  `- **${t.totalFindingsLabel}:** ${summary.totalFindings} (${SEVERITY_ICON.CRITICAL} ${summary.critical} ${t.critical} | ${SEVERITY_ICON.HIGH} ${summary.high} ${t.high} | ${SEVERITY_ICON.MEDIUM} ${summary.medium} ${t.medium} | ${SEVERITY_ICON.LOW} ${summary.low} ${t.low})`
4309
4296
  );
4310
4297
  lines.push("");
4298
+ if (scanResults.aiSummary && scanResults.aiSummary.trim()) {
4299
+ lines.push(`> \u2728 **${t.aiSummaryTitle}**`);
4300
+ lines.push(">");
4301
+ for (const ln of scanResults.aiSummary.trim().split(/\r?\n/)) {
4302
+ lines.push(`> ${ln}`);
4303
+ }
4304
+ lines.push("");
4305
+ }
4311
4306
  if (summary.totalFindings === 0) {
4312
4307
  lines.push(`## ${t.findingsBySeverity}`);
4313
4308
  lines.push("");
@@ -7085,6 +7080,14 @@ function generateMlps3Report(scanResults, lang) {
7085
7080
  lines.push(`## ${t.accountInfo}`);
7086
7081
  lines.push(`- ${t.account}: ${accountId} | ${t.region}: ${region} | ${t.scanTime}: ${scanTime}`);
7087
7082
  lines.push("");
7083
+ if (scanResults.aiSummary && scanResults.aiSummary.trim()) {
7084
+ lines.push(`> \u2728 **${t.aiSummaryTitle}**`);
7085
+ lines.push(">");
7086
+ for (const ln of scanResults.aiSummary.trim().split(/\r?\n/)) {
7087
+ lines.push(`> ${ln}`);
7088
+ }
7089
+ lines.push("");
7090
+ }
7088
7091
  lines.push(`## ${t.preCheckOverview}`);
7089
7092
  lines.push(`- ${t.checkedCount(checkedTotal, autoClean, autoIssues)}`);
7090
7093
  if (autoUnknown > 0) {
@@ -7187,6 +7190,14 @@ function escWithLinks(s) {
7187
7190
  return esc(part);
7188
7191
  }).join("");
7189
7192
  }
7193
+ function aiSummaryBlock(summary, title) {
7194
+ const text = (summary ?? "").trim();
7195
+ if (!text) return "";
7196
+ return `<div class="ai-summary">
7197
+ <div class="ai-summary-title">\u2728 ${esc(title)}</div>
7198
+ <div class="ai-summary-body">${esc(text)}</div>
7199
+ </div>`;
7200
+ }
7190
7201
  function calcScore(summary) {
7191
7202
  const raw = 100 - summary.critical * 15 - summary.high * 5 - summary.medium * 2 - summary.low * 0.5;
7192
7203
  return Math.max(0, Math.min(100, Math.round(raw)));
@@ -7431,7 +7442,13 @@ function sharedCss() {
7431
7442
  details{display:block}
7432
7443
  details>summary{display:block}
7433
7444
  details>:not(summary){display:block !important}
7445
+ .ai-summary{background:#f5f3ff !important;border-color:#c7d2fe !important}
7446
+ .ai-summary-title{color:#4338ca !important}
7447
+ .ai-summary-body{color:#1e293b !important}
7434
7448
  }
7449
+ .ai-summary{background:linear-gradient(135deg,#1e293b,#1e1b4b);border:1px solid #4338ca;border-radius:10px;padding:16px 20px;margin:0 0 28px}
7450
+ .ai-summary-title{font-size:14px;font-weight:700;color:#a5b4fc;margin-bottom:8px}
7451
+ .ai-summary-body{font-size:14px;line-height:1.7;color:#cbd5e1;white-space:pre-wrap}
7435
7452
  `;
7436
7453
  }
7437
7454
  function donutChart(summary) {
@@ -7983,6 +8000,8 @@ ${remaining.map(renderRec).join("\n")}
7983
8000
  <div class="meta">${esc(t.account)}: ${esc(accountId)} | ${esc(t.region)}: ${esc(region)} | ${esc(date)} | ${esc(t.duration)}: ${esc(duration)}</div>
7984
8001
  </header>
7985
8002
 
8003
+ ${aiSummaryBlock(scanResults.aiSummary, t.aiSummaryTitle)}
8004
+
7986
8005
  <section class="summary">
7987
8006
  <div class="score-card">
7988
8007
  <div class="score-value" style="color:${scoreColor(score)}">${score}</div>
@@ -8330,6 +8349,8 @@ ${mlpsRemaining.map(renderMlpsRec).join("\n")}
8330
8349
  <div class="meta">${esc(t.account)}: ${esc(accountId)} | ${esc(t.region)}: ${esc(region)} | ${esc(t.scanTime)}: ${esc(scanTime)}</div>
8331
8350
  </header>
8332
8351
 
8352
+ ${aiSummaryBlock(scanResults.aiSummary, t.aiSummaryTitle)}
8353
+
8333
8354
  <section class="summary" style="display:block;text-align:center">
8334
8355
  <div style="font-size:36px;font-weight:700;margin-bottom:12px">
8335
8356
  <span style="color:#22c55e">${autoClean}</span> <span style="color:#94a3b8;font-size:18px">${esc(t.noIssues)}</span>
@@ -8378,6 +8399,14 @@ function safeUrl2(url) {
8378
8399
  return null;
8379
8400
  }
8380
8401
  }
8402
+ function aiSummaryBlock2(summary, title) {
8403
+ const text = (summary ?? "").trim();
8404
+ if (!text) return "";
8405
+ return `<div class="ai-summary">
8406
+ <div class="ai-summary-title">\u2728 ${esc2(title)}</div>
8407
+ <div class="ai-summary-body">${esc2(text)}</div>
8408
+ </div>`;
8409
+ }
8381
8410
  function escWithLinks2(s) {
8382
8411
  const parts = s.split(/(https?:\/\/\S+)/);
8383
8412
  return parts.map((part, i) => {
@@ -8513,7 +8542,13 @@ function hwCss() {
8513
8542
  details{display:block}
8514
8543
  details>summary{display:block}
8515
8544
  details>:not(summary){display:block !important}
8545
+ .ai-summary{background:#f5f3ff !important;border-color:#c7d2fe !important}
8546
+ .ai-summary-title{color:#4338ca !important}
8547
+ .ai-summary-body{color:#1e293b !important}
8516
8548
  }
8549
+ .ai-summary{background:linear-gradient(135deg,#1e293b,#1e1b4b);border:1px solid #4338ca;border-radius:10px;padding:16px 20px;margin:0 0 28px}
8550
+ .ai-summary-title{font-size:14px;font-weight:700;color:#a5b4fc;margin-bottom:8px}
8551
+ .ai-summary-body{font-size:14px;line-height:1.7;color:#cbd5e1;white-space:pre-wrap}
8517
8552
  `;
8518
8553
  }
8519
8554
  function generateHwDefenseHtmlReport(scanResults, lang) {
@@ -8714,6 +8749,8 @@ function generateHwDefenseHtmlReport(scanResults, lang) {
8714
8749
  <div class="meta">${esc2(t.account)}: ${esc2(accountId)} | ${esc2(t.region)}: ${esc2(region)} | ${esc2(t.scanTime)}: ${esc2(scanTime)}</div>
8715
8750
  </header>
8716
8751
 
8752
+ ${aiSummaryBlock2(scanResults.aiSummary, t.aiSummaryTitle)}
8753
+
8717
8754
  <section class="summary-cards">
8718
8755
  <div class="summary-card"><div class="stat-count" style="color:${findingsColor}">${totalFindings}</div><div class="stat-label">${esc2(t.hwTotalFindings)}</div></div>
8719
8756
  <div class="summary-card"><div class="stat-count" style="color:#60a5fa">${sectionsChecked}</div><div class="stat-label">${esc2(t.hwSectionsChecked)}</div></div>
@@ -8733,6 +8770,137 @@ ${sectionsHtml}
8733
8770
  </html>`;
8734
8771
  }
8735
8772
 
8773
+ // src/tools/ai-summary-prompt.ts
8774
+ function buildFindingsDigest(scan, lang, topN = 12) {
8775
+ const zh = lang === "zh";
8776
+ const all = scan.modules.flatMap(
8777
+ (m) => m.findings.map((f) => ({ ...f, module: f.module ?? m.module }))
8778
+ );
8779
+ const s = scan.summary;
8780
+ const byModule = [...scan.modules].filter((m) => m.findingsCount > 0).sort((a, b) => b.findingsCount - a.findingsCount).slice(0, 19).map((m) => `${m.module}=${m.findingsCount}`).join(", ");
8781
+ const top = [...all].sort((a, b) => b.riskScore - a.riskScore).slice(0, topN);
8782
+ const topLines = top.map(
8783
+ (f, i) => `${i + 1}. [${f.severity}/${f.priority}] ${f.title} \u2014 ${f.resourceType} ${f.resourceId} (${f.region}) risk=${f.riskScore}`
8784
+ ).join("\n");
8785
+ const head = zh ? `\u8D26\u53F7: ${scan.accountId} | \u533A\u57DF: ${scan.region}
8786
+ \u98CE\u9669\u603B\u6570: ${s.totalFindings} (CRITICAL ${s.critical} / HIGH ${s.high} / MEDIUM ${s.medium} / LOW ${s.low})
8787
+ \u6A21\u5757\u5206\u5E03: ${byModule || "\u65E0"}
8788
+
8789
+ \u9AD8\u98CE\u9669 Top ${top.length}:
8790
+ ${topLines || "\u65E0"}` : `Account: ${scan.accountId} | Region: ${scan.region}
8791
+ Total findings: ${s.totalFindings} (CRITICAL ${s.critical} / HIGH ${s.high} / MEDIUM ${s.medium} / LOW ${s.low})
8792
+ Module breakdown: ${byModule || "none"}
8793
+
8794
+ Top ${top.length} by risk:
8795
+ ${topLines || "none"}`;
8796
+ return head;
8797
+ }
8798
+ function getProfile(type, lang) {
8799
+ const zh = lang === "zh";
8800
+ switch (type) {
8801
+ case "dashboard":
8802
+ return zh ? {
8803
+ persona: "\u4F60\u5728\u4E3A\u5B89\u5168\u8FD0\u8425\u4EEA\u8868\u76D8\u5199\u4E00\u6BB5\u9AD8\u7BA1\u901F\u89C8\u3002\u8BFB\u8005\u662F CISO / \u7BA1\u7406\u5C42\uFF0C\u65F6\u95F4\u5F88\u5C11\u3002",
8804
+ focus: "\u6574\u4F53\u5B89\u5168\u6001\u52BF\u4E00\u53E5\u8BDD\u5B9A\u8C03 + \u5B89\u5168\u8BC4\u5206\u89E3\u8BFB + \u6B64\u523B\u6700\u8BE5\u5173\u6CE8\u7684 3 \u4EF6\u4E8B\uFF08\u6309\u4E1A\u52A1\u5F71\u54CD\u6392\u5E8F\uFF09\u3002\u4E0D\u8981\u7F57\u5217\u6240\u6709 finding\u3002",
8805
+ format: "3-5 \u53E5\u8BDD\uFF0C\u5F00\u5934\u4E00\u53E5\u7ED3\u8BBA\u5148\u884C\u3002\u4E2D\u6587\u3002\u4E0D\u7528 Markdown \u6807\u9898\uFF0C\u53EF\u7528\u7B80\u77ED\u5206\u53F7\u6216\u6362\u884C\u3002\u8BED\u6C14\u4E13\u4E1A\u3001\u514B\u5236\u3001\u53EF\u6267\u884C\u3002"
8806
+ } : {
8807
+ persona: "You are writing an executive at-a-glance summary for a security operations dashboard. Readers are CISO/leadership with little time.",
8808
+ focus: "One-line overall posture verdict + interpretation of the security score + the top 3 things to focus on right now (ordered by business impact). Do NOT list every finding.",
8809
+ format: "3-5 sentences, conclusion first. English. No Markdown headings; short line breaks ok. Professional, restrained, actionable tone."
8810
+ };
8811
+ case "html":
8812
+ return zh ? {
8813
+ persona: "\u4F60\u5728\u4E3A\u4E00\u4EFD\u5B8C\u6574\u7684 AWS \u5B89\u5168\u626B\u63CF\u62A5\u544A\u5199\u6267\u884C\u6458\u8981\u3002\u8BFB\u8005\u65E2\u6709\u7BA1\u7406\u5C42\u4E5F\u6709\u6280\u672F\u8D1F\u8D23\u4EBA\u3002",
8814
+ focus: "\u6574\u4F53\u98CE\u9669\u5168\u666F + Top \u98CE\u9669\u7684\u5171\u6027\u6839\u56E0\uFF08\u5982\u516C\u7F51\u66B4\u9732\u9762\u3001IAM \u8FC7\u6743\u3001\u52A0\u5BC6/\u65E5\u5FD7\u7F3A\u5931\u7B49\uFF09+ \u5206\u4F18\u5148\u7EA7(P0\u2192P2)\u7684\u4FEE\u590D\u8DEF\u7EBF\u5EFA\u8BAE\u3002\u70B9\u51FA\u7CFB\u7EDF\u6027\u95EE\u9898\u800C\u975E\u9010\u6761\u590D\u8FF0\u3002",
8815
+ format: "1 \u6BB5\u6982\u8FF0 + 3-5 \u6761\u8981\u70B9\u3002\u4E2D\u6587\u3002\u53EF\u7528\u8981\u70B9\u5217\u8868\u3002\u6280\u672F\u4E0E\u7BA1\u7406\u53CC\u89C6\u89D2\uFF0C\u7ED9\u51FA\u53EF\u843D\u5730\u7684\u4E0B\u4E00\u6B65\u3002"
8816
+ } : {
8817
+ persona: "You are writing the executive summary for a full AWS security scan report. Readers include both management and technical leads.",
8818
+ focus: "Overall risk landscape + common root causes behind the top risks (public exposure, over-privileged IAM, missing encryption/logging, etc.) + a prioritized (P0\u2192P2) remediation roadmap. Surface systemic issues rather than restating each finding.",
8819
+ format: "1 overview paragraph + 3-5 bullet points. English. Bullet list ok. Both technical and management lens, with concrete next steps."
8820
+ };
8821
+ case "hw_defense":
8822
+ return zh ? {
8823
+ persona: "\u4F60\u662F\u62A4\u7F51\u884C\u52A8(\u7F51\u7EDC\u5B89\u5168\u653B\u9632\u6F14\u7EC3)\u7684\u84DD\u961F\u6307\u6325\u3002\u8FD9\u662F\u4E00\u4EFD\u62A4\u7F51\u5907\u6218\u8BC4\u4F30\u62A5\u544A\u3002\u6CE8\u610F\uFF1AHW=\u62A4\u7F51\uFF0C\u4E0E\u534E\u4E3A\u65E0\u5173\u3002",
8824
+ focus: "\u4ECE\u653B\u51FB\u8005\u89C6\u89D2\u4E32\u8054 findings \u6210\u653B\u51FB\u94FE(kill-chain)\uFF1A\u54EA\u4E9B\u662F\u5916\u90E8\u53EF\u8FBE\u7684\u521D\u59CB\u7A81\u7834\u53E3\u3001\u6A2A\u5411\u79FB\u52A8/\u63D0\u6743\u8DEF\u5F84\u3001\u53EF\u80FD\u7684\u5F71\u54CD\u9762\uFF1B\u7136\u540E\u7ED9\u51FA\u5907\u6218\u6536\u655B\u6E05\u5355(\u5148\u5173\u4EC0\u4E48\u3001\u5148\u76EF\u4EC0\u4E48)\u3002\u5F3A\u8C03\u66B4\u9732\u9762\u6536\u655B\u4E0E\u76D1\u63A7\u52A0\u56FA\u3002",
8825
+ format: "\u5148\u4E00\u53E5\u7814\u5224\u7ED3\u8BBA(\u5F53\u524D\u66B4\u9732\u9762/\u88AB\u6253\u7A7F\u98CE\u9669)\uFF0C\u518D\u7ED9\u300E\u653B\u51FB\u94FE\u89C6\u89D2\u300F\u548C\u300E\u5907\u6218\u6574\u6539\u4F18\u5148\u7EA7\u300F\u4E24\u5757\u3002\u4E2D\u6587\u3002\u8BED\u6C14\u8D34\u62A4\u7F51\u5B9E\u6218\u8BED\u5883\uFF0C\u7D27\u8FEB\u4F46\u4E0D\u5938\u5F20\u3002"
8826
+ } : {
8827
+ persona: "You are the blue-team lead for an HW Defense exercise (cybersecurity attack-defense drill). This is a pre-exercise readiness report. Note: HW = HuWang/\u62A4\u7F51, unrelated to Huawei.",
8828
+ focus: "From an attacker's perspective, chain findings into a kill-chain: external initial footholds, lateral movement/privilege-escalation paths, likely blast radius; then a hardening checklist (what to close/watch first). Emphasize attack-surface reduction and monitoring.",
8829
+ format: "Lead with a one-line verdict (current exposure / breach risk), then two blocks: 'Attack-chain view' and 'Readiness remediation priorities'. English. Combat-readiness tone, urgent but not alarmist."
8830
+ };
8831
+ case "mlps3":
8832
+ return zh ? {
8833
+ persona: "\u4F60\u662F\u7B49\u4FDD\u6D4B\u8BC4\u987E\u95EE\u3002\u8FD9\u662F\u4E00\u4EFD\u7B49\u4FDD\u4E09\u7EA7(GB/T 22239-2019)\u5408\u89C4\u9884\u68C0\u62A5\u544A\u3002",
8834
+ focus: "\u4ECE\u5408\u89C4\u89C6\u89D2\u603B\u7ED3\uFF1A\u5F53\u524D\u5BF9\u7167\u7B49\u4FDD\u4E09\u7EA7\u63A7\u5236\u9879\u7684\u603B\u4F53\u8FBE\u6807\u60C5\u51B5\u3001\u54EA\u4E9B\u63A7\u5236\u57DF\u5B58\u5728\u660E\u663E\u4E0D\u8FBE\u6807\u9879(\u5982\u8BBF\u95EE\u63A7\u5236\u3001\u5B89\u5168\u5BA1\u8BA1\u3001\u5165\u4FB5\u9632\u8303\u3001\u6570\u636E\u5B8C\u6574\u6027/\u4FDD\u5BC6\u6027\u7B49)\u3001\u6574\u6539\u7D27\u8FEB\u5EA6\u4E0E\u8FC7\u4FDD\u5EFA\u8BAE\u3002\u628A\u6280\u672F finding \u6620\u5C04\u5230\u5408\u89C4\u8BED\u8A00\u3002",
8835
+ format: "1 \u6BB5\u5408\u89C4\u7ED3\u8BBA + \u6309\u63A7\u5236\u57DF\u5206\u7EC4\u7684\u4E0D\u8FBE\u6807\u8981\u70B9 + \u6574\u6539\u4F18\u5148\u7EA7\u3002\u4E2D\u6587\u3002\u5F15\u7528\u63A7\u5236\u57DF\u540D\u79F0\u3002\u8BED\u6C14\u4E25\u8C28\u3001\u9762\u5411\u6D4B\u8BC4\u3002"
8836
+ } : {
8837
+ persona: "You are an MLPS assessment advisor. This is an MLPS Level 3 (GB/T 22239-2019) compliance pre-check report.",
8838
+ focus: "Summarize from a compliance lens: overall conformance against MLPS L3 controls, which control domains have clear gaps (access control, security audit, intrusion prevention, data integrity/confidentiality, etc.), remediation urgency, and certification-readiness advice. Map technical findings to compliance language.",
8839
+ format: "1 compliance verdict paragraph + non-conformance points grouped by control domain + remediation priority. English. Reference control-domain names. Rigorous, assessment-oriented tone."
8840
+ };
8841
+ }
8842
+ }
8843
+ var TYPE_LABEL = {
8844
+ dashboard: { zh: "\u5B89\u5168\u8FD0\u8425\u4EEA\u8868\u76D8", en: "Security Operations Dashboard" },
8845
+ html: { zh: "AWS \u5B89\u5168\u626B\u63CF\u62A5\u544A", en: "AWS Security Scan Report" },
8846
+ hw_defense: { zh: "\u62A4\u7F51\u884C\u52A8\u8BC4\u4F30\u62A5\u544A", en: "HW Defense (\u62A4\u7F51) Readiness Report" },
8847
+ mlps3: { zh: "\u7B49\u4FDD\u4E09\u7EA7\u5408\u89C4\u9884\u68C0\u62A5\u544A", en: "MLPS Level 3 Compliance Pre-check" }
8848
+ };
8849
+ function buildAiSummaryPrompt(type, scan, lang) {
8850
+ const zh = lang === "zh";
8851
+ const p = getProfile(type, lang);
8852
+ const label = TYPE_LABEL[type][zh ? "zh" : "en"];
8853
+ const digest = buildFindingsDigest(scan, lang);
8854
+ if (zh) {
8855
+ return [
8856
+ `# \u4EFB\u52A1\uFF1A\u4E3A\u300C${label}\u300D\u751F\u6210\u9488\u5BF9\u6027\u7684 AI \u5B89\u5168\u603B\u7ED3`,
8857
+ ``,
8858
+ `## \u89D2\u8272`,
8859
+ p.persona,
8860
+ ``,
8861
+ `## \u603B\u7ED3\u91CD\u70B9`,
8862
+ p.focus,
8863
+ ``,
8864
+ `## \u8F93\u51FA\u683C\u5F0F`,
8865
+ p.format,
8866
+ ``,
8867
+ `## \u7EA6\u675F`,
8868
+ `- \u53EA\u4F9D\u636E\u4E0B\u9762\u7684\u626B\u63CF\u6570\u636E\uFF0C\u4E0D\u8981\u7F16\u9020\u4E0D\u5B58\u5728\u7684\u8D44\u6E90\u6216 finding\u3002`,
8869
+ `- \u4E0D\u8981\u9010\u6761\u590D\u8FF0\u6240\u6709 finding\uFF1B\u63D0\u70BC\u6A21\u5F0F\u4E0E\u4F18\u5148\u7EA7\u3002`,
8870
+ `- \u76F4\u63A5\u8F93\u51FA\u603B\u7ED3\u6B63\u6587\u672C\u8EAB\uFF08\u4E0D\u8981\u5E26\u300C\u4EE5\u4E0B\u662F\u603B\u7ED3\u300D\u4E4B\u7C7B\u7684\u524D\u8A00\uFF0C\u4E0D\u8981 code fence\uFF09\u3002`,
8871
+ `- \u8BED\u8A00\uFF1A\u4E2D\u6587\u3002`,
8872
+ ``,
8873
+ `## \u626B\u63CF\u6570\u636E\u6458\u8981`,
8874
+ digest,
8875
+ ``,
8876
+ `\u8BF7\u73B0\u5728\u8F93\u51FA\u603B\u7ED3\u6B63\u6587\u3002\u751F\u6210\u540E\uFF0C\u628A\u8FD9\u6BB5\u6587\u672C\u901A\u8FC7\u5BF9\u5E94\u62A5\u544A\u5DE5\u5177\u7684 ai_summary \u53C2\u6570\u56DE\u4F20\u5373\u53EF\u3002`
8877
+ ].join("\n");
8878
+ }
8879
+ return [
8880
+ `# Task: Produce a tailored AI security summary for the "${label}"`,
8881
+ ``,
8882
+ `## Role`,
8883
+ p.persona,
8884
+ ``,
8885
+ `## Summary focus`,
8886
+ p.focus,
8887
+ ``,
8888
+ `## Output format`,
8889
+ p.format,
8890
+ ``,
8891
+ `## Constraints`,
8892
+ `- Base it ONLY on the scan data below; do not invent resources or findings.`,
8893
+ `- Do not restate every finding; distill patterns and priorities.`,
8894
+ `- Output the summary body itself (no preamble like "Here is the summary", no code fences).`,
8895
+ `- Language: English.`,
8896
+ ``,
8897
+ `## Scan data digest`,
8898
+ digest,
8899
+ ``,
8900
+ `Now output the summary body. After generating it, pass the text back via the ai_summary parameter of the corresponding report tool.`
8901
+ ].join("\n");
8902
+ }
8903
+
8736
8904
  // src/tools/save-results.ts
8737
8905
  import { writeFileSync, readFileSync, mkdirSync, existsSync } from "fs";
8738
8906
  import { join } from "path";
@@ -8789,7 +8957,8 @@ function saveResults(scanResults, outputDir) {
8789
8957
  })),
8790
8958
  findings: scanResults.modules.flatMap(
8791
8959
  (m) => m.findings.map((f) => ({ ...f, module: m.module }))
8792
- )
8960
+ ),
8961
+ aiSummary: scanResults.aiSummary
8793
8962
  },
8794
8963
  history,
8795
8964
  meta: {
@@ -9405,11 +9574,13 @@ function createServer(defaultRegion) {
9405
9574
  "Generate a Markdown security report from scan results. Read-only. Does not modify any AWS resources.",
9406
9575
  {
9407
9576
  scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
9408
- lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9577
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)"),
9578
+ ai_summary: z.string().optional().describe("Optional pre-generated AI executive summary (Markdown/plain text). Rendered if present; omit to hide.")
9409
9579
  },
9410
- async ({ scan_results, lang }) => {
9580
+ async ({ scan_results, lang, ai_summary }) => {
9411
9581
  try {
9412
9582
  const parsed = JSON.parse(scan_results);
9583
+ if (ai_summary) parsed.aiSummary = ai_summary;
9413
9584
  const report = generateMarkdownReport(parsed, lang ?? "zh");
9414
9585
  return { content: [{ type: "text", text: report }] };
9415
9586
  } catch (err) {
@@ -9422,11 +9593,13 @@ function createServer(defaultRegion) {
9422
9593
  "Generate a GB/T 22239-2019 \u7B49\u4FDD\u4E09\u7EA7 compliance pre-check report from scan results. Best used with scan_group mlps3_precheck results. Read-only.",
9423
9594
  {
9424
9595
  scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all"),
9425
- lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9596
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)"),
9597
+ ai_summary: z.string().optional().describe("Optional pre-generated AI executive summary (Markdown/plain text). Rendered if present; omit to hide.")
9426
9598
  },
9427
- async ({ scan_results, lang }) => {
9599
+ async ({ scan_results, lang, ai_summary }) => {
9428
9600
  try {
9429
9601
  const parsed = JSON.parse(scan_results);
9602
+ if (ai_summary) parsed.aiSummary = ai_summary;
9430
9603
  const report = generateMlps3Report(parsed, lang ?? "zh");
9431
9604
  return { content: [{ type: "text", text: report }] };
9432
9605
  } catch (err) {
@@ -9440,11 +9613,13 @@ function createServer(defaultRegion) {
9440
9613
  {
9441
9614
  scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
9442
9615
  history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts"),
9443
- lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9616
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)"),
9617
+ ai_summary: z.string().optional().describe("Optional pre-generated AI executive summary (Markdown/plain text). Rendered if present; omit to hide.")
9444
9618
  },
9445
- async ({ scan_results, history, lang }) => {
9619
+ async ({ scan_results, history, lang, ai_summary }) => {
9446
9620
  try {
9447
9621
  const parsed = JSON.parse(scan_results);
9622
+ if (ai_summary) parsed.aiSummary = ai_summary;
9448
9623
  const historyData = history ? JSON.parse(history) : void 0;
9449
9624
  const report = generateHtmlReport(parsed, historyData, lang ?? "zh");
9450
9625
  return { content: [{ type: "text", text: report }] };
@@ -9459,11 +9634,13 @@ function createServer(defaultRegion) {
9459
9634
  {
9460
9635
  scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all"),
9461
9636
  history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts"),
9462
- lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9637
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)"),
9638
+ ai_summary: z.string().optional().describe("Optional pre-generated AI executive summary (Markdown/plain text). Rendered if present; omit to hide.")
9463
9639
  },
9464
- async ({ scan_results, history, lang }) => {
9640
+ async ({ scan_results, history, lang, ai_summary }) => {
9465
9641
  try {
9466
9642
  const parsed = JSON.parse(scan_results);
9643
+ if (ai_summary) parsed.aiSummary = ai_summary;
9467
9644
  const historyData = history ? JSON.parse(history) : void 0;
9468
9645
  const report = generateMlps3HtmlReport(parsed, historyData, lang ?? "zh");
9469
9646
  return { content: [{ type: "text", text: report }] };
@@ -9472,16 +9649,36 @@ function createServer(defaultRegion) {
9472
9649
  }
9473
9650
  }
9474
9651
  );
9652
+ server.tool(
9653
+ "get_ai_summary_prompt",
9654
+ "Return a report-type-tailored prompt (with a grounded findings digest) that the CALLING AI should run to produce an AI security summary. Then pass the generated text back via the `ai_summary` parameter of the matching report tool (or scan_and_report). The server performs no LLM calls. Use this to make each summary specific to the report type (dashboard / security scan / HW Defense \u62A4\u7F51 / MLPS3 \u7B49\u4FDD).",
9655
+ {
9656
+ report_type: z.enum(["dashboard", "html", "hw_defense", "mlps3"]).describe("Target report type the summary is for: dashboard (overview), html (AWS security scan report), hw_defense (\u62A4\u7F51 attack-defense drill), mlps3 (\u7B49\u4FDD\u4E09\u7EA7 compliance)"),
9657
+ scan_results: z.string().describe("JSON string of FullScanResult from scan_all / scan_group"),
9658
+ lang: z.enum(["zh", "en"]).optional().describe("Summary language (default: zh)")
9659
+ },
9660
+ async ({ report_type, scan_results, lang }) => {
9661
+ try {
9662
+ const parsed = JSON.parse(scan_results);
9663
+ const prompt = buildAiSummaryPrompt(report_type, parsed, lang ?? "zh");
9664
+ return { content: [{ type: "text", text: prompt }] };
9665
+ } catch (err) {
9666
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
9667
+ }
9668
+ }
9669
+ );
9475
9670
  server.tool(
9476
9671
  "generate_hw_defense_report",
9477
9672
  "Generate an HTML report organized by HW Defense (\u62A4\u7F51) SOP checklist categories. Save as .html file.",
9478
9673
  {
9479
9674
  scan_results: z.string().describe("JSON string of FullScanResult from scan_group hw_defense or scan_all"),
9480
- lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9675
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)"),
9676
+ ai_summary: z.string().optional().describe("Optional pre-generated AI executive summary (Markdown/plain text). Rendered if present; omit to hide.")
9481
9677
  },
9482
- async ({ scan_results, lang }) => {
9678
+ async ({ scan_results, lang, ai_summary }) => {
9483
9679
  try {
9484
9680
  const parsed = JSON.parse(scan_results);
9681
+ if (ai_summary) parsed.aiSummary = ai_summary;
9485
9682
  const report = generateHwDefenseHtmlReport(parsed, lang ?? "zh");
9486
9683
  return { content: [{ type: "text", text: report }] };
9487
9684
  } catch (err) {
@@ -9610,11 +9807,13 @@ function createServer(defaultRegion) {
9610
9807
  "Saves scan results to local disk or S3 for dashboard display. Does not modify any AWS resources.",
9611
9808
  {
9612
9809
  scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
9613
- output_dir: z.string().optional().describe("Output directory (default: ~/.aws-security)")
9810
+ output_dir: z.string().optional().describe("Output directory (default: ~/.aws-security)"),
9811
+ ai_summary: z.string().optional().describe("Optional pre-generated AI executive summary (from get_ai_summary_prompt with report_type=dashboard). Persisted into dashboard data and rendered on the Overview; omit to hide.")
9614
9812
  },
9615
- async ({ scan_results, output_dir }) => {
9813
+ async ({ scan_results, output_dir, ai_summary }) => {
9616
9814
  try {
9617
9815
  const parsed = JSON.parse(scan_results);
9816
+ if (ai_summary) parsed.aiSummary = ai_summary;
9618
9817
  const dataPath = saveResults(parsed, output_dir);
9619
9818
  return {
9620
9819
  content: [
@@ -9712,9 +9911,10 @@ Deploy this as a StackSet from your Management Account to all member accounts.`
9712
9911
  role_name: z.string().optional().describe("IAM role name for cross-account scanning"),
9713
9912
  account_ids: z.array(z.string()).optional().describe("Filter to specific account IDs"),
9714
9913
  reports: z.array(z.enum(["html", "hw_defense", "mlps3", "markdown", "all"])).optional().describe("Report types to generate (default: all)"),
9715
- lang: z.enum(["zh", "en"]).optional().describe("Language: zh or en (default: zh)")
9914
+ lang: z.enum(["zh", "en"]).optional().describe("Language: zh or en (default: zh)"),
9915
+ ai_summary: z.string().optional().describe("Optional pre-generated AI executive summary (Markdown/plain text). Rendered in reports + dashboard if present; omit to hide.")
9716
9916
  },
9717
- async ({ region, org_mode, role_name, account_ids, reports, lang }) => {
9917
+ async ({ region, org_mode, role_name, account_ids, reports, lang, ai_summary }) => {
9718
9918
  try {
9719
9919
  const r = region ?? defaultRegion;
9720
9920
  const l = lang ?? "zh";
@@ -9730,6 +9930,7 @@ Deploy this as a StackSet from your Management Account to all member accounts.`
9730
9930
  } else {
9731
9931
  result = await runAllScanners(allScanners, r);
9732
9932
  }
9933
+ if (ai_summary) result.aiSummary = ai_summary;
9733
9934
  const baseDir = join2(homedir2(), ".aws-security", "reports", (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
9734
9935
  mkdirSync2(baseDir, { recursive: true });
9735
9936
  const savedFiles = [];
@@ -9854,6 +10055,7 @@ async function startServer(defaultRegion) {
9854
10055
  }
9855
10056
  export {
9856
10057
  assumeRole,
10058
+ buildAiSummaryPrompt,
9857
10059
  buildRoleArn,
9858
10060
  calculateScore,
9859
10061
  createServer,
@@ -9861,6 +10063,7 @@ export {
9861
10063
  generateHwDefenseHtmlReport,
9862
10064
  generateMarkdownReport,
9863
10065
  generateMlps3HtmlReport,
10066
+ generateMlps3Report,
9864
10067
  getCurrentAccountId,
9865
10068
  listOrgAccounts,
9866
10069
  runAllScanners,