aws-security-mcp 0.4.3 → 0.5.1

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/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.4.3";
7
+ var VERSION = "0.5.1";
8
8
 
9
9
  // src/utils/aws-client.ts
10
10
  import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
@@ -3733,6 +3733,242 @@ var PatchComplianceFindingsScanner = class {
3733
3733
  }
3734
3734
  };
3735
3735
 
3736
+ // src/scanners/imdsv2-enforcement.ts
3737
+ import {
3738
+ EC2Client as EC2Client6,
3739
+ DescribeInstancesCommand as DescribeInstancesCommand5
3740
+ } from "@aws-sdk/client-ec2";
3741
+ function makeFinding11(opts) {
3742
+ const severity = severityFromScore(opts.riskScore);
3743
+ return { ...opts, severity, priority: priorityFromSeverity(severity) };
3744
+ }
3745
+ var Imdsv2EnforcementScanner = class {
3746
+ moduleName = "imdsv2_enforcement";
3747
+ async scan(ctx) {
3748
+ const { region, partition, accountId } = ctx;
3749
+ const startMs = Date.now();
3750
+ const findings = [];
3751
+ const warnings = [];
3752
+ try {
3753
+ const client = createClient(EC2Client6, region, ctx.credentials);
3754
+ const instances = [];
3755
+ let nextToken;
3756
+ do {
3757
+ const resp = await client.send(
3758
+ new DescribeInstancesCommand5({
3759
+ Filters: [{ Name: "instance-state-name", Values: ["running"] }],
3760
+ NextToken: nextToken
3761
+ })
3762
+ );
3763
+ if (resp.Reservations) {
3764
+ for (const reservation of resp.Reservations) {
3765
+ if (reservation.Instances) {
3766
+ instances.push(...reservation.Instances);
3767
+ }
3768
+ }
3769
+ }
3770
+ nextToken = resp.NextToken;
3771
+ } while (nextToken);
3772
+ for (const instance of instances) {
3773
+ const instanceId = instance.InstanceId ?? "unknown";
3774
+ const instanceType = instance.InstanceType ?? "unknown";
3775
+ const state = instance.State?.Name ?? "unknown";
3776
+ const httpTokens = instance.MetadataOptions?.HttpTokens ?? "unknown";
3777
+ const hopLimit = instance.MetadataOptions?.HttpPutResponseHopLimit ?? 1;
3778
+ const instanceArn = `arn:${partition}:ec2:${region}:${accountId}:instance/${instanceId}`;
3779
+ if (httpTokens !== "required") {
3780
+ const description = [
3781
+ `EC2 instance ${instanceId} (type: ${instanceType}, state: ${state}) has HttpTokens set to "${httpTokens}".`,
3782
+ `IMDSv1 is accessible, allowing unauthenticated metadata requests.`
3783
+ ];
3784
+ if (hopLimit > 1) {
3785
+ description.push(`HttpPutResponseHopLimit is ${hopLimit} (>1), which may allow containers to reach IMDS.`);
3786
+ }
3787
+ findings.push(
3788
+ makeFinding11({
3789
+ riskScore: 7.5,
3790
+ title: `EC2 instance ${instanceId} does not enforce IMDSv2`,
3791
+ resourceType: "AWS::EC2::Instance",
3792
+ resourceId: instanceId,
3793
+ resourceArn: instanceArn,
3794
+ region,
3795
+ description: description.join(" "),
3796
+ impact: "IMDSv1 allows attackers to steal IAM role credentials via SSRF attacks",
3797
+ remediationSteps: [
3798
+ "Enforce IMDSv2 by setting HttpTokens to 'required'.",
3799
+ "Run: aws ec2 modify-instance-metadata-options --instance-id " + instanceId + " --http-tokens required --http-endpoint enabled",
3800
+ "Set HttpPutResponseHopLimit to 1 unless running containers that need metadata access.",
3801
+ "Update launch templates and Auto Scaling groups to enforce IMDSv2 for new instances."
3802
+ ]
3803
+ })
3804
+ );
3805
+ } else if (hopLimit > 1) {
3806
+ warnings.push(
3807
+ `Instance ${instanceId} enforces IMDSv2 but HttpPutResponseHopLimit is ${hopLimit} (>1). Verify this is intentional for containerized workloads.`
3808
+ );
3809
+ }
3810
+ }
3811
+ return {
3812
+ module: this.moduleName,
3813
+ status: "success",
3814
+ warnings: warnings.length > 0 ? warnings : void 0,
3815
+ resourcesScanned: instances.length,
3816
+ findingsCount: findings.length,
3817
+ scanTimeMs: Date.now() - startMs,
3818
+ findings
3819
+ };
3820
+ } catch (err) {
3821
+ return {
3822
+ module: this.moduleName,
3823
+ status: "error",
3824
+ error: err instanceof Error ? err.message : String(err),
3825
+ warnings: warnings.length > 0 ? warnings : void 0,
3826
+ resourcesScanned: 0,
3827
+ findingsCount: 0,
3828
+ scanTimeMs: Date.now() - startMs,
3829
+ findings: []
3830
+ };
3831
+ }
3832
+ }
3833
+ };
3834
+
3835
+ // src/scanners/waf-coverage.ts
3836
+ import {
3837
+ ElasticLoadBalancingV2Client,
3838
+ DescribeLoadBalancersCommand
3839
+ } from "@aws-sdk/client-elastic-load-balancing-v2";
3840
+ import {
3841
+ WAFV2Client,
3842
+ GetWebACLForResourceCommand
3843
+ } from "@aws-sdk/client-wafv2";
3844
+ function makeFinding12(opts) {
3845
+ const severity = severityFromScore(opts.riskScore);
3846
+ return { ...opts, severity, priority: priorityFromSeverity(severity) };
3847
+ }
3848
+ var WafCoverageScanner = class {
3849
+ moduleName = "waf_coverage";
3850
+ async scan(ctx) {
3851
+ const { region } = ctx;
3852
+ const startMs = Date.now();
3853
+ const findings = [];
3854
+ const warnings = [];
3855
+ try {
3856
+ const elbClient = createClient(ElasticLoadBalancingV2Client, region, ctx.credentials);
3857
+ const wafClient = createClient(WAFV2Client, region, ctx.credentials);
3858
+ const loadBalancers = [];
3859
+ let marker;
3860
+ do {
3861
+ const resp = await elbClient.send(
3862
+ new DescribeLoadBalancersCommand({ Marker: marker })
3863
+ );
3864
+ if (resp.LoadBalancers) {
3865
+ loadBalancers.push(...resp.LoadBalancers);
3866
+ }
3867
+ marker = resp.NextMarker;
3868
+ } while (marker);
3869
+ const internetFacing = loadBalancers.filter((lb) => lb.Scheme === "internet-facing");
3870
+ for (const lb of internetFacing) {
3871
+ const lbName = lb.LoadBalancerName ?? "unknown";
3872
+ const lbArn = lb.LoadBalancerArn ?? "unknown";
3873
+ const lbType = lb.Type ?? "unknown";
3874
+ if (lbType !== "application") {
3875
+ warnings.push(
3876
+ `Skipping ${lbType} load balancer "${lbName}" \u2014 WAF Web ACL association is only supported for ALBs.`
3877
+ );
3878
+ continue;
3879
+ }
3880
+ try {
3881
+ const wafResp = await wafClient.send(
3882
+ new GetWebACLForResourceCommand({ ResourceArn: lbArn })
3883
+ );
3884
+ if (!wafResp.WebACL) {
3885
+ findings.push(
3886
+ makeFinding12({
3887
+ riskScore: 7.5,
3888
+ title: `Internet-facing ALB ${lbName} has no WAF protection`,
3889
+ resourceType: "AWS::ElasticLoadBalancingV2::LoadBalancer",
3890
+ resourceId: lbName,
3891
+ resourceArn: lbArn,
3892
+ region,
3893
+ description: `Internet-facing Application Load Balancer "${lbName}" does not have a WAF Web ACL associated. Traffic is not inspected for common web exploits.`,
3894
+ impact: "Without WAF, the ALB is exposed to SQL injection, XSS, and other OWASP Top 10 attacks",
3895
+ remediationSteps: [
3896
+ "Create a WAFv2 Web ACL with managed rule groups (e.g., AWSManagedRulesCommonRuleSet).",
3897
+ "Associate the Web ACL with the ALB using the REGIONAL scope.",
3898
+ "Enable WAF logging for visibility into blocked requests.",
3899
+ "Consider adding rate-based rules to mitigate DDoS at the application layer."
3900
+ ]
3901
+ })
3902
+ );
3903
+ }
3904
+ } catch (wafErr) {
3905
+ const errMsg = wafErr instanceof Error ? wafErr.message : String(wafErr);
3906
+ const errName = wafErr instanceof Error ? wafErr.name ?? "" : "";
3907
+ if (errName === "WAFNonexistentItemException") {
3908
+ findings.push(
3909
+ makeFinding12({
3910
+ riskScore: 7.5,
3911
+ title: `Internet-facing ALB ${lbName} has no WAF protection`,
3912
+ resourceType: "AWS::ElasticLoadBalancingV2::LoadBalancer",
3913
+ resourceId: lbName,
3914
+ resourceArn: lbArn,
3915
+ region,
3916
+ description: `Internet-facing Application Load Balancer "${lbName}" does not have a WAF Web ACL associated. Traffic is not inspected for common web exploits.`,
3917
+ impact: "Without WAF, the ALB is exposed to SQL injection, XSS, and other OWASP Top 10 attacks",
3918
+ remediationSteps: [
3919
+ "Create a WAFv2 Web ACL with managed rule groups (e.g., AWSManagedRulesCommonRuleSet).",
3920
+ "Associate the Web ACL with the ALB using the REGIONAL scope.",
3921
+ "Enable WAF logging for visibility into blocked requests.",
3922
+ "Consider adding rate-based rules to mitigate DDoS at the application layer."
3923
+ ]
3924
+ })
3925
+ );
3926
+ } else if (errName === "AccessDeniedException" || errName === "WAFInvalidParameterException") {
3927
+ warnings.push(
3928
+ `Could not check WAF for ALB "${lbName}": ${errMsg}. Ensure wafv2:GetWebACLForResource permission is granted.`
3929
+ );
3930
+ } else {
3931
+ warnings.push(`Error checking WAF for ALB "${lbName}": ${errMsg}`);
3932
+ }
3933
+ }
3934
+ }
3935
+ return {
3936
+ module: this.moduleName,
3937
+ status: "success",
3938
+ warnings: warnings.length > 0 ? warnings : void 0,
3939
+ resourcesScanned: internetFacing.length,
3940
+ findingsCount: findings.length,
3941
+ scanTimeMs: Date.now() - startMs,
3942
+ findings
3943
+ };
3944
+ } catch (err) {
3945
+ const errMsg = err instanceof Error ? err.message : String(err);
3946
+ const errName = err instanceof Error ? err.name ?? "" : "";
3947
+ if (errName === "AccessDeniedException" || errName === "UnrecognizedClientException") {
3948
+ return {
3949
+ module: this.moduleName,
3950
+ status: "success",
3951
+ warnings: [`WAF coverage check skipped: ${errMsg}`],
3952
+ resourcesScanned: 0,
3953
+ findingsCount: 0,
3954
+ scanTimeMs: Date.now() - startMs,
3955
+ findings: []
3956
+ };
3957
+ }
3958
+ return {
3959
+ module: this.moduleName,
3960
+ status: "error",
3961
+ error: errMsg,
3962
+ warnings: warnings.length > 0 ? warnings : void 0,
3963
+ resourcesScanned: 0,
3964
+ findingsCount: 0,
3965
+ scanTimeMs: Date.now() - startMs,
3966
+ findings: []
3967
+ };
3968
+ }
3969
+ }
3970
+ };
3971
+
3736
3972
  // src/tools/report-tool.ts
