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.
@@ -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.1";
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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)} &mdash; ${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">&times;${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">&#10003; ${esc2(t.hwClean)}</span>`);
8591
+ }
8592
+ if (section.manualItems.length > 0) {
8593
+ statBadges.push(`<span style="color:#fbbf24;font-size:12px">&#9744; ${esc2(t.hwManualCount(section.manualItems.length))}</span>`);
8594
+ }
8595
+ let autoHtml;
8596
+ if (!section.hasAutoModules) {
8597
+ autoHtml = `<div class="hw-no-auto">&#9898; ${esc2(t.hwNoAutoCheck)}</div>`;
8598
+ } else if (!section.hasAutoResults) {
8599
+ autoHtml = `<div class="hw-no-auto">&#9898; ${esc2(t.hwNoAutoCheck)}</div>`;
8600
+ } else if (section.findings.length === 0) {
8601
+ autoHtml = `<div class="hw-clean">&#10004; ${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">&#9633;</span>${esc2(item)}</div>`).join("\n");
8614
+ manualHtml = `
8615
+ <div class="hw-manual-section">
8616
+ <h4>&#128203; ${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>&#129302; ${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)} &mdash; ${esc2(date)}</title>
8642
+ <style>${hwCss()}</style>
8643
+ </head>
8644
+ <body>
8645
+ <div class="container">
8646
+
8647
+ <header>
8648
+ <h1>&#128737;&#65039; ${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 SEVERITY_ORDER3 = {
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 = SEVERITY_ORDER3[filter.minSeverity.toUpperCase()] ?? 0;
8317
- result = result.filter((f) => (SEVERITY_ORDER3[f.severity] ?? 0) >= minLevel);
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", "secret_exposure", "network_reachability", "dns_dangling", "ssl_certificate", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings", "inspector_findings", "config_rules_findings", "access_analyzer_findings", "patch_compliance_findings", "imdsv2_enforcement", "waf_coverage"],
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
- guardDutyTypes: ["Backdoor", "Trojan", "PenTest", "CryptoCurrency"],
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,