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.
- package/README.md +68 -2
- package/README.zh-CN.md +311 -0
- package/dashboard/dist/assets/{index-DsWFAp9v.js → index-BoNhDdVm.js} +2 -2
- package/dashboard/dist/assets/index-DY4x_jab.css +2 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/bin/aws-security-mcp.js +239 -38
- package/dist/bin/aws-security-mcp.js.map +1 -1
- package/dist/src/index.d.ts +15 -1
- package/dist/src/index.js +241 -38
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/index-BXloWmhE.css +0 -2
package/dist/src/index.d.ts
CHANGED
|
@@ -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.
|
|
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/
|
|
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
|
|
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
|
|
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,
|