3737
3973
  var SEVERITY_ICON = {
3738
3974
  CRITICAL: "\u{1F534}",
@@ -4120,6 +4356,92 @@ function scoreColor(score) {
4120
4356
  if (score >= 50) return "#eab308";
4121
4357
  return "#ef4444";
4122
4358
  }
4359
+ var SERVICE_RECOMMENDATIONS = {
4360
+ security_hub_findings: {
4361
+ icon: "\u{1F534}",
4362
+ service: "Security Hub",
4363
+ impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
4364
+ action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
4365
+ },
4366
+ guardduty_findings: {
4367
+ icon: "\u{1F534}",
4368
+ service: "GuardDuty",
4369
+ impact: "\u65E0\u6CD5\u68C0\u6D4B\u5A01\u80C1\u6D3B\u52A8\uFF08\u6076\u610F IP\u3001\u5F02\u5E38 API \u8C03\u7528\u3001\u52A0\u5BC6\u8D27\u5E01\u6316\u77FF\u7B49\uFF09",
4370
+ action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
4371
+ },
4372
+ inspector_findings: {
4373
+ icon: "\u{1F7E1}",
4374
+ service: "Inspector",
4375
+ impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
4376
+ action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
4377
+ },
4378
+ trusted_advisor_findings: {
4379
+ icon: "\u{1F7E1}",
4380
+ service: "Trusted Advisor",
4381
+ impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
4382
+ action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
4383
+ },
4384
+ config_rules_findings: {
4385
+ icon: "\u{1F7E1}",
4386
+ service: "AWS Config",
4387
+ impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
4388
+ action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
4389
+ },
4390
+ access_analyzer_findings: {
4391
+ icon: "\u{1F7E1}",
4392
+ service: "IAM Access Analyzer",
4393
+ impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
4394
+ action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
4395
+ },
4396
+ patch_compliance_findings: {
4397
+ icon: "\u{1F7E1}",
4398
+ service: "SSM Patch Manager",
4399
+ impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
4400
+ action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
4401
+ }
4402
+ };
4403
+ var SERVICE_NOT_ENABLED_PATTERNS = [
4404
+ "not enabled",
4405
+ "not found",
4406
+ "No IAM Access Analyzer",
4407
+ "No SSM-managed instances",
4408
+ "requires AWS Business or Enterprise Support",
4409
+ "not available",
4410
+ "is not enabled"
4411
+ ];
4412
+ function getDisabledServices(modules) {
4413
+ const disabled = [];
4414
+ for (const mod of modules) {
4415
+ const rec = SERVICE_RECOMMENDATIONS[mod.module];
4416
+ if (!rec) continue;
4417
+ if (!mod.warnings?.length) continue;
4418
+ const hasNotEnabled = mod.warnings.some(
4419
+ (w) => SERVICE_NOT_ENABLED_PATTERNS.some((p) => w.includes(p))
4420
+ );
4421
+ if (hasNotEnabled) {
4422
+ disabled.push(rec);
4423
+ }
4424
+ }
4425
+ return disabled;
4426
+ }
4427
+ function buildServiceReminderHtml(modules) {
4428
+ const disabled = getDisabledServices(modules);
4429
+ if (disabled.length === 0) return "";
4430
+ const items = disabled.map((svc) => `
4431
+ <div style="margin-bottom:12px">
4432
+ <div style="font-weight:600;font-size:15px">${esc(svc.icon)} ${esc(svc.service)} \u672A\u542F\u7528</div>
4433
+ <div style="margin-left:28px;color:#cbd5e1;font-size:13px">\u5F71\u54CD\uFF1A${esc(svc.impact)}</div>
4434
+ <div style="margin-left:28px;color:#cbd5e1;font-size:13px">\u5EFA\u8BAE\uFF1A${esc(svc.action)}</div>
4435
+ </div>`).join("\n");
4436
+ return `
4437
+ <section>
4438
+ <div style="background:#2d1f00;border:1px solid #b45309;border-radius:8px;padding:20px;margin-bottom:32px">
4439
+ <div style="font-size:17px;font-weight:700;margin-bottom:12px">&#9889; \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A</div>
4440
+ ${items}
4441
+ <div style="margin-top:12px;font-size:13px;color:#fbbf24;font-weight:500">\u542F\u7528\u4EE5\u4E0A\u670D\u52A1\u540E\u91CD\u65B0\u626B\u63CF\u53EF\u83B7\u5F97\u66F4\u5B8C\u6574\u7684\u5B89\u5168\u8BC4\u4F30\u3002</div>
4442
+ </div>
4443
+ </section>`;
4444
+ }
4123
4445
  function sharedCss() {
4124
4446
  return `
4125
4447
  *{margin:0;padding:0;box-sizing:border-box}
@@ -4645,6 +4967,8 @@ ${trendHtml}
4645
4967
 
4646
4968
  ${top5Html}
4647
4969
 
4970
+ ${buildServiceReminderHtml(modules)}
4971
+
4648
4972
  <section>
4649
4973
  <h2>Scan Statistics</h2>
4650
4974
  <table>
@@ -4841,6 +5165,8 @@ ${unknownNote}
4841
5165
 
4842
5166
  ${trendHtml}
4843
5167
 
5168
+ ${buildServiceReminderHtml(scanResults.modules)}
5169
+
4844
5170
  ${categorySections}
4845
5171
 
4846
5172
  ${remediationHtml}
@@ -4964,13 +5290,13 @@ var SCAN_GROUPS = {
4964
5290
  mlps3_precheck: {
4965
5291
  name: "\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0",
4966
5292
  description: "GB/T 22239-2019 \u7B49\u4FDD\u4E09\u7EA7 AWS \u4E91\u79DF\u6237\u5C42\u914D\u7F6E\u68C0\u67E5",
4967
- modules: ["service_detection", "secret_exposure", "ssl_certificate", "dns_dangling", "network_reachability", "iam_privilege_escalation", "tag_compliance", "disaster_recovery", "security_hub_findings", "guardduty_findings", "inspector_findings", "trusted_advisor_findings", "config_rules_findings", "access_analyzer_findings", "patch_compliance_findings"],
5293
+ modules: ["service_detection", "secret_exposure", "ssl_certificate", "dns_dangling", "network_reachability", "iam_privilege_escalation", "tag_compliance", "disaster_recovery", "security_hub_findings", "guardduty_findings", "inspector_findings", "trusted_advisor_findings", "config_rules_findings", "access_analyzer_findings", "patch_compliance_findings", "imdsv2_enforcement", "waf_coverage"],
4968
5294
  reportType: "mlps3"
4969
5295
  },
4970
5296
  hw_defense: {
4971
5297
  name: "\u62A4\u7F51\u84DD\u961F\u52A0\u56FA",
4972
5298
  description: "\u62A4\u7F51\u524D\u5B89\u5168\u81EA\u67E5 \u2014 \u653B\u51FB\u9762+\u5F31\u70B9\u8BC4\u4F30",
4973
- 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"],
5299
+ 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"],
4974
5300
  findingsFilter: {
4975
5301
  guardDutyTypes: ["Backdoor", "Trojan", "PenTest", "CryptoCurrency"],
4976
5302
  minSeverity: "MEDIUM"
@@ -4979,7 +5305,7 @@ var SCAN_GROUPS = {
4979
5305
  exposure: {
4980
5306
  name: "\u516C\u7F51\u66B4\u9732\u9762\u8BC4\u4F30",
4981
5307
  description: "\u8BC4\u4F30\u516C\u7F51\u53EF\u8FBE\u7684\u8D44\u6E90\u548C\u7AEF\u53E3",
4982
- modules: ["network_reachability", "dns_dangling", "public_access_verify", "ssl_certificate", "security_hub_findings", "access_analyzer_findings"],
5308
+ modules: ["network_reachability", "dns_dangling", "public_access_verify", "ssl_certificate", "security_hub_findings", "access_analyzer_findings", "imdsv2_enforcement", "waf_coverage"],
4983
5309
  findingsFilter: {
4984
5310
  securityHubCategories: ["network", "public", "exposure", "port"]
4985
5311
  }
@@ -5026,7 +5352,7 @@ var SCAN_GROUPS = {
5026
5352
  new_account_baseline: {
5027
5353
  name: "\u65B0\u8D26\u6237\u57FA\u7EBF\u68C0\u67E5",
5028
5354
  description: "\u65B0 AWS \u8D26\u6237\u5B89\u5168\u57FA\u7EBF",
5029
- modules: ["service_detection", "secret_exposure", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings", "access_analyzer_findings"]
5355
+ modules: ["service_detection", "secret_exposure", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings", "access_analyzer_findings", "imdsv2_enforcement"]
5030
5356
  },
5031
5357
  aggregation: {
5032
5358
  name: "\u5B89\u5168\u670D\u52A1\u805A\u5408",
@@ -5036,7 +5362,7 @@ var SCAN_GROUPS = {
5036
5362
  };
5037
5363
 
5038
5364
  // src/resources/index.ts
5039
- var SECURITY_RULES_CONTENT = `# AWS Security Scan Modules & Rules (17 modules)
5365
+ var SECURITY_RULES_CONTENT = `# AWS Security Scan Modules & Rules (19 modules)
5040
5366
 
5041
5367
  ## 1. Service Detection (service_detection)
5042
5368
  Detects which AWS security services are enabled and assesses overall security maturity.
@@ -5130,6 +5456,21 @@ Checks patch compliance status for SSM-managed instances.
5130
5456
  - Missing non-security patches \u2192 MEDIUM (5.5).
5131
5457
  - Instances without patch data flagged as LOW (3.0) for visibility.
5132
5458
  - Includes platform info, missing/failed counts, and last scan time.
5459
+
5460
+ ## 18. IMDSv2 Enforcement (imdsv2_enforcement)
5461
+ Checks if EC2 instances enforce IMDSv2 (Instance Metadata Service v2).
5462
+ - Lists all running EC2 instances and checks MetadataOptions.HttpTokens.
5463
+ - **HttpTokens != "required"** \u2014 Risk 7.5: IMDSv1 allows credential theft via SSRF attacks.
5464
+ - Also checks HttpPutResponseHopLimit \u2014 values >1 on containerized workloads noted as warning.
5465
+ - Remediation: Set HttpTokens to "required" via modify-instance-metadata-options.
5466
+
5467
+ ## 19. WAF Coverage (waf_coverage)
5468
+ Checks if internet-facing ALBs have WAF Web ACL associated for protection.
5469
+ - Lists all ELBv2 load balancers, filters to internet-facing only.
5470
+ - For each internet-facing ALB, checks WAFv2 Web ACL association.
5471
+ - **No WAF Web ACL** \u2014 Risk 7.5: ALB exposed to SQL injection, XSS, and OWASP Top 10 attacks.
5472
+ - NLBs (L4) are skipped as WAF does not apply \u2014 noted in warnings.
5473
+ - Gracefully handles WAFv2 access denied or unavailable regions.
5133
5474
  `;
5134
5475
  var RISK_SCORING_CONTENT = `# Risk Scoring Model
5135
5476
 
@@ -5194,8 +5535,144 @@ var MODULE_DESCRIPTIONS = {
5194
5535
  trusted_advisor_findings: "Aggregates security checks from AWS Trusted Advisor \u2014 requires Business or Enterprise Support plan.",
5195
5536
  config_rules_findings: "Pulls non-compliant AWS Config Rule evaluation results \u2014 configuration compliance violations across all resource types.",
5196
5537
  access_analyzer_findings: "Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account (external principals, public access).",
5197
- patch_compliance_findings: "Checks SSM Patch Manager compliance \u2014 managed instances with missing or failed security and system patches."
5538
+ patch_compliance_findings: "Checks SSM Patch Manager compliance \u2014 managed instances with missing or failed security and system patches.",
5539
+ imdsv2_enforcement: "Checks if EC2 instances enforce IMDSv2 (HttpTokens: required) \u2014 IMDSv1 allows credential theft via SSRF.",
5540
+ waf_coverage: "Checks if internet-facing ALBs have WAF Web ACL associated for protection against common web exploits."
5541
+ };
5542
+ var HW_DEFENSE_CHECKLIST = `
5543
+ \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\u2550\u2550
5544
+ \u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09
5545
+ \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\u2550\u2550
5546
+
5547
+ \u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A
5548
+
5549
+ \u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
5550
+ \u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
5551
+ \u25A1 \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
5552
+ \u25A1 \u660E\u786E\u5404\u7CFB\u7EDF\uFF08\u751F\u4EA7\u6838\u5FC3/\u751F\u4EA7\u975E\u6838\u5FC3/\u6D4B\u8BD5/\u5F00\u53D1\uFF09\u7684\u5E94\u6025\u5904\u7F6E\u65B9\u5F0F
5553
+ \u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F
5554
+
5555
+ \u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
5556
+ \u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
5557
+ \u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
5558
+ \u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563
5559
+
5560
+ \u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
5561
+ \u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
5562
+ \u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
5563
+ \u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
5564
+ \u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
5565
+ \u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09
5566
+
5567
+ \u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
5568
+ \u25A1 \u786E\u4FDD\u6240\u6709\u4E92\u8054\u7F51/DX \u4E13\u7EBF\u51FA\u5165\u7AD9\u8DEF\u5F84\u5728\u67B6\u6784\u56FE\u4E2D\u6E05\u6670\u6807\u6CE8
5569
+ \u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
5570
+ \u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3
5571
+
5572
+ \u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
5573
+ \u25A1 \u62A4\u7F51\u524D\u8054\u7CFB\u5B89\u5168\u5382\u5546\uFF08\u9752\u85E4/\u957F\u4EAD/\u5FAE\u6B65\u7B49\uFF09\u8FDB\u884C\u6A21\u62DF\u653B\u51FB\u6F14\u7EC3
5574
+ \u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
5575
+ \u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09
5576
+
5577
+ \u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
5578
+ \u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
5579
+ \u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
5580
+ \u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A"\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0"
5581
+
5582
+ \u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
5583
+ \u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
5584
+ \u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
5585
+ \u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
5586
+ \u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801
5587
+
5588
+ \u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
5589
+ \u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
5590
+ \u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
5591
+ \u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669
5592
+
5593
+ \u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)
5594
+ `;
5595
+ var SERVICE_RECOMMENDATIONS2 = {
5596
+ security_hub_findings: {
5597
+ icon: "\u{1F534}",
5598
+ service: "Security Hub",
5599
+ impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
5600
+ action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
5601
+ },
5602
+ guardduty_findings: {
5603
+ icon: "\u{1F534}",
5604
+ service: "GuardDuty",
5605
+ impact: "\u65E0\u6CD5\u68C0\u6D4B\u5A01\u80C1\u6D3B\u52A8\uFF08\u6076\u610F IP\u3001\u5F02\u5E38 API \u8C03\u7528\u3001\u52A0\u5BC6\u8D27\u5E01\u6316\u77FF\u7B49\uFF09",
5606
+ action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
5607
+ },
5608
+ inspector_findings: {
5609
+ icon: "\u{1F7E1}",
5610
+ service: "Inspector",
5611
+ impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
5612
+ action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
5613
+ },
5614
+ trusted_advisor_findings: {
5615
+ icon: "\u{1F7E1}",
5616
+ service: "Trusted Advisor",
5617
+ impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
5618
+ action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
5619
+ },
5620
+ config_rules_findings: {
5621
+ icon: "\u{1F7E1}",
5622
+ service: "AWS Config",
5623
+ impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
5624
+ action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
5625
+ },
5626
+ access_analyzer_findings: {
5627
+ icon: "\u{1F7E1}",
5628
+ service: "IAM Access Analyzer",
5629
+ impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
5630
+ action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
5631
+ },
5632
+ patch_compliance_findings: {
5633
+ icon: "\u{1F7E1}",
5634
+ service: "SSM Patch Manager",
5635
+ impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
5636
+ action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
5637
+ }
5198
5638
  };
5639
+ var SERVICE_NOT_ENABLED_PATTERNS2 = [
5640
+ "not enabled",
5641
+ "not found",
5642
+ "No IAM Access Analyzer",
5643
+ "No SSM-managed instances",
5644
+ "requires AWS Business or Enterprise Support",
5645
+ "not available",
5646
+ "is not enabled"
5647
+ ];
5648
+ function buildServiceReminder(modules) {
5649
+ const disabledServices = [];
5650
+ for (const mod of modules) {
5651
+ const rec = SERVICE_RECOMMENDATIONS2[mod.module];
5652
+ if (!rec) continue;
5653
+ if (!mod.warnings?.length) continue;
5654
+ const hasNotEnabled = mod.warnings.some(
5655
+ (w) => SERVICE_NOT_ENABLED_PATTERNS2.some((p) => w.includes(p))
5656
+ );
5657
+ if (hasNotEnabled) {
5658
+ disabledServices.push(rec);
5659
+ }
5660
+ }
5661
+ if (disabledServices.length === 0) return "";
5662
+ const lines = [
5663
+ "",
5664
+ "\u26A1 \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A",
5665
+ ""
5666
+ ];
5667
+ for (const svc of disabledServices) {
5668
+ lines.push(`${svc.icon} ${svc.service} \u672A\u542F\u7528`);
5669
+ lines.push(` \u5F71\u54CD\uFF1A${svc.impact}`);
5670
+ lines.push(` \u5EFA\u8BAE\uFF1A${svc.action}`);
5671
+ lines.push("");
5672
+ }
5673
+ lines.push("\u542F\u7528\u4EE5\u4E0A\u670D\u52A1\u540E\u91CD\u65B0\u626B\u63CF\u53EF\u83B7\u5F97\u66F4\u5B8C\u6574\u7684\u5B89\u5168\u8BC4\u4F30\u3002");
5674
+ return lines.join("\n");
5675
+ }
5199
5676
  function summarizeResult(result) {
5200
5677
  const { summary } = result;
5201
5678
  const lines = [
@@ -5203,6 +5680,10 @@ function summarizeResult(result) {
5203
5680
  `Total findings: ${summary.totalFindings} (${summary.critical} Critical, ${summary.high} High, ${summary.medium} Medium, ${summary.low} Low)`,
5204
5681
  `Modules: ${summary.modulesSuccess} succeeded, ${summary.modulesError} errored`
5205
5682
  ];
5683
+ const reminder = buildServiceReminder(result.modules);
5684
+ if (reminder) {
5685
+ lines.push(reminder);
5686
+ }
5206
5687
  return lines.join("\n");
5207
5688
  }
5208
5689
  function summarizeScanResult(result) {
@@ -5246,7 +5727,9 @@ function createServer(defaultRegion) {
5246
5727
  new TrustedAdvisorFindingsScanner(),
5247
5728
  new ConfigRulesFindingsScanner(),
5248
5729
  new AccessAnalyzerFindingsScanner(),
5249
- new PatchComplianceFindingsScanner()
5730
+ new PatchComplianceFindingsScanner(),
5731
+ new Imdsv2EnforcementScanner(),
5732
+ new WafCoverageScanner()
5250
5733
  ];
5251
5734
  const scannerMap = /* @__PURE__ */ new Map();
5252
5735
  for (const s of allScanners) {
@@ -5302,7 +5785,9 @@ function createServer(defaultRegion) {
5302
5785
  { toolName: "scan_trusted_advisor_findings", moduleName: "trusted_advisor_findings", label: "Trusted Advisor Findings" },
5303
5786
  { toolName: "scan_config_rules_findings", moduleName: "config_rules_findings", label: "Config Rules Findings" },
5304
5787
  { toolName: "scan_access_analyzer_findings", moduleName: "access_analyzer_findings", label: "Access Analyzer Findings" },
5305
- { toolName: "scan_patch_compliance_findings", moduleName: "patch_compliance_findings", label: "Patch Compliance Findings" }
5788
+ { toolName: "scan_patch_compliance_findings", moduleName: "patch_compliance_findings", label: "Patch Compliance Findings" },
5789
+ { toolName: "scan_imdsv2_enforcement", moduleName: "imdsv2_enforcement", label: "IMDSv2 Enforcement" },
5790
+ { toolName: "scan_waf_coverage", moduleName: "waf_coverage", label: "WAF Coverage" }
5306
5791
  ];
5307
5792
  for (const { toolName, moduleName, label } of individualScanners) {
5308
5793
  server.tool(
@@ -5425,12 +5910,17 @@ function createServer(defaultRegion) {
5425
5910
  lines.push("");
5426
5911
  lines.push(`Warning: ${missingModules.length} requested module(s) not available: ${missingModules.join(", ")}`);
5427
5912
  }
5428
- return {
5429
- content: [
5430
- { type: "text", text: lines.join("\n") },
5431
- { type: "text", text: JSON.stringify(result, null, 2) }
5432
- ]
5433
- };
5913
+ const content = [
5914
+ { type: "text", text: lines.join("\n") },
5915
+ { type: "text", text: JSON.stringify(result, null, 2) }
5916
+ ];
5917
+ if (group === "hw_defense") {
5918
+ const summaryContent = content[0];
5919
+ if (summaryContent && summaryContent.type === "text") {
5920
+ summaryContent.text += "\n\n" + HW_DEFENSE_CHECKLIST;
5921
+ }
5922
+ }
5923
+ return { content };
5434
5924
  } catch (err) {
5435
5925
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
5436
5926
  }
@@ -5734,7 +6224,7 @@ Deploy this as a StackSet from your Management Account to all member accounts.`
5734
6224
  server.resource(
5735
6225
  "security-rules",
5736
6226
  "security://rules",
5737
- { description: "Describes all 17 scan modules and their check rules", mimeType: "text/markdown" },
6227
+ { description: "Describes all 19 scan modules and their check rules", mimeType: "text/markdown" },
5738
6228
  async () => ({
5739
6229
  contents: [{ uri: "security://rules", text: SECURITY_RULES_CONTENT, mimeType: "text/markdown" }]
5740
6230
  })
@@ -5781,6 +6271,25 @@ ${finding}`
5781
6271
  ]
5782
6272
  })
5783
6273
  );
6274
+ server.prompt(
6275
+ "hw_defense_checklist",
6276
+ "\u62A4\u7F51\u884C\u52A8\u5B8C\u6574\u68C0\u67E5\u6E05\u5355 \u2014 \u5305\u542B\u81EA\u52A8\u5316\u626B\u63CF\u9879\u548C\u4EBA\u5DE5\u68C0\u67E5\u9879",
6277
+ async () => ({
6278
+ messages: [
6279
+ {
6280
+ role: "user",
6281
+ content: {
6282
+ type: "text",
6283
+ text: `\u8BF7\u57FA\u4E8E\u4EE5\u4E0B\u62A4\u7F51\u884C\u52A8\u68C0\u67E5\u6E05\u5355\uFF0C\u5E2E\u52A9\u6211\u5236\u5B9A\u62A4\u7F51\u51C6\u5907\u8BA1\u5212\uFF1A
6284
+
6285
+ ${HW_DEFENSE_CHECKLIST}
6286
+
6287
+ \u81EA\u52A8\u5316\u626B\u63CF\u90E8\u5206\u8BF7\u4F7F\u7528 scan_group hw_defense \u6267\u884C\u3002\u4EE5\u4E0A\u4EBA\u5DE5\u68C0\u67E5\u9879\u8BF7\u9010\u9879\u786E\u8BA4\u5E76\u63D0\u4F9B\u5177\u4F53\u5EFA\u8BAE\u3002`
6288
+ }
6289
+ }
6290
+ ]
6291
+ })
6292
+ );
5784
6293
  return server;
5785
6294
  }
5786
6295
  async function startServer(defaultRegion) {