aws-security-mcp 0.7.1 → 0.7.2
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/dashboard/dist/assets/index-BXloWmhE.css +2 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/bin/aws-security-mcp.js +459 -7
- package/dist/bin/aws-security-mcp.js.map +1 -1
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +460 -7
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/index-UN8P_PO6.css +0 -2
- /package/dashboard/dist/assets/{index-AKJ_-GfD.js → index-DsWFAp9v.js} +0 -0
package/dist/src/index.d.ts
CHANGED
|
@@ -127,10 +127,12 @@ declare function generateMarkdownReport(scanResults: FullScanResult, lang?: Lang
|
|
|
127
127
|
declare function generateHtmlReport(scanResults: FullScanResult, history?: DashboardHistoryEntry[], lang?: Lang): string;
|
|
128
128
|
declare function generateMlps3HtmlReport(scanResults: FullScanResult, history?: DashboardHistoryEntry[], lang?: Lang): string;
|
|
129
129
|
|
|
130
|
+
declare function generateHwDefenseHtmlReport(scanResults: FullScanResult, lang?: Lang): string;
|
|
131
|
+
|
|
130
132
|
declare function calculateScore(summary: FullScanResult["summary"]): number;
|
|
131
133
|
declare function saveResults(scanResults: FullScanResult, outputDir?: string): string;
|
|
132
134
|
|
|
133
135
|
declare function createServer(defaultRegion: string): McpServer;
|
|
134
136
|
declare function startServer(defaultRegion: string): Promise<void>;
|
|
135
137
|
|
|
136
|
-
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, generateMarkdownReport, generateMlps3HtmlReport, getCurrentAccountId, listOrgAccounts, runAllScanners, runMultiAccountScanners, saveResults, startServer };
|
|
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 };
|
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.2";
|
|
8
8
|
|
|
9
9
|
// src/utils/aws-client.ts
|
|
10
10
|
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
|
|
@@ -3784,6 +3784,39 @@ var zhI18n = {
|
|
|
3784
3784
|
action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
|
|
3785
3785
|
}
|
|
3786
3786
|
},
|
|
3787
|
+
// HW Defense HTML Report
|
|
3788
|
+
hwReportTitle: "\u62A4\u7F51\u84DD\u961F\u5B89\u5168\u8BC4\u4F30\u62A5\u544A",
|
|
3789
|
+
hwAutoCheck: "\u81EA\u52A8\u5316\u68C0\u67E5",
|
|
3790
|
+
hwManualCheck: "\u4EBA\u5DE5\u786E\u8BA4\u4E8B\u9879",
|
|
3791
|
+
hwNoAutoCheck: "\u6B64\u9879\u65E0\u81EA\u52A8\u5316\u68C0\u67E5",
|
|
3792
|
+
hwClean: "\u672A\u53D1\u73B0\u95EE\u9898",
|
|
3793
|
+
hwTotalFindings: "\u53D1\u73B0\u603B\u6570",
|
|
3794
|
+
hwSectionsChecked: "\u68C0\u67E5\u5206\u7C7B",
|
|
3795
|
+
hwAutoVerified: "\u81EA\u52A8\u9A8C\u8BC1",
|
|
3796
|
+
hwManualPending: "\u4EBA\u5DE5\u5F85\u786E\u8BA4",
|
|
3797
|
+
hwManualCount: (n) => `${n} \u9879\u4EBA\u5DE5\u786E\u8BA4`,
|
|
3798
|
+
hwAffectedResources: (n) => `\u67E5\u770B\u53D7\u5F71\u54CD\u8D44\u6E90 (${n})`,
|
|
3799
|
+
hwRemediation: "\u4FEE\u590D\u5EFA\u8BAE",
|
|
3800
|
+
hwSectionNames: {
|
|
3801
|
+
attack_surface: { name: "\u653B\u51FB\u9762\u6536\u655B", icon: "\u{1F3AF}" },
|
|
3802
|
+
vulnerability_patch: { name: "\u6F0F\u6D1E\u4E0E\u8865\u4E01\u7BA1\u7406", icon: "\u{1FA79}" },
|
|
3803
|
+
identity_credential: { name: "\u8EAB\u4EFD\u4E0E\u51ED\u8BC1\u5B89\u5168", icon: "\u{1F511}" },
|
|
3804
|
+
transport_security: { name: "\u4F20\u8F93\u4E0E\u5B9E\u4F8B\u5B89\u5168", icon: "\u{1F512}" },
|
|
3805
|
+
security_services: { name: "\u5B89\u5168\u670D\u52A1\u72B6\u6001", icon: "\u{1F6E1}\uFE0F" },
|
|
3806
|
+
emergency_response: { name: "\u5E94\u6025\u54CD\u5E94\u51C6\u5907", icon: "\u{1F6A8}" },
|
|
3807
|
+
environment_control: { name: "\u73AF\u5883\u5904\u7F6E", icon: "\u{1F3D7}\uFE0F" },
|
|
3808
|
+
post_review: { name: "\u62A4\u7F51\u540E\u4F18\u5316", icon: "\u{1F4CA}" }
|
|
3809
|
+
},
|
|
3810
|
+
hwManualItems: {
|
|
3811
|
+
attack_surface: ["\u7ED8\u5236\u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE\uFF0C\u6807\u6CE8\u6240\u6709\u4E92\u8054\u7F51/DX\u4E13\u7EBF\u51FA\u5165\u7AD9\u8DEF\u5F84"],
|
|
3812
|
+
vulnerability_patch: ["\u8054\u7CFB\u5B89\u5168\u5382\u5546\u8FDB\u884C\u6A21\u62DF\u653B\u51FB\u6F14\u7EC3\uFF08\u6E17\u900F\u6D4B\u8BD5\uFF09", "\u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09"],
|
|
3813
|
+
identity_credential: ["\u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA", "AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929", "\u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528", "S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801"],
|
|
3814
|
+
transport_security: ["\u786E\u8BA4\u6240\u6709\u5BF9\u5916\u670D\u52A1\u4F7F\u7528 TLS 1.2+", "\u68C0\u67E5\u5185\u90E8\u670D\u52A1\u95F4\u901A\u4FE1\u662F\u5426\u52A0\u5BC6"],
|
|
3815
|
+
security_services: ["\u786E\u8BA4 Security Hub \u5DF2\u5F00\u542F\u5E76\u914D\u7F6E\u6807\u51C6", "\u786E\u8BA4 GuardDuty \u5DF2\u5728\u6240\u6709\u533A\u57DF\u5F00\u542F", "\u786E\u8BA4 CloudTrail \u591A\u533A\u57DF\u65E5\u5FD7\u8BB0\u5F55\u5DF2\u5F00\u542F", "\u786E\u8BA4 Config Rules \u5DF2\u914D\u7F6E"],
|
|
3816
|
+
emergency_response: ["\u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09", "\u5236\u5B9A\u5B9E\u4F8B\u9694\u79BB SOP\uFF1A\u544A\u8B66 \u2192 \u6392\u67E5 \u2192 \u5C01\u9501\u653B\u51FBIP \u2192 \u7F51\u7EDC\u9694\u79BB \u2192 \u5B89\u5168\u5904\u7F6E \u2192 \u8BB0\u5F55\u653B\u51FB\u9879", "\u7EC4\u5EFA 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F", "\u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09", "\u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09"],
|
|
3817
|
+
environment_control: ["\u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED", "\u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF", "\u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563"],
|
|
3818
|
+
post_review: ["\u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D", "\u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B", "\u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669"]
|
|
3819
|
+
},
|
|
3787
3820
|
// HW Checklist (full composite)
|
|
3788
3821
|
hwChecklist: `
|
|
3789
3822
|
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
@@ -4060,6 +4093,39 @@ var enI18n = {
|
|
|
4060
4093
|
action: "Install SSM Agent and configure Patch Manager"
|
|
4061
4094
|
}
|
|
4062
4095
|
},
|
|
4096
|
+
// HW Defense HTML Report
|
|
4097
|
+
hwReportTitle: "HW Defense Security Assessment Report",
|
|
4098
|
+
hwAutoCheck: "Automated Checks",
|
|
4099
|
+
hwManualCheck: "Manual Verification Items",
|
|
4100
|
+
hwNoAutoCheck: "No automated checks for this section",
|
|
4101
|
+
hwClean: "No issues found",
|
|
4102
|
+
hwTotalFindings: "Total Findings",
|
|
4103
|
+
hwSectionsChecked: "Sections Checked",
|
|
4104
|
+
hwAutoVerified: "Auto-Verified",
|
|
4105
|
+
hwManualPending: "Manual Pending",
|
|
4106
|
+
hwManualCount: (n) => `${n} manual item${n === 1 ? "" : "s"}`,
|
|
4107
|
+
hwAffectedResources: (n) => `View affected resources (${n})`,
|
|
4108
|
+
hwRemediation: "Remediation",
|
|
4109
|
+
hwSectionNames: {
|
|
4110
|
+
attack_surface: { name: "Attack Surface Reduction", icon: "\u{1F3AF}" },
|
|
4111
|
+
vulnerability_patch: { name: "Vulnerability & Patch Management", icon: "\u{1FA79}" },
|
|
4112
|
+
identity_credential: { name: "Identity & Credential Security", icon: "\u{1F511}" },
|
|
4113
|
+
transport_security: { name: "Transport & Instance Security", icon: "\u{1F512}" },
|
|
4114
|
+
security_services: { name: "Security Service Status", icon: "\u{1F6E1}\uFE0F" },
|
|
4115
|
+
emergency_response: { name: "Emergency Response Readiness", icon: "\u{1F6A8}" },
|
|
4116
|
+
environment_control: { name: "Environment Control", icon: "\u{1F3D7}\uFE0F" },
|
|
4117
|
+
post_review: { name: "Post-Drill Optimization", icon: "\u{1F4CA}" }
|
|
4118
|
+
},
|
|
4119
|
+
hwManualItems: {
|
|
4120
|
+
attack_surface: ["Draw ingress/egress path architecture diagram, mark all Internet/DX dedicated line paths"],
|
|
4121
|
+
vulnerability_patch: ["Contact security vendors for simulated attack drills (penetration testing)", "Monitor AWS security advisories (known vulnerabilities and patches)"],
|
|
4122
|
+
identity_credential: ["All IAM users must have MFA enabled", "Access key rotation cycle \u2264 90 days", "Avoid shared account usage", "No plaintext passwords in S3/Lambda/application code"],
|
|
4123
|
+
transport_security: ["Verify all external-facing services use TLS 1.2+", "Check internal service-to-service communication encryption"],
|
|
4124
|
+
security_services: ["Verify Security Hub is enabled with standards configured", "Verify GuardDuty is enabled in all regions", "Verify CloudTrail multi-region logging is enabled", "Verify Config Rules are configured"],
|
|
4125
|
+
emergency_response: ["Prepare dedicated isolation security groups (no Inbound/Outbound rules)", "Establish instance isolation SOP: Alert \u2192 Investigate \u2192 Block attacker IP \u2192 Network isolation \u2192 Security response \u2192 Log attack details", "Form 7\xD724 monitoring rapid response team", "Create dedicated communication channels for the drill period (Teams/Slack/Chime)", "Establish WAR-ROOM connection with AWS TAM (Enterprise Support customers)"],
|
|
4126
|
+
environment_control: ["Shut down non-critical systems during the drill period", "Shut down test/dev environments or maintain same security baseline as production", "Confirm which environments can be emergency-stopped to prevent attack propagation"],
|
|
4127
|
+
post_review: ["Address and remediate each item from the attack report", "Establish periodic security maintenance processes with the security team", "Continuously fill security risk gaps"]
|
|
4128
|
+
},
|
|
4063
4129
|
// HW Checklist (full composite)
|
|
4064
4130
|
hwChecklist: `
|
|
4065
4131
|
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
@@ -8234,6 +8300,374 @@ ${naNote}
|
|
|
8234
8300
|
</html>`;
|
|
8235
8301
|
}
|
|
8236
8302
|
|
|
8303
|
+
// src/tools/hw-report.ts
|
|
8304
|
+
function esc2(s) {
|
|
8305
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
8306
|
+
}
|
|
8307
|
+
function safeUrl2(url) {
|
|
8308
|
+
try {
|
|
8309
|
+
const u = new URL(url);
|
|
8310
|
+
if (u.protocol === "https:" || u.protocol === "http:") return url;
|
|
8311
|
+
return null;
|
|
8312
|
+
} catch {
|
|
8313
|
+
return null;
|
|
8314
|
+
}
|
|
8315
|
+
}
|
|
8316
|
+
function escWithLinks2(s) {
|
|
8317
|
+
const parts = s.split(/(https?:\/\/\S+)/);
|
|
8318
|
+
return parts.map((part, i) => {
|
|
8319
|
+
if (i % 2 === 1) {
|
|
8320
|
+
const safe = safeUrl2(part);
|
|
8321
|
+
if (safe) {
|
|
8322
|
+
return `<a href="${esc2(safe)}" style="color:#60a5fa" target="_blank" rel="noopener">${esc2(part)}</a>`;
|
|
8323
|
+
}
|
|
8324
|
+
return esc2(part);
|
|
8325
|
+
}
|
|
8326
|
+
return esc2(part);
|
|
8327
|
+
}).join("");
|
|
8328
|
+
}
|
|
8329
|
+
var SEVERITY_ORDER3 = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
|
|
8330
|
+
var HW_SECTIONS = [
|
|
8331
|
+
{
|
|
8332
|
+
id: "attack_surface",
|
|
8333
|
+
autoModules: ["network_reachability", "dns_dangling", "public_access_verify", "waf_coverage"],
|
|
8334
|
+
shKeywords: ["network", "public", "exposure", "port", "waf", "firewall", "vpc", "securitygroup"]
|
|
8335
|
+
},
|
|
8336
|
+
{
|
|
8337
|
+
id: "vulnerability_patch",
|
|
8338
|
+
autoModules: ["patch_compliance_findings"],
|
|
8339
|
+
shKeywords: ["vulnerability", "patch", "cve", "inspector", "software"]
|
|
8340
|
+
},
|
|
8341
|
+
{
|
|
8342
|
+
id: "identity_credential",
|
|
8343
|
+
autoModules: ["iam_privilege_escalation", "secret_exposure"],
|
|
8344
|
+
shKeywords: ["iam", "access", "privilege", "credential", "password", "mfa", "key rotation"]
|
|
8345
|
+
},
|
|
8346
|
+
{
|
|
8347
|
+
id: "transport_security",
|
|
8348
|
+
autoModules: ["ssl_certificate", "imdsv2_enforcement"],
|
|
8349
|
+
shKeywords: ["ssl", "tls", "certificate", "imds", "metadata"]
|
|
8350
|
+
},
|
|
8351
|
+
{
|
|
8352
|
+
id: "security_services",
|
|
8353
|
+
autoModules: ["service_detection"],
|
|
8354
|
+
shKeywords: []
|
|
8355
|
+
},
|
|
8356
|
+
{
|
|
8357
|
+
id: "emergency_response",
|
|
8358
|
+
autoModules: [],
|
|
8359
|
+
shKeywords: []
|
|
8360
|
+
},
|
|
8361
|
+
{
|
|
8362
|
+
id: "environment_control",
|
|
8363
|
+
autoModules: [],
|
|
8364
|
+
shKeywords: []
|
|
8365
|
+
},
|
|
8366
|
+
{
|
|
8367
|
+
id: "post_review",
|
|
8368
|
+
autoModules: [],
|
|
8369
|
+
shKeywords: []
|
|
8370
|
+
}
|
|
8371
|
+
];
|
|
8372
|
+
function hwCss() {
|
|
8373
|
+
return `
|
|
8374
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
8375
|
+
body{background:#0f172a;color:#f8fafc;font-family:Inter,system-ui,-apple-system,sans-serif;line-height:1.6;font-size:14px}
|
|
8376
|
+
.container{max-width:900px;margin:0 auto;padding:40px 24px}
|
|
8377
|
+
header{text-align:center;margin-bottom:40px;border-bottom:1px solid #334155;padding-bottom:24px}
|
|
8378
|
+
header h1{font-size:28px;font-weight:700;margin-bottom:8px;letter-spacing:-0.5px}
|
|
8379
|
+
.meta{color:#94a3b8;font-size:13px}
|
|
8380
|
+
h2{font-size:20px;font-weight:600;margin:32px 0 16px;padding-bottom:8px;border-bottom:1px solid #334155}
|
|
8381
|
+
h3{font-size:16px;font-weight:600;margin:16px 0 8px}
|
|
8382
|
+
h4{font-size:14px;font-weight:600;margin:12px 0 4px}
|
|
8383
|
+
.summary-cards{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:32px;justify-content:center}
|
|
8384
|
+
.summary-card{background:#1e293b;border:1px solid #334155;border-radius:8px;padding:16px 20px;text-align:center;min-width:100px;flex:1}
|
|
8385
|
+
.summary-card .stat-count{font-size:28px;font-weight:700}
|
|
8386
|
+
.summary-card .stat-label{font-size:12px;color:#94a3b8;margin-top:2px}
|
|
8387
|
+
.badge{display:inline-block;padding:2px 10px;border-radius:4px;font-size:11px;font-weight:700;letter-spacing:0.5px;color:#fff}
|
|
8388
|
+
.badge-critical{background:#ef4444}
|
|
8389
|
+
.badge-high{background:#f97316}
|
|
8390
|
+
.badge-medium{background:#eab308;color:#1e293b}
|
|
8391
|
+
.badge-low{background:#22c55e;color:#1e293b}
|
|
8392
|
+
.hw-section{background:#1e293b;border:1px solid #334155;border-radius:8px;margin-bottom:16px;overflow:hidden}
|
|
8393
|
+
.hw-section>summary{cursor:pointer;padding:16px 20px;display:flex;align-items:center;gap:12px;list-style:none;font-size:16px;font-weight:600;user-select:none;flex-wrap:wrap}
|
|
8394
|
+
.hw-section>summary::-webkit-details-marker{display:none}
|
|
8395
|
+
.hw-section>summary::marker{content:""}
|
|
8396
|
+
.hw-section>summary::after{content:"\\25B6";font-size:12px;color:#64748b;flex-shrink:0;transition:transform 0.2s;margin-left:auto}
|
|
8397
|
+
.hw-section[open]>summary::after{transform:rotate(90deg)}
|
|
8398
|
+
.hw-section[open]>summary{border-bottom:1px solid #334155}
|
|
8399
|
+
.hw-section-body{padding:16px 20px}
|
|
8400
|
+
.hw-section-icon{font-size:20px}
|
|
8401
|
+
.hw-section-title{flex:1}
|
|
8402
|
+
.hw-section-stats{display:inline-flex;gap:8px;font-size:12px;flex-wrap:wrap}
|
|
8403
|
+
.hw-section-stats .badge{font-size:10px;padding:1px 8px}
|
|
8404
|
+
.hw-auto-section{margin-bottom:16px}
|
|
8405
|
+
.hw-auto-section h4{color:#60a5fa;margin-bottom:8px}
|
|
8406
|
+
.hw-manual-section h4{color:#fbbf24;margin-bottom:8px}
|
|
8407
|
+
.hw-clean{color:#22c55e;font-size:14px;padding:8px 12px;background:rgba(34,197,94,0.1);border-radius:6px}
|
|
8408
|
+
.hw-no-auto{color:#94a3b8;font-size:14px;padding:8px 12px;background:rgba(148,163,184,0.08);border-radius:6px}
|
|
8409
|
+
.hw-manual-item{display:flex;align-items:flex-start;gap:8px;padding:6px 12px;margin-bottom:4px;font-size:14px;color:#cbd5e1;border-radius:4px;background:rgba(148,163,184,0.06)}
|
|
8410
|
+
.hw-manual-checkbox{color:#fbbf24;font-size:16px;flex-shrink:0}
|
|
8411
|
+
.finding-card{display:flex;align-items:center;gap:8px;padding:8px 12px;margin-bottom:4px;border-radius:6px;border-left:4px solid #334155;background:rgba(30,41,59,0.5);flex-wrap:wrap}
|
|
8412
|
+
.sev-critical{border-left-color:#ef4444}
|
|
8413
|
+
.sev-high{border-left-color:#f97316}
|
|
8414
|
+
.sev-medium{border-left-color:#eab308}
|
|
8415
|
+
.sev-low{border-left-color:#22c55e}
|
|
8416
|
+
.finding-title-text{font-weight:600;font-size:13px;flex:1;min-width:200px}
|
|
8417
|
+
.finding-resource{color:#94a3b8;font-size:12px;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
8418
|
+
.finding-card>details{width:100%;margin-top:4px}
|
|
8419
|
+
.finding-card>details>summary{cursor:pointer;font-size:12px;color:#60a5fa;user-select:none}
|
|
8420
|
+
.finding-card-body{padding:8px 0}
|
|
8421
|
+
.finding-card-body p{color:#cbd5e1;font-size:13px;margin-bottom:4px}
|
|
8422
|
+
.finding-card-body ol{padding-left:20px}
|
|
8423
|
+
.finding-card-body li{color:#cbd5e1;font-size:13px;margin-bottom:2px}
|
|
8424
|
+
.hw-finding-group{background:#1e293b;border:1px solid #334155;border-radius:8px;padding:12px 16px;margin-bottom:8px}
|
|
8425
|
+
.hw-finding-header{display:flex;align-items:center;gap:8px}
|
|
8426
|
+
.hw-finding-title{color:#e2e8f0;font-size:14px;flex:1}
|
|
8427
|
+
.hw-finding-count{color:#94a3b8;font-size:13px;font-weight:600;background:#334155;padding:2px 8px;border-radius:4px}
|
|
8428
|
+
.hw-finding-resources{padding:8px 0}
|
|
8429
|
+
.hw-resource-item{font-size:12px;color:#94a3b8;padding:2px 0;font-family:monospace}
|
|
8430
|
+
.hw-finding-remediation{border-top:1px solid #334155;margin-top:8px;padding-top:8px}
|
|
8431
|
+
.hw-finding-remediation ol{margin:4px 0;padding-left:20px;font-size:13px;color:#cbd5e1}
|
|
8432
|
+
footer{margin-top:48px;padding-top:24px;border-top:1px solid #334155;text-align:center}
|
|
8433
|
+
footer p{color:#64748b;font-size:12px;margin-bottom:4px}
|
|
8434
|
+
@media print{
|
|
8435
|
+
body{background:#fff;color:#1e293b;-webkit-print-color-adjust:exact;print-color-adjust:exact}
|
|
8436
|
+
.container{max-width:100%;padding:20px}
|
|
8437
|
+
.hw-section,.summary-card,.finding-card{background:#fff;border:1px solid #e2e8f0}
|
|
8438
|
+
.badge{border:1px solid}
|
|
8439
|
+
header{border-bottom-color:#e2e8f0}
|
|
8440
|
+
h2{border-bottom-color:#e2e8f0}
|
|
8441
|
+
footer{border-top-color:#e2e8f0}
|
|
8442
|
+
.summary-card .stat-label{color:#64748b}
|
|
8443
|
+
.finding-title-text{color:#1e293b}
|
|
8444
|
+
.finding-resource{color:#64748b}
|
|
8445
|
+
.finding-card-body p,.finding-card-body li{color:#475569}
|
|
8446
|
+
.hw-section[open]>summary{border-bottom-color:#e2e8f0}
|
|
8447
|
+
.hw-manual-item{color:#475569}
|
|
8448
|
+
details{display:block}
|
|
8449
|
+
details>summary{display:block}
|
|
8450
|
+
details>:not(summary){display:block !important}
|
|
8451
|
+
}
|
|
8452
|
+
`;
|
|
8453
|
+
}
|
|
8454
|
+
function generateHwDefenseHtmlReport(scanResults, lang) {
|
|
8455
|
+
const t = getI18n(lang ?? "zh");
|
|
8456
|
+
const htmlLang = (lang ?? "zh") === "zh" ? "zh-CN" : "en";
|
|
8457
|
+
const { accountId, region, scanStart } = scanResults;
|
|
8458
|
+
const date = scanStart.split("T")[0];
|
|
8459
|
+
const scanTime = scanStart.replace("T", " ").replace(/\.\d+Z$/, " UTC");
|
|
8460
|
+
const moduleMap = /* @__PURE__ */ new Map();
|
|
8461
|
+
for (const mod of scanResults.modules) {
|
|
8462
|
+
const findings = mod.findings.map((f) => ({ ...f, module: f.module ?? mod.module }));
|
|
8463
|
+
moduleMap.set(mod.module, findings);
|
|
8464
|
+
}
|
|
8465
|
+
const assignedShFindings = /* @__PURE__ */ new Set();
|
|
8466
|
+
function shFindingKey(f) {
|
|
8467
|
+
return `${f.title}|${f.resourceId}|${f.resourceArn}`;
|
|
8468
|
+
}
|
|
8469
|
+
const sectionResults = [];
|
|
8470
|
+
for (const section of HW_SECTIONS) {
|
|
8471
|
+
const findings = [];
|
|
8472
|
+
for (const mod of section.autoModules) {
|
|
8473
|
+
if (mod === "security_hub_findings") continue;
|
|
8474
|
+
const modFindings = moduleMap.get(mod) ?? [];
|
|
8475
|
+
findings.push(...modFindings);
|
|
8476
|
+
}
|
|
8477
|
+
if (section.shKeywords.length > 0) {
|
|
8478
|
+
const shFindings = moduleMap.get("security_hub_findings") ?? [];
|
|
8479
|
+
for (const f of shFindings) {
|
|
8480
|
+
const key = shFindingKey(f);
|
|
8481
|
+
if (assignedShFindings.has(key)) continue;
|
|
8482
|
+
const searchText = `${f.title} ${f.description} ${f.impact}`.toLowerCase();
|
|
8483
|
+
if (section.shKeywords.some((kw) => searchText.includes(kw))) {
|
|
8484
|
+
findings.push(f);
|
|
8485
|
+
assignedShFindings.add(key);
|
|
8486
|
+
}
|
|
8487
|
+
}
|
|
8488
|
+
}
|
|
8489
|
+
const hasAutoModules = section.autoModules.length > 0 || section.shKeywords.length > 0;
|
|
8490
|
+
const hasAutoResults = hasAutoModules && (section.autoModules.some((m) => moduleMap.has(m)) || section.shKeywords.length > 0 && moduleMap.has("security_hub_findings"));
|
|
8491
|
+
const manualItems = t.hwManualItems[section.id] ?? [];
|
|
8492
|
+
sectionResults.push({
|
|
8493
|
+
id: section.id,
|
|
8494
|
+
findings,
|
|
8495
|
+
manualItems,
|
|
8496
|
+
hasAutoModules,
|
|
8497
|
+
hasAutoResults
|
|
8498
|
+
});
|
|
8499
|
+
}
|
|
8500
|
+
const totalFindings = sectionResults.reduce((sum, s) => sum + s.findings.length, 0);
|
|
8501
|
+
const sectionsChecked = sectionResults.filter((s) => s.hasAutoResults || s.manualItems.length > 0).length;
|
|
8502
|
+
const autoVerified = sectionResults.filter((s) => s.hasAutoResults).length;
|
|
8503
|
+
const totalManualItems = sectionResults.reduce((sum, s) => sum + s.manualItems.length, 0);
|
|
8504
|
+
function groupFindings(findings) {
|
|
8505
|
+
const groups = /* @__PURE__ */ new Map();
|
|
8506
|
+
const groupTitles = /* @__PURE__ */ new Map();
|
|
8507
|
+
for (const f of findings) {
|
|
8508
|
+
const cveMatch = f.title.match(/CVE-\d{4}-\d+/i);
|
|
8509
|
+
if (cveMatch) {
|
|
8510
|
+
const key2 = `cve:${cveMatch[0].toUpperCase()}`;
|
|
8511
|
+
if (!groups.has(key2)) {
|
|
8512
|
+
groups.set(key2, []);
|
|
8513
|
+
groupTitles.set(key2, f.title);
|
|
8514
|
+
}
|
|
8515
|
+
groups.get(key2).push(f);
|
|
8516
|
+
continue;
|
|
8517
|
+
}
|
|
8518
|
+
const controlMatch = f.title.match(/[A-Z]+\.\d+/);
|
|
8519
|
+
if (controlMatch) {
|
|
8520
|
+
const key2 = `ctrl:${controlMatch[0]}`;
|
|
8521
|
+
if (!groups.has(key2)) {
|
|
8522
|
+
groups.set(key2, []);
|
|
8523
|
+
groupTitles.set(key2, f.title);
|
|
8524
|
+
}
|
|
8525
|
+
groups.get(key2).push(f);
|
|
8526
|
+
continue;
|
|
8527
|
+
}
|
|
8528
|
+
const key = `title:${f.title}`;
|
|
8529
|
+
if (!groups.has(key)) {
|
|
8530
|
+
groups.set(key, []);
|
|
8531
|
+
groupTitles.set(key, f.title);
|
|
8532
|
+
}
|
|
8533
|
+
groups.get(key).push(f);
|
|
8534
|
+
}
|
|
8535
|
+
const result = [];
|
|
8536
|
+
for (const [key, gFindings] of groups) {
|
|
8537
|
+
let highestSeverity = "LOW";
|
|
8538
|
+
for (const f of gFindings) {
|
|
8539
|
+
if (SEVERITY_ORDER3.indexOf(f.severity) < SEVERITY_ORDER3.indexOf(highestSeverity)) {
|
|
8540
|
+
highestSeverity = f.severity;
|
|
8541
|
+
}
|
|
8542
|
+
}
|
|
8543
|
+
result.push({ title: groupTitles.get(key), findings: gFindings, highestSeverity });
|
|
8544
|
+
}
|
|
8545
|
+
return result;
|
|
8546
|
+
}
|
|
8547
|
+
const renderGroup = (group) => {
|
|
8548
|
+
const sev = group.highestSeverity.toLowerCase();
|
|
8549
|
+
const count = group.findings.length;
|
|
8550
|
+
const first = group.findings[0];
|
|
8551
|
+
const resourceItems = group.findings.map((f) => `<div class="hw-resource-item">${esc2(f.resourceId)} — ${esc2(f.resourceArn)}</div>`).join("\n");
|
|
8552
|
+
const remediationSteps = first.remediationSteps.map((s) => `<li>${escWithLinks2(s)}</li>`).join("");
|
|
8553
|
+
return `<div class="hw-finding-group">
|
|
8554
|
+
<div class="hw-finding-header">
|
|
8555
|
+
<span class="badge badge-${esc2(sev)}">${esc2(group.highestSeverity)}</span>
|
|
8556
|
+
<span class="hw-finding-title">${esc2(group.title)}</span>
|
|
8557
|
+
<span class="hw-finding-count">×${count}</span>
|
|
8558
|
+
</div>
|
|
8559
|
+
<details>
|
|
8560
|
+
<summary>${t.hwAffectedResources(count)}</summary>
|
|
8561
|
+
<div class="hw-finding-resources">
|
|
8562
|
+
${resourceItems}
|
|
8563
|
+
</div>
|
|
8564
|
+
<div class="hw-finding-remediation">
|
|
8565
|
+
<strong>${esc2(t.hwRemediation)}:</strong>
|
|
8566
|
+
<ol>${remediationSteps}</ol>
|
|
8567
|
+
</div>
|
|
8568
|
+
</details>
|
|
8569
|
+
</div>`;
|
|
8570
|
+
};
|
|
8571
|
+
const sectionsHtml = sectionResults.map((section) => {
|
|
8572
|
+
const meta = t.hwSectionNames[section.id];
|
|
8573
|
+
if (!meta) return "";
|
|
8574
|
+
const sectionName = meta.name;
|
|
8575
|
+
const sectionIcon = meta.icon ?? "";
|
|
8576
|
+
const statBadges = [];
|
|
8577
|
+
if (section.findings.length > 0) {
|
|
8578
|
+
const sevCounts = {};
|
|
8579
|
+
for (const f of section.findings) {
|
|
8580
|
+
sevCounts[f.severity] = (sevCounts[f.severity] ?? 0) + 1;
|
|
8581
|
+
}
|
|
8582
|
+
for (const sev of SEVERITY_ORDER3) {
|
|
8583
|
+
if (sevCounts[sev]) {
|
|
8584
|
+
statBadges.push(
|
|
8585
|
+
`<span class="badge badge-${sev.toLowerCase()}">${sevCounts[sev]} ${sev}</span>`
|
|
8586
|
+
);
|
|
8587
|
+
}
|
|
8588
|
+
}
|
|
8589
|
+
} else if (section.hasAutoResults) {
|
|
8590
|
+
statBadges.push(`<span style="color:#22c55e;font-size:12px">✓ ${esc2(t.hwClean)}</span>`);
|
|
8591
|
+
}
|
|
8592
|
+
if (section.manualItems.length > 0) {
|
|
8593
|
+
statBadges.push(`<span style="color:#fbbf24;font-size:12px">☐ ${esc2(t.hwManualCount(section.manualItems.length))}</span>`);
|
|
8594
|
+
}
|
|
8595
|
+
let autoHtml;
|
|
8596
|
+
if (!section.hasAutoModules) {
|
|
8597
|
+
autoHtml = `<div class="hw-no-auto">⚪ ${esc2(t.hwNoAutoCheck)}</div>`;
|
|
8598
|
+
} else if (!section.hasAutoResults) {
|
|
8599
|
+
autoHtml = `<div class="hw-no-auto">⚪ ${esc2(t.hwNoAutoCheck)}</div>`;
|
|
8600
|
+
} else if (section.findings.length === 0) {
|
|
8601
|
+
autoHtml = `<div class="hw-clean">✔ ${esc2(t.hwClean)}</div>`;
|
|
8602
|
+
} else {
|
|
8603
|
+
const sorted = [...section.findings].sort((a, b) => {
|
|
8604
|
+
const sevDiff = SEVERITY_ORDER3.indexOf(a.severity) - SEVERITY_ORDER3.indexOf(b.severity);
|
|
8605
|
+
if (sevDiff !== 0) return sevDiff;
|
|
8606
|
+
return b.riskScore - a.riskScore;
|
|
8607
|
+
});
|
|
8608
|
+
const groups = groupFindings(sorted);
|
|
8609
|
+
autoHtml = groups.map(renderGroup).join("\n");
|
|
8610
|
+
}
|
|
8611
|
+
let manualHtml = "";
|
|
8612
|
+
if (section.manualItems.length > 0) {
|
|
8613
|
+
const items = section.manualItems.map((item) => `<div class="hw-manual-item"><span class="hw-manual-checkbox">□</span>${esc2(item)}</div>`).join("\n");
|
|
8614
|
+
manualHtml = `
|
|
8615
|
+
<div class="hw-manual-section">
|
|
8616
|
+
<h4>📋 ${esc2(t.hwManualCheck)}</h4>
|
|
8617
|
+
${items}
|
|
8618
|
+
</div>`;
|
|
8619
|
+
}
|
|
8620
|
+
return `<details class="hw-section">
|
|
8621
|
+
<summary>
|
|
8622
|
+
<span class="hw-section-icon">${esc2(sectionIcon)}</span>
|
|
8623
|
+
<span class="hw-section-title">${esc2(sectionName)}</span>
|
|
8624
|
+
<span class="hw-section-stats">${statBadges.join(" ")}</span>
|
|
8625
|
+
</summary>
|
|
8626
|
+
<div class="hw-section-body">
|
|
8627
|
+
<div class="hw-auto-section">
|
|
8628
|
+
<h4>🤖 ${esc2(t.hwAutoCheck)}</h4>
|
|
8629
|
+
${autoHtml}
|
|
8630
|
+
</div>
|
|
8631
|
+
${manualHtml}
|
|
8632
|
+
</div>
|
|
8633
|
+
</details>`;
|
|
8634
|
+
}).filter(Boolean).join("\n");
|
|
8635
|
+
const findingsColor = totalFindings === 0 ? "#22c55e" : totalFindings <= 5 ? "#eab308" : "#ef4444";
|
|
8636
|
+
return `<!DOCTYPE html>
|
|
8637
|
+
<html lang="${htmlLang}">
|
|
8638
|
+
<head>
|
|
8639
|
+
<meta charset="UTF-8">
|
|
8640
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
8641
|
+
<title>${esc2(t.hwReportTitle)} — ${esc2(date)}</title>
|
|
8642
|
+
<style>${hwCss()}</style>
|
|
8643
|
+
</head>
|
|
8644
|
+
<body>
|
|
8645
|
+
<div class="container">
|
|
8646
|
+
|
|
8647
|
+
<header>
|
|
8648
|
+
<h1>🛡️ ${esc2(t.hwReportTitle)}</h1>
|
|
8649
|
+
<div class="meta">${esc2(t.account)}: ${esc2(accountId)} | ${esc2(t.region)}: ${esc2(region)} | ${esc2(t.scanTime)}: ${esc2(scanTime)}</div>
|
|
8650
|
+
</header>
|
|
8651
|
+
|
|
8652
|
+
<section class="summary-cards">
|
|
8653
|
+
<div class="summary-card"><div class="stat-count" style="color:${findingsColor}">${totalFindings}</div><div class="stat-label">${esc2(t.hwTotalFindings)}</div></div>
|
|
8654
|
+
<div class="summary-card"><div class="stat-count" style="color:#60a5fa">${sectionsChecked}</div><div class="stat-label">${esc2(t.hwSectionsChecked)}</div></div>
|
|
8655
|
+
<div class="summary-card"><div class="stat-count" style="color:#22c55e">${autoVerified}</div><div class="stat-label">${esc2(t.hwAutoVerified)}</div></div>
|
|
8656
|
+
<div class="summary-card"><div class="stat-count" style="color:#fbbf24">${totalManualItems}</div><div class="stat-label">${esc2(t.hwManualPending)}</div></div>
|
|
8657
|
+
</section>
|
|
8658
|
+
|
|
8659
|
+
${sectionsHtml}
|
|
8660
|
+
|
|
8661
|
+
<footer>
|
|
8662
|
+
<p>${esc2(t.generatedBy)} v${VERSION}</p>
|
|
8663
|
+
|
|
8664
|
+
</footer>
|
|
8665
|
+
|
|
8666
|
+
</div>
|
|
8667
|
+
</body>
|
|
8668
|
+
</html>`;
|
|
8669
|
+
}
|
|
8670
|
+
|
|
8237
8671
|
// src/tools/save-results.ts
|
|
8238
8672
|
import { writeFileSync, readFileSync, mkdirSync, existsSync } from "fs";
|
|
8239
8673
|
import { join } from "path";
|
|
@@ -8304,7 +8738,7 @@ function saveResults(scanResults, outputDir) {
|
|
|
8304
8738
|
}
|
|
8305
8739
|
|
|
8306
8740
|
// src/tools/scan-groups.ts
|
|
8307
|
-
var
|
|
8741
|
+
var SEVERITY_ORDER4 = {
|
|
8308
8742
|
LOW: 0,
|
|
8309
8743
|
MEDIUM: 1,
|
|
8310
8744
|
HIGH: 2,
|
|
@@ -8313,8 +8747,8 @@ var SEVERITY_ORDER3 = {
|
|
|
8313
8747
|
function applyFindingsFilter(moduleName, findings, filter) {
|
|
8314
8748
|
let result = findings;
|
|
8315
8749
|
if (filter.minSeverity) {
|
|
8316
|
-
const minLevel =
|
|
8317
|
-
result = result.filter((f) => (
|
|
8750
|
+
const minLevel = SEVERITY_ORDER4[filter.minSeverity.toUpperCase()] ?? 0;
|
|
8751
|
+
result = result.filter((f) => (SEVERITY_ORDER4[f.severity] ?? 0) >= minLevel);
|
|
8318
8752
|
}
|
|
8319
8753
|
if (moduleName === "security_hub_findings" && filter.securityHubCategories?.length) {
|
|
8320
8754
|
const keywords = filter.securityHubCategories;
|
|
@@ -8348,10 +8782,10 @@ var SCAN_GROUPS = {
|
|
|
8348
8782
|
},
|
|
8349
8783
|
hw_defense: {
|
|
8350
8784
|
name: "\u62A4\u7F51\u84DD\u961F\u52A0\u56FA",
|
|
8351
|
-
description: "\u62A4\u7F51\u524D\u5B89\u5168\u81EA\u67E5 \u2014 \u653B\u51FB\u9762+\u5F31\u70B9\u8BC4\u4F30",
|
|
8352
|
-
modules: ["service_detection", "
|
|
8785
|
+
description: "\u62A4\u7F51\u524D\u5B89\u5168\u81EA\u67E5 \u2014 \u653B\u51FB\u8005\u89C6\u89D2\u7684\u653B\u51FB\u9762+\u5F31\u70B9\u8BC4\u4F30",
|
|
8786
|
+
modules: ["service_detection", "network_reachability", "dns_dangling", "public_access_verify", "ssl_certificate", "waf_coverage", "imdsv2_enforcement", "secret_exposure", "iam_privilege_escalation", "patch_compliance_findings", "security_hub_findings"],
|
|
8353
8787
|
findingsFilter: {
|
|
8354
|
-
|
|
8788
|
+
securityHubCategories: ["network", "public", "exposure", "port", "WAF", "vulnerability", "patch", "IAM", "iam", "access", "privilege", "secret", "credential", "password", "IMDS", "firewall", "VPC", "vpc", "SecurityGroup", "securitygroup", "CVE", "cve", "Inspector", "inspector", "software", "MFA", "mfa", "key rotation", "SSL", "ssl", "TLS", "tls", "certificate", "metadata", "CloudTrail", "cloudtrail", "logging", "audit", "encryption"],
|
|
8355
8789
|
minSeverity: "MEDIUM"
|
|
8356
8790
|
}
|
|
8357
8791
|
},
|
|
@@ -8873,6 +9307,7 @@ function createServer(defaultRegion) {
|
|
|
8873
9307
|
const summaryContent = content[0];
|
|
8874
9308
|
if (summaryContent && summaryContent.type === "text") {
|
|
8875
9309
|
summaryContent.text += "\n\n" + getHwDefenseChecklist(lang ?? "zh");
|
|
9310
|
+
summaryContent.text += "\n\n\u{1F4A1} Tip: Call generate_hw_defense_report with these scan results to get a dedicated HTML report organized by HW Defense SOP checklist categories.";
|
|
8876
9311
|
}
|
|
8877
9312
|
}
|
|
8878
9313
|
return { content };
|
|
@@ -8971,6 +9406,23 @@ function createServer(defaultRegion) {
|
|
|
8971
9406
|
}
|
|
8972
9407
|
}
|
|
8973
9408
|
);
|
|
9409
|
+
server.tool(
|
|
9410
|
+
"generate_hw_defense_report",
|
|
9411
|
+
"Generate an HTML report organized by HW Defense (\u62A4\u7F51) SOP checklist categories. Save as .html file.",
|
|
9412
|
+
{
|
|
9413
|
+
scan_results: z.string().describe("JSON string of FullScanResult from scan_group hw_defense or scan_all"),
|
|
9414
|
+
lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
|
|
9415
|
+
},
|
|
9416
|
+
async ({ scan_results, lang }) => {
|
|
9417
|
+
try {
|
|
9418
|
+
const parsed = JSON.parse(scan_results);
|
|
9419
|
+
const report = generateHwDefenseHtmlReport(parsed, lang ?? "zh");
|
|
9420
|
+
return { content: [{ type: "text", text: report }] };
|
|
9421
|
+
} catch (err) {
|
|
9422
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
9423
|
+
}
|
|
9424
|
+
}
|
|
9425
|
+
);
|
|
8974
9426
|
server.tool(
|
|
8975
9427
|
"generate_maturity_report",
|
|
8976
9428
|
"Generate a security maturity assessment report from scan_all results. Requires service_detection module output. Read-only.",
|
|
@@ -9267,6 +9719,7 @@ export {
|
|
|
9267
9719
|
calculateScore,
|
|
9268
9720
|
createServer,
|
|
9269
9721
|
generateHtmlReport,
|
|
9722
|
+
generateHwDefenseHtmlReport,
|
|
9270
9723
|
generateMarkdownReport,
|
|
9271
9724
|
generateMlps3HtmlReport,
|
|
9272
9725
|
getCurrentAccountId,
|