aws-security-mcp 0.4.0 → 0.4.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/dist/bin/aws-security-mcp.js +640 -66
- package/dist/bin/aws-security-mcp.js.map +1 -1
- package/dist/src/index.js +640 -66
- package/dist/src/index.js.map +1 -1
- package/package.json +4 -2
|
@@ -17226,6 +17226,504 @@ var TrustedAdvisorFindingsScanner = class {
|
|
|
17226
17226
|
}
|
|
17227
17227
|
};
|
|
17228
17228
|
|
|
17229
|
+
// src/scanners/config-rules-findings.ts
|
|
17230
|
+
import {
|
|
17231
|
+
ConfigServiceClient as ConfigServiceClient2,
|
|
17232
|
+
DescribeComplianceByConfigRuleCommand,
|
|
17233
|
+
GetComplianceDetailsByConfigRuleCommand
|
|
17234
|
+
} from "@aws-sdk/client-config-service";
|
|
17235
|
+
var SECURITY_RULE_PATTERNS = [
|
|
17236
|
+
"securitygroup",
|
|
17237
|
+
"security-group",
|
|
17238
|
+
"encryption",
|
|
17239
|
+
"encrypted",
|
|
17240
|
+
"public",
|
|
17241
|
+
"unrestricted",
|
|
17242
|
+
"mfa",
|
|
17243
|
+
"password",
|
|
17244
|
+
"access-key",
|
|
17245
|
+
"root",
|
|
17246
|
+
"admin",
|
|
17247
|
+
"logging",
|
|
17248
|
+
"cloudtrail",
|
|
17249
|
+
"iam",
|
|
17250
|
+
"kms",
|
|
17251
|
+
"ssl",
|
|
17252
|
+
"tls",
|
|
17253
|
+
"vpc-flow",
|
|
17254
|
+
"guardduty",
|
|
17255
|
+
"securityhub"
|
|
17256
|
+
];
|
|
17257
|
+
function ruleIsSecurityRelated(ruleName) {
|
|
17258
|
+
const lower = ruleName.toLowerCase();
|
|
17259
|
+
return SECURITY_RULE_PATTERNS.some((pat) => lower.includes(pat));
|
|
17260
|
+
}
|
|
17261
|
+
var ConfigRulesFindingsScanner = class {
|
|
17262
|
+
moduleName = "config_rules_findings";
|
|
17263
|
+
async scan(ctx) {
|
|
17264
|
+
const { region, partition, accountId } = ctx;
|
|
17265
|
+
const startMs = Date.now();
|
|
17266
|
+
const findings = [];
|
|
17267
|
+
const warnings = [];
|
|
17268
|
+
let resourcesScanned = 0;
|
|
17269
|
+
try {
|
|
17270
|
+
const client = createClient(ConfigServiceClient2, region, ctx.credentials);
|
|
17271
|
+
let nextToken;
|
|
17272
|
+
const nonCompliantRules = [];
|
|
17273
|
+
do {
|
|
17274
|
+
const resp = await client.send(
|
|
17275
|
+
new DescribeComplianceByConfigRuleCommand({ NextToken: nextToken })
|
|
17276
|
+
);
|
|
17277
|
+
for (const rule of resp.ComplianceByConfigRules ?? []) {
|
|
17278
|
+
resourcesScanned++;
|
|
17279
|
+
if (rule.Compliance?.ComplianceType === "NON_COMPLIANT") {
|
|
17280
|
+
nonCompliantRules.push(rule);
|
|
17281
|
+
}
|
|
17282
|
+
}
|
|
17283
|
+
nextToken = resp.NextToken;
|
|
17284
|
+
} while (nextToken);
|
|
17285
|
+
if (resourcesScanned === 0) {
|
|
17286
|
+
warnings.push("AWS Config is not enabled in this region or no Config Rules are defined.");
|
|
17287
|
+
return {
|
|
17288
|
+
module: this.moduleName,
|
|
17289
|
+
status: "success",
|
|
17290
|
+
warnings,
|
|
17291
|
+
resourcesScanned: 0,
|
|
17292
|
+
findingsCount: 0,
|
|
17293
|
+
scanTimeMs: Date.now() - startMs,
|
|
17294
|
+
findings: []
|
|
17295
|
+
};
|
|
17296
|
+
}
|
|
17297
|
+
for (const rule of nonCompliantRules) {
|
|
17298
|
+
const ruleName = rule.ConfigRuleName ?? "unknown";
|
|
17299
|
+
try {
|
|
17300
|
+
let detailToken;
|
|
17301
|
+
do {
|
|
17302
|
+
const detailResp = await client.send(
|
|
17303
|
+
new GetComplianceDetailsByConfigRuleCommand({
|
|
17304
|
+
ConfigRuleName: ruleName,
|
|
17305
|
+
ComplianceTypes: ["NON_COMPLIANT"],
|
|
17306
|
+
NextToken: detailToken
|
|
17307
|
+
})
|
|
17308
|
+
);
|
|
17309
|
+
for (const evalResult of detailResp.EvaluationResults ?? []) {
|
|
17310
|
+
const qualifier = evalResult.EvaluationResultIdentifier?.EvaluationResultQualifier;
|
|
17311
|
+
const resourceType = qualifier?.ResourceType ?? "AWS::Unknown";
|
|
17312
|
+
const resourceId = qualifier?.ResourceId ?? "unknown";
|
|
17313
|
+
const annotation = evalResult.Annotation;
|
|
17314
|
+
const isSecurityRule = ruleIsSecurityRelated(ruleName);
|
|
17315
|
+
const riskScore = isSecurityRule ? 7.5 : 5.5;
|
|
17316
|
+
const severity = severityFromScore(riskScore);
|
|
17317
|
+
const descParts = [`Config Rule: ${ruleName}`, `Resource Type: ${resourceType}`];
|
|
17318
|
+
if (annotation) descParts.push(`Annotation: ${annotation}`);
|
|
17319
|
+
findings.push({
|
|
17320
|
+
severity,
|
|
17321
|
+
title: `Config Rule: ${ruleName} - ${resourceType}/${resourceId} Non-Compliant`,
|
|
17322
|
+
resourceType,
|
|
17323
|
+
resourceId,
|
|
17324
|
+
resourceArn: resourceId,
|
|
17325
|
+
region,
|
|
17326
|
+
description: descParts.join(". "),
|
|
17327
|
+
impact: `Resource is non-compliant with Config Rule: ${ruleName}`,
|
|
17328
|
+
riskScore,
|
|
17329
|
+
remediationSteps: [
|
|
17330
|
+
`Review the Config Rule "${ruleName}" in the AWS Config console.`,
|
|
17331
|
+
`Check resource ${resourceId} for compliance violations.`,
|
|
17332
|
+
"Follow the rule's remediation guidance to bring the resource into compliance."
|
|
17333
|
+
],
|
|
17334
|
+
priority: priorityFromSeverity(severity),
|
|
17335
|
+
module: this.moduleName,
|
|
17336
|
+
accountId
|
|
17337
|
+
});
|
|
17338
|
+
}
|
|
17339
|
+
detailToken = detailResp.NextToken;
|
|
17340
|
+
} while (detailToken);
|
|
17341
|
+
} catch (detailErr) {
|
|
17342
|
+
const msg = detailErr instanceof Error ? detailErr.message : String(detailErr);
|
|
17343
|
+
warnings.push(`Failed to get details for rule ${ruleName}: ${msg}`);
|
|
17344
|
+
}
|
|
17345
|
+
}
|
|
17346
|
+
return {
|
|
17347
|
+
module: this.moduleName,
|
|
17348
|
+
status: "success",
|
|
17349
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
17350
|
+
resourcesScanned,
|
|
17351
|
+
findingsCount: findings.length,
|
|
17352
|
+
scanTimeMs: Date.now() - startMs,
|
|
17353
|
+
findings
|
|
17354
|
+
};
|
|
17355
|
+
} catch (err) {
|
|
17356
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17357
|
+
if (msg.includes("NoSuchConfigurationRecorder") || msg.includes("InsufficientDeliveryPolicy") || msg.includes("No Configuration Recorder")) {
|
|
17358
|
+
warnings.push("AWS Config is not enabled in this region.");
|
|
17359
|
+
return {
|
|
17360
|
+
module: this.moduleName,
|
|
17361
|
+
status: "success",
|
|
17362
|
+
warnings,
|
|
17363
|
+
resourcesScanned: 0,
|
|
17364
|
+
findingsCount: 0,
|
|
17365
|
+
scanTimeMs: Date.now() - startMs,
|
|
17366
|
+
findings: []
|
|
17367
|
+
};
|
|
17368
|
+
}
|
|
17369
|
+
return {
|
|
17370
|
+
module: this.moduleName,
|
|
17371
|
+
status: "error",
|
|
17372
|
+
error: `Config Rules scan failed: ${msg}`,
|
|
17373
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
17374
|
+
resourcesScanned,
|
|
17375
|
+
findingsCount: 0,
|
|
17376
|
+
scanTimeMs: Date.now() - startMs,
|
|
17377
|
+
findings: []
|
|
17378
|
+
};
|
|
17379
|
+
}
|
|
17380
|
+
}
|
|
17381
|
+
};
|
|
17382
|
+
|
|
17383
|
+
// src/scanners/access-analyzer-findings.ts
|
|
17384
|
+
import {
|
|
17385
|
+
AccessAnalyzerClient,
|
|
17386
|
+
ListAnalyzersCommand,
|
|
17387
|
+
ListFindingsV2Command
|
|
17388
|
+
} from "@aws-sdk/client-accessanalyzer";
|
|
17389
|
+
function findingTypeToScore(findingType) {
|
|
17390
|
+
const ft = findingType;
|
|
17391
|
+
switch (ft) {
|
|
17392
|
+
case "ExternalAccess":
|
|
17393
|
+
return 8;
|
|
17394
|
+
case "UnusedIAMRole":
|
|
17395
|
+
case "UnusedIAMUserAccessKey":
|
|
17396
|
+
case "UnusedIAMUserPassword":
|
|
17397
|
+
return 5.5;
|
|
17398
|
+
case "UnusedPermission":
|
|
17399
|
+
return 3;
|
|
17400
|
+
default:
|
|
17401
|
+
return 5.5;
|
|
17402
|
+
}
|
|
17403
|
+
}
|
|
17404
|
+
var UNUSED_FINDING_TYPES = /* @__PURE__ */ new Set([
|
|
17405
|
+
"UnusedIAMRole",
|
|
17406
|
+
"UnusedIAMUserAccessKey",
|
|
17407
|
+
"UnusedIAMUserPassword",
|
|
17408
|
+
"UnusedPermission"
|
|
17409
|
+
]);
|
|
17410
|
+
function isSecurityRelevant(findingType) {
|
|
17411
|
+
const ft = findingType;
|
|
17412
|
+
return ft === "ExternalAccess" || UNUSED_FINDING_TYPES.has(ft ?? "");
|
|
17413
|
+
}
|
|
17414
|
+
function isExternalAccess(findingType) {
|
|
17415
|
+
return findingType === "ExternalAccess";
|
|
17416
|
+
}
|
|
17417
|
+
var AccessAnalyzerFindingsScanner = class {
|
|
17418
|
+
moduleName = "access_analyzer_findings";
|
|
17419
|
+
async scan(ctx) {
|
|
17420
|
+
const { region, partition, accountId } = ctx;
|
|
17421
|
+
const startMs = Date.now();
|
|
17422
|
+
const findings = [];
|
|
17423
|
+
const warnings = [];
|
|
17424
|
+
let resourcesScanned = 0;
|
|
17425
|
+
try {
|
|
17426
|
+
const client = createClient(AccessAnalyzerClient, region, ctx.credentials);
|
|
17427
|
+
let analyzerToken;
|
|
17428
|
+
const analyzers = [];
|
|
17429
|
+
do {
|
|
17430
|
+
const resp = await client.send(
|
|
17431
|
+
new ListAnalyzersCommand({ nextToken: analyzerToken })
|
|
17432
|
+
);
|
|
17433
|
+
for (const analyzer of resp.analyzers ?? []) {
|
|
17434
|
+
if (analyzer.status === "ACTIVE") {
|
|
17435
|
+
analyzers.push(analyzer);
|
|
17436
|
+
}
|
|
17437
|
+
}
|
|
17438
|
+
analyzerToken = resp.nextToken;
|
|
17439
|
+
} while (analyzerToken);
|
|
17440
|
+
if (analyzers.length === 0) {
|
|
17441
|
+
warnings.push("No IAM Access Analyzer found. Create an analyzer to detect external access to your resources.");
|
|
17442
|
+
return {
|
|
17443
|
+
module: this.moduleName,
|
|
17444
|
+
status: "success",
|
|
17445
|
+
warnings,
|
|
17446
|
+
resourcesScanned: 0,
|
|
17447
|
+
findingsCount: 0,
|
|
17448
|
+
scanTimeMs: Date.now() - startMs,
|
|
17449
|
+
findings: []
|
|
17450
|
+
};
|
|
17451
|
+
}
|
|
17452
|
+
for (const analyzer of analyzers) {
|
|
17453
|
+
const analyzerArn = analyzer.arn ?? "unknown";
|
|
17454
|
+
let findingToken;
|
|
17455
|
+
do {
|
|
17456
|
+
const listResp = await client.send(
|
|
17457
|
+
new ListFindingsV2Command({
|
|
17458
|
+
analyzerArn,
|
|
17459
|
+
filter: {
|
|
17460
|
+
status: { eq: ["ACTIVE"] }
|
|
17461
|
+
},
|
|
17462
|
+
nextToken: findingToken
|
|
17463
|
+
})
|
|
17464
|
+
);
|
|
17465
|
+
for (const aaf of listResp.findings ?? []) {
|
|
17466
|
+
if (!isSecurityRelevant(aaf.findingType)) {
|
|
17467
|
+
continue;
|
|
17468
|
+
}
|
|
17469
|
+
resourcesScanned++;
|
|
17470
|
+
const score = findingTypeToScore(aaf.findingType);
|
|
17471
|
+
const severity = severityFromScore(score);
|
|
17472
|
+
const resourceArn = aaf.resource ?? "unknown";
|
|
17473
|
+
const resourceType = aaf.resourceType ?? "AWS::Unknown";
|
|
17474
|
+
const resourceId = resourceArn.split("/").pop() ?? resourceArn.split(":").pop() ?? "unknown";
|
|
17475
|
+
const external = isExternalAccess(aaf.findingType);
|
|
17476
|
+
const descParts = [`Resource Type: ${resourceType}`];
|
|
17477
|
+
if (aaf.resourceOwnerAccount) descParts.push(`Owner Account: ${aaf.resourceOwnerAccount}`);
|
|
17478
|
+
if (aaf.findingType) descParts.push(`Finding Type: ${aaf.findingType}`);
|
|
17479
|
+
const title = buildFindingTitle(aaf);
|
|
17480
|
+
const impact = external ? `Resource is accessible from outside the account. Type: ${aaf.findingType ?? "unknown"}` : `Unused access detected \u2014 review and remove to follow least-privilege. Type: ${aaf.findingType ?? "unknown"}`;
|
|
17481
|
+
const remediationSteps = external ? [
|
|
17482
|
+
"Review the finding in the IAM Access Analyzer console.",
|
|
17483
|
+
`Check resource ${resourceId} for unintended external access.`,
|
|
17484
|
+
"Remove or restrict the resource policy to eliminate external access."
|
|
17485
|
+
] : [
|
|
17486
|
+
"Review the finding in the IAM Access Analyzer console.",
|
|
17487
|
+
`Check resource ${resourceId} for unused access permissions.`,
|
|
17488
|
+
"Remove unused permissions, roles, or credentials to follow least-privilege."
|
|
17489
|
+
];
|
|
17490
|
+
findings.push({
|
|
17491
|
+
severity,
|
|
17492
|
+
title,
|
|
17493
|
+
resourceType: mapResourceType(resourceType),
|
|
17494
|
+
resourceId,
|
|
17495
|
+
resourceArn,
|
|
17496
|
+
region,
|
|
17497
|
+
description: descParts.join(". "),
|
|
17498
|
+
impact,
|
|
17499
|
+
riskScore: score,
|
|
17500
|
+
remediationSteps,
|
|
17501
|
+
priority: priorityFromSeverity(severity),
|
|
17502
|
+
module: this.moduleName,
|
|
17503
|
+
accountId: aaf.resourceOwnerAccount ?? accountId
|
|
17504
|
+
});
|
|
17505
|
+
}
|
|
17506
|
+
findingToken = listResp.nextToken;
|
|
17507
|
+
} while (findingToken);
|
|
17508
|
+
}
|
|
17509
|
+
return {
|
|
17510
|
+
module: this.moduleName,
|
|
17511
|
+
status: "success",
|
|
17512
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
17513
|
+
resourcesScanned,
|
|
17514
|
+
findingsCount: findings.length,
|
|
17515
|
+
scanTimeMs: Date.now() - startMs,
|
|
17516
|
+
findings
|
|
17517
|
+
};
|
|
17518
|
+
} catch (err) {
|
|
17519
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17520
|
+
return {
|
|
17521
|
+
module: this.moduleName,
|
|
17522
|
+
status: "error",
|
|
17523
|
+
error: `Access Analyzer scan failed: ${msg}`,
|
|
17524
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
17525
|
+
resourcesScanned,
|
|
17526
|
+
findingsCount: 0,
|
|
17527
|
+
scanTimeMs: Date.now() - startMs,
|
|
17528
|
+
findings: []
|
|
17529
|
+
};
|
|
17530
|
+
}
|
|
17531
|
+
}
|
|
17532
|
+
};
|
|
17533
|
+
function buildFindingTitle(finding) {
|
|
17534
|
+
const resourceType = finding.resourceType ?? "Resource";
|
|
17535
|
+
const resource = finding.resource ? finding.resource.split("/").pop() ?? finding.resource.split(":").pop() ?? finding.resource : "unknown";
|
|
17536
|
+
const label = isExternalAccess(finding.findingType) ? "external access detected" : "unused access detected";
|
|
17537
|
+
return `[Access Analyzer] ${resourceType} ${resource} \u2014 ${label}`;
|
|
17538
|
+
}
|
|
17539
|
+
function mapResourceType(aaType) {
|
|
17540
|
+
const mapping = {
|
|
17541
|
+
"AWS::S3::Bucket": "AWS::S3::Bucket",
|
|
17542
|
+
"AWS::IAM::Role": "AWS::IAM::Role",
|
|
17543
|
+
"AWS::SQS::Queue": "AWS::SQS::Queue",
|
|
17544
|
+
"AWS::Lambda::Function": "AWS::Lambda::Function",
|
|
17545
|
+
"AWS::Lambda::LayerVersion": "AWS::Lambda::LayerVersion",
|
|
17546
|
+
"AWS::KMS::Key": "AWS::KMS::Key",
|
|
17547
|
+
"AWS::SecretsManager::Secret": "AWS::SecretsManager::Secret",
|
|
17548
|
+
"AWS::SNS::Topic": "AWS::SNS::Topic",
|
|
17549
|
+
"AWS::EFS::FileSystem": "AWS::EFS::FileSystem",
|
|
17550
|
+
"AWS::RDS::DBSnapshot": "AWS::RDS::DBSnapshot",
|
|
17551
|
+
"AWS::RDS::DBClusterSnapshot": "AWS::RDS::DBClusterSnapshot",
|
|
17552
|
+
"AWS::ECR::Repository": "AWS::ECR::Repository"
|
|
17553
|
+
};
|
|
17554
|
+
return mapping[aaType] ?? aaType;
|
|
17555
|
+
}
|
|
17556
|
+
|
|
17557
|
+
// src/scanners/patch-compliance-findings.ts
|
|
17558
|
+
import {
|
|
17559
|
+
SSMClient,
|
|
17560
|
+
DescribeInstanceInformationCommand,
|
|
17561
|
+
DescribeInstancePatchStatesCommand
|
|
17562
|
+
} from "@aws-sdk/client-ssm";
|
|
17563
|
+
var PatchComplianceFindingsScanner = class {
|
|
17564
|
+
moduleName = "patch_compliance_findings";
|
|
17565
|
+
async scan(ctx) {
|
|
17566
|
+
const { region, partition, accountId } = ctx;
|
|
17567
|
+
const startMs = Date.now();
|
|
17568
|
+
const findings = [];
|
|
17569
|
+
const warnings = [];
|
|
17570
|
+
let resourcesScanned = 0;
|
|
17571
|
+
try {
|
|
17572
|
+
const client = createClient(SSMClient, region, ctx.credentials);
|
|
17573
|
+
let nextToken;
|
|
17574
|
+
const instances = [];
|
|
17575
|
+
do {
|
|
17576
|
+
const resp = await client.send(
|
|
17577
|
+
new DescribeInstanceInformationCommand({
|
|
17578
|
+
MaxResults: 50,
|
|
17579
|
+
NextToken: nextToken
|
|
17580
|
+
})
|
|
17581
|
+
);
|
|
17582
|
+
instances.push(...resp.InstanceInformationList ?? []);
|
|
17583
|
+
nextToken = resp.NextToken;
|
|
17584
|
+
} while (nextToken);
|
|
17585
|
+
if (instances.length === 0) {
|
|
17586
|
+
warnings.push("No SSM-managed instances found in this region.");
|
|
17587
|
+
return {
|
|
17588
|
+
module: this.moduleName,
|
|
17589
|
+
status: "success",
|
|
17590
|
+
warnings,
|
|
17591
|
+
resourcesScanned: 0,
|
|
17592
|
+
findingsCount: 0,
|
|
17593
|
+
scanTimeMs: Date.now() - startMs,
|
|
17594
|
+
findings: []
|
|
17595
|
+
};
|
|
17596
|
+
}
|
|
17597
|
+
resourcesScanned = instances.length;
|
|
17598
|
+
const instanceIds = instances.map((i) => i.InstanceId).filter(Boolean);
|
|
17599
|
+
const patchStateMap = /* @__PURE__ */ new Map();
|
|
17600
|
+
for (let i = 0; i < instanceIds.length; i += 50) {
|
|
17601
|
+
const batch = instanceIds.slice(i, i + 50);
|
|
17602
|
+
let patchToken;
|
|
17603
|
+
do {
|
|
17604
|
+
const patchResp = await client.send(
|
|
17605
|
+
new DescribeInstancePatchStatesCommand({
|
|
17606
|
+
InstanceIds: batch,
|
|
17607
|
+
NextToken: patchToken
|
|
17608
|
+
})
|
|
17609
|
+
);
|
|
17610
|
+
for (const ps of patchResp.InstancePatchStates ?? []) {
|
|
17611
|
+
if (ps.InstanceId) {
|
|
17612
|
+
patchStateMap.set(ps.InstanceId, ps);
|
|
17613
|
+
}
|
|
17614
|
+
}
|
|
17615
|
+
patchToken = patchResp.NextToken;
|
|
17616
|
+
} while (patchToken);
|
|
17617
|
+
}
|
|
17618
|
+
for (const instance of instances) {
|
|
17619
|
+
const instanceId = instance.InstanceId ?? "unknown";
|
|
17620
|
+
const platform = instance.PlatformName ?? instance.PlatformType ?? "unknown";
|
|
17621
|
+
const instanceArn = `arn:${partition}:ec2:${region}:${accountId}:instance/${instanceId}`;
|
|
17622
|
+
const patchState = patchStateMap.get(instanceId);
|
|
17623
|
+
if (!patchState) {
|
|
17624
|
+
const riskScore2 = 3;
|
|
17625
|
+
const severity2 = severityFromScore(riskScore2);
|
|
17626
|
+
findings.push({
|
|
17627
|
+
severity: severity2,
|
|
17628
|
+
title: `Instance ${instanceId} has no patch compliance data`,
|
|
17629
|
+
resourceType: "AWS::EC2::Instance",
|
|
17630
|
+
resourceId: instanceId,
|
|
17631
|
+
resourceArn: instanceArn,
|
|
17632
|
+
region,
|
|
17633
|
+
description: `Instance ${instanceId} (${platform}) is managed by SSM but has no patch compliance data. Patch Manager may not be configured for this instance.`,
|
|
17634
|
+
impact: "Patch compliance status is unknown \u2014 vulnerabilities may exist.",
|
|
17635
|
+
riskScore: riskScore2,
|
|
17636
|
+
remediationSteps: [
|
|
17637
|
+
"Configure AWS Systems Manager Patch Manager for this instance.",
|
|
17638
|
+
"Create a patch baseline and maintenance window.",
|
|
17639
|
+
"Run a patch scan to establish compliance status."
|
|
17640
|
+
],
|
|
17641
|
+
priority: priorityFromSeverity(severity2),
|
|
17642
|
+
module: this.moduleName,
|
|
17643
|
+
accountId
|
|
17644
|
+
});
|
|
17645
|
+
continue;
|
|
17646
|
+
}
|
|
17647
|
+
const missingCount = patchState.MissingCount ?? 0;
|
|
17648
|
+
const failedCount = patchState.FailedCount ?? 0;
|
|
17649
|
+
const criticalNonCompliantCount = patchState.CriticalNonCompliantCount ?? 0;
|
|
17650
|
+
const securityNonCompliantCount = patchState.SecurityNonCompliantCount ?? 0;
|
|
17651
|
+
const otherNonCompliantCount = patchState.OtherNonCompliantCount ?? 0;
|
|
17652
|
+
const lastScanTime = patchState.OperationEndTime?.toISOString() ?? "unknown";
|
|
17653
|
+
if (missingCount === 0 && failedCount === 0 && criticalNonCompliantCount === 0 && securityNonCompliantCount === 0 && otherNonCompliantCount === 0) {
|
|
17654
|
+
continue;
|
|
17655
|
+
}
|
|
17656
|
+
let riskScore;
|
|
17657
|
+
if (criticalNonCompliantCount > 0 || securityNonCompliantCount > 0 || failedCount > 0) {
|
|
17658
|
+
riskScore = 7.5;
|
|
17659
|
+
} else if (otherNonCompliantCount > 0) {
|
|
17660
|
+
riskScore = 5.5;
|
|
17661
|
+
} else {
|
|
17662
|
+
riskScore = 5.5;
|
|
17663
|
+
}
|
|
17664
|
+
const severity = severityFromScore(riskScore);
|
|
17665
|
+
const titleParts = [];
|
|
17666
|
+
if (missingCount > 0) titleParts.push(`${missingCount} missing`);
|
|
17667
|
+
if (failedCount > 0) titleParts.push(`${failedCount} failed`);
|
|
17668
|
+
if (criticalNonCompliantCount > 0) titleParts.push(`${criticalNonCompliantCount} critical non-compliant`);
|
|
17669
|
+
if (securityNonCompliantCount > 0) titleParts.push(`${securityNonCompliantCount} security non-compliant`);
|
|
17670
|
+
if (otherNonCompliantCount > 0) titleParts.push(`${otherNonCompliantCount} other non-compliant`);
|
|
17671
|
+
const descParts = [
|
|
17672
|
+
`Instance: ${instanceId}`,
|
|
17673
|
+
`Platform: ${platform}`,
|
|
17674
|
+
`Missing patches: ${missingCount}`,
|
|
17675
|
+
`Failed patches: ${failedCount}`,
|
|
17676
|
+
`Critical non-compliant: ${criticalNonCompliantCount}`,
|
|
17677
|
+
`Security non-compliant: ${securityNonCompliantCount}`,
|
|
17678
|
+
`Other non-compliant: ${otherNonCompliantCount}`,
|
|
17679
|
+
`Last scan: ${lastScanTime}`
|
|
17680
|
+
];
|
|
17681
|
+
findings.push({
|
|
17682
|
+
severity,
|
|
17683
|
+
title: `Instance ${instanceId} has ${titleParts.join(", ")} patches`,
|
|
17684
|
+
resourceType: "AWS::EC2::Instance",
|
|
17685
|
+
resourceId: instanceId,
|
|
17686
|
+
resourceArn: instanceArn,
|
|
17687
|
+
region,
|
|
17688
|
+
description: descParts.join(". "),
|
|
17689
|
+
impact: `Instance has ${missingCount} missing and ${failedCount} failed patches \u2014 potential security vulnerabilities.`,
|
|
17690
|
+
riskScore,
|
|
17691
|
+
remediationSteps: [
|
|
17692
|
+
`Review patch compliance for instance ${instanceId} in the Systems Manager console.`,
|
|
17693
|
+
"Apply missing patches using a maintenance window or manual patching.",
|
|
17694
|
+
"Investigate and resolve any failed patch installations.",
|
|
17695
|
+
"Consider enabling automatic patching through Patch Manager."
|
|
17696
|
+
],
|
|
17697
|
+
priority: priorityFromSeverity(severity),
|
|
17698
|
+
module: this.moduleName,
|
|
17699
|
+
accountId
|
|
17700
|
+
});
|
|
17701
|
+
}
|
|
17702
|
+
return {
|
|
17703
|
+
module: this.moduleName,
|
|
17704
|
+
status: "success",
|
|
17705
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
17706
|
+
resourcesScanned,
|
|
17707
|
+
findingsCount: findings.length,
|
|
17708
|
+
scanTimeMs: Date.now() - startMs,
|
|
17709
|
+
findings
|
|
17710
|
+
};
|
|
17711
|
+
} catch (err) {
|
|
17712
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17713
|
+
return {
|
|
17714
|
+
module: this.moduleName,
|
|
17715
|
+
status: "error",
|
|
17716
|
+
error: `Patch compliance scan failed: ${msg}`,
|
|
17717
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
17718
|
+
resourcesScanned,
|
|
17719
|
+
findingsCount: 0,
|
|
17720
|
+
scanTimeMs: Date.now() - startMs,
|
|
17721
|
+
findings: []
|
|
17722
|
+
};
|
|
17723
|
+
}
|
|
17724
|
+
}
|
|
17725
|
+
};
|
|
17726
|
+
|
|
17229
17727
|
// src/tools/report-tool.ts
|
|
17230
17728
|
var SEVERITY_ICON = {
|
|
17231
17729
|
CRITICAL: "\u{1F534}",
|
|
@@ -17737,10 +18235,21 @@ function sharedCss() {
|
|
|
17737
18235
|
.finding-card-body p{color:#cbd5e1;font-size:13px;margin-bottom:4px}
|
|
17738
18236
|
.finding-card-body ol{padding-left:20px}
|
|
17739
18237
|
.finding-card-body li{color:#cbd5e1;font-size:13px;margin-bottom:2px}
|
|
18238
|
+
.rec-fold{background:#1e293b;border:1px solid #334155;border-radius:8px;margin-bottom:16px;overflow:hidden}
|
|
18239
|
+
.rec-fold>summary{cursor:pointer;padding:16px 20px;display:flex;align-items:center;gap:12px;list-style:none;user-select:none}
|
|
18240
|
+
.rec-fold>summary::-webkit-details-marker{display:none}
|
|
18241
|
+
.rec-fold>summary::marker{content:""}
|
|
18242
|
+
.rec-fold>summary::after{content:"\\25B6";font-size:12px;color:#64748b;flex-shrink:0;transition:transform 0.2s;margin-left:auto}
|
|
18243
|
+
.rec-fold[open]>summary::after{transform:rotate(90deg)}
|
|
18244
|
+
.rec-fold[open]>summary{border-bottom:1px solid #334155}
|
|
18245
|
+
.rec-body{padding:12px 20px 16px}
|
|
18246
|
+
.rec-body ol{padding-left:24px}
|
|
18247
|
+
.rec-body li{margin-bottom:8px;color:#cbd5e1;font-size:13px}
|
|
18248
|
+
.rec-body .badge{margin-right:6px;vertical-align:middle}
|
|
17740
18249
|
@media print{
|
|
17741
18250
|
body{background:#fff;color:#1e293b;-webkit-print-color-adjust:exact;print-color-adjust:exact}
|
|
17742
18251
|
.container{max-width:100%;padding:20px}
|
|
17743
|
-
.card,.score-card,.stat-card,.chart-box,.finding-fold,.top5-card,.trend-chart,.category-fold,.module-fold,.finding-card{background:#fff;border:1px solid #e2e8f0}
|
|
18252
|
+
.card,.score-card,.stat-card,.chart-box,.finding-fold,.top5-card,.trend-chart,.category-fold,.module-fold,.finding-card,.rec-fold{background:#fff;border:1px solid #e2e8f0}
|
|
17744
18253
|
.badge{border:1px solid}
|
|
17745
18254
|
header{border-bottom-color:#e2e8f0}
|
|
17746
18255
|
h2{border-bottom-color:#e2e8f0}
|
|
@@ -17758,10 +18267,10 @@ function sharedCss() {
|
|
|
17758
18267
|
.finding-title-text{color:#1e293b}
|
|
17759
18268
|
.finding-resource{color:#64748b}
|
|
17760
18269
|
.check-findings li{color:#64748b}
|
|
17761
|
-
.finding-fold,.top5-card,.category-fold,.module-fold,.finding-card{break-inside:avoid}
|
|
18270
|
+
.finding-fold,.top5-card,.category-fold,.module-fold,.finding-card,.rec-fold{break-inside:avoid}
|
|
17762
18271
|
.check-item{break-inside:avoid}
|
|
17763
18272
|
svg text{fill:#1e293b !important}
|
|
17764
|
-
.finding-fold[open]>summary,.category-fold[open]>summary,.module-fold[open]>summary{border-bottom-color:#e2e8f0}
|
|
18273
|
+
.finding-fold[open]>summary,.category-fold[open]>summary,.module-fold[open]>summary,.rec-fold[open]>summary{border-bottom-color:#e2e8f0}
|
|
17765
18274
|
details{display:block}
|
|
17766
18275
|
details>summary{display:block}
|
|
17767
18276
|
details>:not(summary){display:block !important}
|
|
@@ -18000,8 +18509,6 @@ ${rest}
|
|
|
18000
18509
|
return b[1].length - a[1].length;
|
|
18001
18510
|
});
|
|
18002
18511
|
findingsHtml = moduleEntries.map(([modName, modFindings]) => {
|
|
18003
|
-
const hasCritHigh = modFindings.some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
|
|
18004
|
-
const openAttr = hasCritHigh ? " open" : "";
|
|
18005
18512
|
const sevCounts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
18006
18513
|
for (const f of modFindings) sevCounts[f.severity]++;
|
|
18007
18514
|
const badges = SEVERITY_ORDER2.filter((sev) => sevCounts[sev] > 0).map((sev) => `<span class="badge badge-${sev.toLowerCase()}">${sevCounts[sev]} ${sev.charAt(0) + sev.slice(1).toLowerCase()}</span>`).join(" ");
|
|
@@ -18011,19 +18518,12 @@ ${rest}
|
|
|
18011
18518
|
findings.sort((a, b) => b.riskScore - a.riskScore);
|
|
18012
18519
|
const emoji3 = SEV_EMOJI[sev] ?? "";
|
|
18013
18520
|
const label = sev.charAt(0) + sev.slice(1).toLowerCase();
|
|
18014
|
-
const isCritHigh = sev === "CRITICAL" || sev === "HIGH";
|
|
18015
|
-
if (isCritHigh) {
|
|
18016
|
-
return `<div class="severity-group">
|
|
18017
|
-
<h4>${emoji3} ${label} (${findings.length})</h4>
|
|
18018
|
-
${renderCards(findings)}
|
|
18019
|
-
</div>`;
|
|
18020
|
-
}
|
|
18021
18521
|
return `<details class="severity-group-fold">
|
|
18022
|
-
<summary><h4>${emoji3} ${label} (${findings.length})
|
|
18522
|
+
<summary><h4>${emoji3} ${label} (${findings.length})</h4></summary>
|
|
18023
18523
|
${renderCards(findings)}
|
|
18024
18524
|
</details>`;
|
|
18025
18525
|
}).filter(Boolean).join("\n");
|
|
18026
|
-
return `<details class="module-fold"
|
|
18526
|
+
return `<details class="module-fold">
|
|
18027
18527
|
<summary>
|
|
18028
18528
|
<h3>🔒 ${esc2(modName)} (${modFindings.length})</h3>
|
|
18029
18529
|
<span class="module-badges">${badges}</span>
|
|
@@ -18054,17 +18554,43 @@ ${rest}
|
|
|
18054
18554
|
).join("\n");
|
|
18055
18555
|
let recsHtml = "";
|
|
18056
18556
|
if (summary.totalFindings > 0) {
|
|
18057
|
-
const
|
|
18058
|
-
const
|
|
18059
|
-
const pc = f.priority.toLowerCase();
|
|
18557
|
+
const recMap = /* @__PURE__ */ new Map();
|
|
18558
|
+
for (const f of allFindings) {
|
|
18060
18559
|
const rem = f.remediationSteps[0] ?? "Review and remediate.";
|
|
18061
|
-
|
|
18062
|
-
|
|
18560
|
+
const existing = recMap.get(rem);
|
|
18561
|
+
if (existing) {
|
|
18562
|
+
existing.count++;
|
|
18563
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
|
|
18564
|
+
existing.severity = f.severity;
|
|
18565
|
+
}
|
|
18566
|
+
} else {
|
|
18567
|
+
recMap.set(rem, { text: rem, severity: f.severity, count: 1 });
|
|
18568
|
+
}
|
|
18569
|
+
}
|
|
18570
|
+
const uniqueRecs = [...recMap.values()].sort((a, b) => {
|
|
18571
|
+
const sevDiff = SEVERITY_ORDER2.indexOf(a.severity) - SEVERITY_ORDER2.indexOf(b.severity);
|
|
18572
|
+
if (sevDiff !== 0) return sevDiff;
|
|
18573
|
+
return b.count - a.count;
|
|
18574
|
+
});
|
|
18575
|
+
const renderRec = (r) => {
|
|
18576
|
+
const sev = r.severity.toLowerCase();
|
|
18577
|
+
const countLabel = r.count > 1 ? ` (× ${r.count})` : "";
|
|
18578
|
+
return `<li><span class="badge badge-${esc2(sev)}">${esc2(r.severity)}</span> ${esc2(r.text)}${countLabel}</li>`;
|
|
18579
|
+
};
|
|
18580
|
+
const TOP_N = 10;
|
|
18581
|
+
const topItems = uniqueRecs.slice(0, TOP_N).map(renderRec).join("\n");
|
|
18582
|
+
const remaining = uniqueRecs.slice(TOP_N);
|
|
18583
|
+
const moreHtml = remaining.length > 0 ? `
|
|
18584
|
+
<details><summary>Show ${remaining.length} more…</summary>
|
|
18585
|
+
${remaining.map(renderRec).join("\n")}
|
|
18586
|
+
</details>` : "";
|
|
18063
18587
|
recsHtml = `
|
|
18064
|
-
<
|
|
18065
|
-
<h2>Recommendations (
|
|
18066
|
-
<
|
|
18067
|
-
|
|
18588
|
+
<details class="rec-fold">
|
|
18589
|
+
<summary><h2 style="margin:0;border:0;display:inline">Recommendations (${uniqueRecs.length} unique)</h2></summary>
|
|
18590
|
+
<div class="rec-body">
|
|
18591
|
+
<ol>${topItems}${moreHtml}</ol>
|
|
18592
|
+
</div>
|
|
18593
|
+
</details>`;
|
|
18068
18594
|
}
|
|
18069
18595
|
return `<!DOCTYPE html>
|
|
18070
18596
|
<html lang="en">
|
|
@@ -18179,8 +18705,6 @@ function generateMlps3HtmlReport(scanResults, history) {
|
|
|
18179
18705
|
const catUnknown = categoryResults.filter(
|
|
18180
18706
|
(r) => r.status === "unknown"
|
|
18181
18707
|
).length;
|
|
18182
|
-
const hasFailure = catFail > 0;
|
|
18183
|
-
const openAttr = hasFailure ? " open" : "";
|
|
18184
18708
|
const byId = /* @__PURE__ */ new Map();
|
|
18185
18709
|
for (const r of categoryResults) {
|
|
18186
18710
|
const existing = byId.get(r.check.id) ?? [];
|
|
@@ -18188,6 +18712,9 @@ function generateMlps3HtmlReport(scanResults, history) {
|
|
|
18188
18712
|
byId.set(r.check.id, existing);
|
|
18189
18713
|
}
|
|
18190
18714
|
const groups = [...byId.entries()].map(([checkId, checkResults]) => {
|
|
18715
|
+
const grpPass = checkResults.filter((r) => r.status === "pass").length;
|
|
18716
|
+
const grpFail = checkResults.filter((r) => r.status === "fail").length;
|
|
18717
|
+
const grpUnknown = checkResults.filter((r) => r.status === "unknown").length;
|
|
18191
18718
|
const items = checkResults.map((r) => {
|
|
18192
18719
|
const icon = r.status === "pass" ? "✔" : r.status === "fail" ? "✘" : "⚠";
|
|
18193
18720
|
const cls = `check-${r.status}`;
|
|
@@ -18206,15 +18733,21 @@ function generateMlps3HtmlReport(scanResults, history) {
|
|
|
18206
18733
|
}
|
|
18207
18734
|
return `<div class="check-item ${cls}"><span class="check-icon">${icon}</span><span class="check-name">${esc2(r.check.name)}${label}</span></div>${findingsHtml}`;
|
|
18208
18735
|
}).join("\n");
|
|
18209
|
-
|
|
18210
|
-
${
|
|
18736
|
+
const statusBadges = [
|
|
18737
|
+
grpPass > 0 ? `<span class="category-stat-pass">✓ ${grpPass}</span>` : "",
|
|
18738
|
+
grpFail > 0 ? `<span class="category-stat-fail">✗ ${grpFail}</span>` : "",
|
|
18739
|
+
grpUnknown > 0 ? `<span class="category-stat-unknown">? ${grpUnknown}</span>` : ""
|
|
18740
|
+
].filter(Boolean).join(" ");
|
|
18741
|
+
return `<details class="severity-group-fold"><summary><h4>${esc2(checkId)} ${esc2(checkResults[0].check.name)} <span class="category-stats">${statusBadges}</span></h4></summary>
|
|
18742
|
+
${items}
|
|
18743
|
+
</details>`;
|
|
18211
18744
|
}).join("\n");
|
|
18212
18745
|
const statsHtml = [
|
|
18213
18746
|
catPass > 0 ? `<span class="category-stat-pass">✓ ${catPass}</span>` : "",
|
|
18214
18747
|
catFail > 0 ? `<span class="category-stat-fail">✗ ${catFail}</span>` : "",
|
|
18215
18748
|
catUnknown > 0 ? `<span class="category-stat-unknown">? ${catUnknown}</span>` : ""
|
|
18216
18749
|
].filter(Boolean).join("");
|
|
18217
|
-
return `<details class="category-fold"
|
|
18750
|
+
return `<details class="category-fold">
|
|
18218
18751
|
<summary>
|
|
18219
18752
|
<span class="category-title">${esc2(sectionTitle)}</span>
|
|
18220
18753
|
<span class="category-stats">${statsHtml}</span>
|
|
@@ -18225,28 +18758,45 @@ ${items}`;
|
|
|
18225
18758
|
const failedResults = results.filter((r) => r.status === "fail");
|
|
18226
18759
|
let remediationHtml = "";
|
|
18227
18760
|
if (failedResults.length > 0) {
|
|
18228
|
-
const
|
|
18761
|
+
const mlpsRecMap = /* @__PURE__ */ new Map();
|
|
18229
18762
|
for (const r of failedResults) {
|
|
18230
18763
|
for (const f of r.relatedFindings) {
|
|
18231
|
-
const
|
|
18232
|
-
|
|
18233
|
-
|
|
18764
|
+
const rem = f.remediationSteps[0] ?? "Review and remediate.";
|
|
18765
|
+
const existing = mlpsRecMap.get(rem);
|
|
18766
|
+
if (existing) {
|
|
18767
|
+
existing.count++;
|
|
18768
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
|
|
18769
|
+
existing.severity = f.severity;
|
|
18770
|
+
}
|
|
18771
|
+
} else {
|
|
18772
|
+
mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1 });
|
|
18234
18773
|
}
|
|
18235
18774
|
}
|
|
18236
18775
|
}
|
|
18237
|
-
const
|
|
18238
|
-
|
|
18239
|
-
|
|
18240
|
-
|
|
18241
|
-
|
|
18242
|
-
|
|
18243
|
-
|
|
18244
|
-
|
|
18776
|
+
const mlpsUniqueRecs = [...mlpsRecMap.values()].sort((a, b) => {
|
|
18777
|
+
const sevDiff = SEVERITY_ORDER2.indexOf(a.severity) - SEVERITY_ORDER2.indexOf(b.severity);
|
|
18778
|
+
if (sevDiff !== 0) return sevDiff;
|
|
18779
|
+
return b.count - a.count;
|
|
18780
|
+
});
|
|
18781
|
+
const renderMlpsRec = (r) => {
|
|
18782
|
+
const sev = r.severity.toLowerCase();
|
|
18783
|
+
const countLabel = r.count > 1 ? ` (× ${r.count})` : "";
|
|
18784
|
+
return `<li><span class="badge badge-${esc2(sev)}">${esc2(r.severity)}</span> ${esc2(r.text)}${countLabel}</li>`;
|
|
18785
|
+
};
|
|
18786
|
+
const MLPS_TOP_N = 10;
|
|
18787
|
+
const mlpsTopItems = mlpsUniqueRecs.slice(0, MLPS_TOP_N).map(renderMlpsRec).join("\n");
|
|
18788
|
+
const mlpsRemaining = mlpsUniqueRecs.slice(MLPS_TOP_N);
|
|
18789
|
+
const mlpsMoreHtml = mlpsRemaining.length > 0 ? `
|
|
18790
|
+
<details><summary>\u663E\u793A\u5176\u4F59 ${mlpsRemaining.length} \u9879…</summary>
|
|
18791
|
+
${mlpsRemaining.map(renderMlpsRec).join("\n")}
|
|
18792
|
+
</details>` : "";
|
|
18245
18793
|
remediationHtml = `
|
|
18246
|
-
<
|
|
18247
|
-
<h2>\u5EFA\u8BAE\u6574\u6539\u9879\uFF08\
|
|
18248
|
-
<
|
|
18249
|
-
|
|
18794
|
+
<details class="rec-fold">
|
|
18795
|
+
<summary><h2 style="margin:0;border:0;display:inline">\u5EFA\u8BAE\u6574\u6539\u9879\uFF08${mlpsUniqueRecs.length} \u9879\u53BB\u91CD\uFF09</h2></summary>
|
|
18796
|
+
<div class="rec-body">
|
|
18797
|
+
<ol>${mlpsTopItems}${mlpsMoreHtml}</ol>
|
|
18798
|
+
</div>
|
|
18799
|
+
</details>`;
|
|
18250
18800
|
}
|
|
18251
18801
|
const passRateColor = percent >= 80 ? "#22c55e" : percent >= 50 ? "#eab308" : "#ef4444";
|
|
18252
18802
|
const unknownNote = unknownCount > 0 ? `<div style="color:#94a3b8;font-size:12px;margin-top:8px">\uFF08\u672A\u68C0\u67E5\u9879\u4E0D\u8BA1\u5165\u901A\u8FC7\u7387\uFF09</div>` : "";
|
|
@@ -18405,13 +18955,13 @@ var SCAN_GROUPS = {
|
|
|
18405
18955
|
mlps3_precheck: {
|
|
18406
18956
|
name: "\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0",
|
|
18407
18957
|
description: "GB/T 22239-2019 \u7B49\u4FDD\u4E09\u7EA7 AWS \u4E91\u79DF\u6237\u5C42\u914D\u7F6E\u68C0\u67E5",
|
|
18408
|
-
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"],
|
|
18958
|
+
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"],
|
|
18409
18959
|
reportType: "mlps3"
|
|
18410
18960
|
},
|
|
18411
18961
|
hw_defense: {
|
|
18412
18962
|
name: "\u62A4\u7F51\u84DD\u961F\u52A0\u56FA",
|
|
18413
18963
|
description: "\u62A4\u7F51\u524D\u5B89\u5168\u81EA\u67E5 \u2014 \u653B\u51FB\u9762+\u5F31\u70B9\u8BC4\u4F30",
|
|
18414
|
-
modules: ["service_detection", "secret_exposure", "network_reachability", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings", "inspector_findings"],
|
|
18964
|
+
modules: ["service_detection", "secret_exposure", "network_reachability", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings", "inspector_findings", "config_rules_findings", "access_analyzer_findings", "patch_compliance_findings"],
|
|
18415
18965
|
findingsFilter: {
|
|
18416
18966
|
guardDutyTypes: ["Backdoor", "Trojan", "PenTest", "CryptoCurrency"],
|
|
18417
18967
|
minSeverity: "MEDIUM"
|
|
@@ -18420,16 +18970,11 @@ var SCAN_GROUPS = {
|
|
|
18420
18970
|
exposure: {
|
|
18421
18971
|
name: "\u516C\u7F51\u66B4\u9732\u9762\u8BC4\u4F30",
|
|
18422
18972
|
description: "\u8BC4\u4F30\u516C\u7F51\u53EF\u8FBE\u7684\u8D44\u6E90\u548C\u7AEF\u53E3",
|
|
18423
|
-
modules: ["network_reachability", "dns_dangling", "public_access_verify", "ssl_certificate", "security_hub_findings"],
|
|
18973
|
+
modules: ["network_reachability", "dns_dangling", "public_access_verify", "ssl_certificate", "security_hub_findings", "access_analyzer_findings"],
|
|
18424
18974
|
findingsFilter: {
|
|
18425
18975
|
securityHubCategories: ["network", "public", "exposure", "port"]
|
|
18426
18976
|
}
|
|
18427
18977
|
},
|
|
18428
|
-
pre_launch: {
|
|
18429
|
-
name: "\u751F\u4EA7\u4E0A\u7EBF\u524D\u68C0\u67E5",
|
|
18430
|
-
description: "\u4E0A\u7EBF\u524D\u5168\u9762\u5B89\u5168\u8BC4\u4F30",
|
|
18431
|
-
modules: ["ALL"]
|
|
18432
|
-
},
|
|
18433
18978
|
data_encryption: {
|
|
18434
18979
|
name: "\u6570\u636E\u52A0\u5BC6\u5BA1\u8BA1",
|
|
18435
18980
|
description: "\u5168\u9762\u68C0\u67E5\u5B58\u50A8\u548C\u4F20\u8F93\u52A0\u5BC6\u72B6\u6001",
|
|
@@ -18441,7 +18986,7 @@ var SCAN_GROUPS = {
|
|
|
18441
18986
|
least_privilege: {
|
|
18442
18987
|
name: "\u6700\u5C0F\u6743\u9650\u5BA1\u8BA1",
|
|
18443
18988
|
description: "IAM \u6743\u9650\u6700\u5C0F\u5316\u8BC4\u4F30",
|
|
18444
|
-
modules: ["iam_privilege_escalation", "security_hub_findings"],
|
|
18989
|
+
modules: ["iam_privilege_escalation", "security_hub_findings", "access_analyzer_findings"],
|
|
18445
18990
|
findingsFilter: {
|
|
18446
18991
|
securityHubCategories: ["IAM", "iam", "access", "privilege"]
|
|
18447
18992
|
}
|
|
@@ -18469,25 +19014,20 @@ var SCAN_GROUPS = {
|
|
|
18469
19014
|
description: "\u68C0\u67E5\u5FC5\u9700\u6807\u7B7E",
|
|
18470
19015
|
modules: ["tag_compliance"]
|
|
18471
19016
|
},
|
|
18472
|
-
public_access_verify: {
|
|
18473
|
-
name: "\u516C\u7F51\u53EF\u8FBE\u6027\u9A8C\u8BC1",
|
|
18474
|
-
description: "\u9A8C\u8BC1\u6807\u8BB0\u4E3A\u516C\u5F00\u7684\u8D44\u6E90\u662F\u5426\u771F\u6B63\u53EF\u4ECE\u4E92\u8054\u7F51\u8BBF\u95EE",
|
|
18475
|
-
modules: ["public_access_verify"]
|
|
18476
|
-
},
|
|
18477
19017
|
new_account_baseline: {
|
|
18478
19018
|
name: "\u65B0\u8D26\u6237\u57FA\u7EBF\u68C0\u67E5",
|
|
18479
19019
|
description: "\u65B0 AWS \u8D26\u6237\u5B89\u5168\u57FA\u7EBF",
|
|
18480
|
-
modules: ["service_detection", "secret_exposure", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings"]
|
|
19020
|
+
modules: ["service_detection", "secret_exposure", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings", "access_analyzer_findings"]
|
|
18481
19021
|
},
|
|
18482
19022
|
aggregation: {
|
|
18483
19023
|
name: "\u5B89\u5168\u670D\u52A1\u805A\u5408",
|
|
18484
19024
|
description: "\u4ECE Security Hub / GuardDuty / Inspector / Trusted Advisor \u805A\u5408\u6240\u6709\u5B89\u5168\u53D1\u73B0",
|
|
18485
|
-
modules: ["security_hub_findings", "guardduty_findings", "inspector_findings", "trusted_advisor_findings"]
|
|
19025
|
+
modules: ["security_hub_findings", "guardduty_findings", "inspector_findings", "trusted_advisor_findings", "config_rules_findings", "access_analyzer_findings", "patch_compliance_findings"]
|
|
18486
19026
|
}
|
|
18487
19027
|
};
|
|
18488
19028
|
|
|
18489
19029
|
// src/resources/index.ts
|
|
18490
|
-
var SECURITY_RULES_CONTENT = `# AWS Security Scan Modules & Rules
|
|
19030
|
+
var SECURITY_RULES_CONTENT = `# AWS Security Scan Modules & Rules (17 modules)
|
|
18491
19031
|
|
|
18492
19032
|
## 1. Service Detection (service_detection)
|
|
18493
19033
|
Detects which AWS security services are enabled and assesses overall security maturity.
|
|
@@ -18556,6 +19096,31 @@ Finds unused/idle AWS resources (unattached EBS volumes, unused EIPs, stopped in
|
|
|
18556
19096
|
|
|
18557
19097
|
## 14. Disaster Recovery (disaster_recovery)
|
|
18558
19098
|
Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.
|
|
19099
|
+
|
|
19100
|
+
## 15. Config Rules Findings (config_rules_findings)
|
|
19101
|
+
Pulls non-compliant AWS Config Rule evaluation results.
|
|
19102
|
+
- Lists all Config Rules and their compliance status.
|
|
19103
|
+
- For NON_COMPLIANT rules, retrieves specific non-compliant resources.
|
|
19104
|
+
- Security-related rules (encryption, IAM, public access, etc.) mapped to HIGH severity (7.5).
|
|
19105
|
+
- Other non-compliant rules mapped to MEDIUM severity (5.5).
|
|
19106
|
+
- Gracefully handles regions where AWS Config is not enabled.
|
|
19107
|
+
|
|
19108
|
+
## 16. IAM Access Analyzer Findings (access_analyzer_findings)
|
|
19109
|
+
Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account.
|
|
19110
|
+
- Lists active analyzers (ACCOUNT or ORGANIZATION type).
|
|
19111
|
+
- Retrieves ACTIVE findings showing external access to resources.
|
|
19112
|
+
- Covers S3 buckets, IAM roles, SQS queues, Lambda functions, KMS keys, and more.
|
|
19113
|
+
- Severity mapped: CRITICAL \u2192 9.5, HIGH \u2192 8.0, MEDIUM \u2192 5.5, LOW \u2192 3.0.
|
|
19114
|
+
- Returns warning if no analyzer is configured.
|
|
19115
|
+
|
|
19116
|
+
## 17. SSM Patch Compliance (patch_compliance_findings)
|
|
19117
|
+
Checks patch compliance status for SSM-managed instances.
|
|
19118
|
+
- Lists all managed instances via SSM.
|
|
19119
|
+
- Retrieves patch compliance state for each instance.
|
|
19120
|
+
- Missing security patches or failed patches \u2192 HIGH (7.5).
|
|
19121
|
+
- Missing non-security patches \u2192 MEDIUM (5.5).
|
|
19122
|
+
- Instances without patch data flagged as LOW (3.0) for visibility.
|
|
19123
|
+
- Includes platform info, missing/failed counts, and last scan time.
|
|
18559
19124
|
`;
|
|
18560
19125
|
var RISK_SCORING_CONTENT = `# Risk Scoring Model
|
|
18561
19126
|
|
|
@@ -18617,7 +19182,10 @@ var MODULE_DESCRIPTIONS = {
|
|
|
18617
19182
|
security_hub_findings: "Aggregates active findings from AWS Security Hub \u2014 replaces individual config scanners with centralized compliance checks.",
|
|
18618
19183
|
guardduty_findings: "Aggregates threat detection findings from Amazon GuardDuty \u2014 account compromise, instance compromise, and reconnaissance.",
|
|
18619
19184
|
inspector_findings: "Aggregates vulnerability findings from Amazon Inspector \u2014 CVEs in EC2, Lambda, and container images.",
|
|
18620
|
-
trusted_advisor_findings: "Aggregates security checks from AWS Trusted Advisor \u2014 requires Business or Enterprise Support plan."
|
|
19185
|
+
trusted_advisor_findings: "Aggregates security checks from AWS Trusted Advisor \u2014 requires Business or Enterprise Support plan.",
|
|
19186
|
+
config_rules_findings: "Pulls non-compliant AWS Config Rule evaluation results \u2014 configuration compliance violations across all resource types.",
|
|
19187
|
+
access_analyzer_findings: "Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account (external principals, public access).",
|
|
19188
|
+
patch_compliance_findings: "Checks SSM Patch Manager compliance \u2014 managed instances with missing or failed security and system patches."
|
|
18621
19189
|
};
|
|
18622
19190
|
function summarizeResult(result) {
|
|
18623
19191
|
const { summary } = result;
|
|
@@ -18666,7 +19234,10 @@ function createServer(defaultRegion) {
|
|
|
18666
19234
|
new SecurityHubFindingsScanner(),
|
|
18667
19235
|
new GuardDutyFindingsScanner(),
|
|
18668
19236
|
new InspectorFindingsScanner(),
|
|
18669
|
-
new TrustedAdvisorFindingsScanner()
|
|
19237
|
+
new TrustedAdvisorFindingsScanner(),
|
|
19238
|
+
new ConfigRulesFindingsScanner(),
|
|
19239
|
+
new AccessAnalyzerFindingsScanner(),
|
|
19240
|
+
new PatchComplianceFindingsScanner()
|
|
18670
19241
|
];
|
|
18671
19242
|
const scannerMap = /* @__PURE__ */ new Map();
|
|
18672
19243
|
for (const s of allScanners) {
|
|
@@ -18719,7 +19290,10 @@ function createServer(defaultRegion) {
|
|
|
18719
19290
|
{ toolName: "scan_security_hub_findings", moduleName: "security_hub_findings", label: "Security Hub Findings" },
|
|
18720
19291
|
{ toolName: "scan_guardduty_findings", moduleName: "guardduty_findings", label: "GuardDuty Findings" },
|
|
18721
19292
|
{ toolName: "scan_inspector_findings", moduleName: "inspector_findings", label: "Inspector Findings" },
|
|
18722
|
-
{ toolName: "scan_trusted_advisor_findings", moduleName: "trusted_advisor_findings", label: "Trusted Advisor Findings" }
|
|
19293
|
+
{ toolName: "scan_trusted_advisor_findings", moduleName: "trusted_advisor_findings", label: "Trusted Advisor Findings" },
|
|
19294
|
+
{ toolName: "scan_config_rules_findings", moduleName: "config_rules_findings", label: "Config Rules Findings" },
|
|
19295
|
+
{ toolName: "scan_access_analyzer_findings", moduleName: "access_analyzer_findings", label: "Access Analyzer Findings" },
|
|
19296
|
+
{ toolName: "scan_patch_compliance_findings", moduleName: "patch_compliance_findings", label: "Patch Compliance Findings" }
|
|
18723
19297
|
];
|
|
18724
19298
|
for (const { toolName, moduleName, label } of individualScanners) {
|
|
18725
19299
|
server.tool(
|
|
@@ -18748,7 +19322,7 @@ function createServer(defaultRegion) {
|
|
|
18748
19322
|
"scan_group",
|
|
18749
19323
|
"Run a predefined group of security scanners for a specific scenario (e.g., MLPS compliance, network defense). Read-only. Supports multi-account org scanning.",
|
|
18750
19324
|
{
|
|
18751
|
-
group: external_exports.string().describe("Scan group ID: mlps3_precheck, hw_defense, exposure,
|
|
19325
|
+
group: external_exports.string().describe("Scan group ID: mlps3_precheck, hw_defense, exposure, data_encryption, least_privilege, log_integrity, disaster_recovery, idle_resources, tag_compliance, new_account_baseline, aggregation"),
|
|
18752
19326
|
region: external_exports.string().optional().describe("AWS region to scan (default: server region)"),
|
|
18753
19327
|
org_mode: external_exports.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
|
|
18754
19328
|
role_name: external_exports.string().optional().describe("IAM role name to assume in child accounts (default: AWSSecurityMCPAudit)"),
|
|
@@ -19151,7 +19725,7 @@ Deploy this as a StackSet from your Management Account to all member accounts.`
|
|
|
19151
19725
|
server.resource(
|
|
19152
19726
|
"security-rules",
|
|
19153
19727
|
"security://rules",
|
|
19154
|
-
{ description: "Describes all
|
|
19728
|
+
{ description: "Describes all 17 scan modules and their check rules", mimeType: "text/markdown" },
|
|
19155
19729
|
async () => ({
|
|
19156
19730
|
contents: [{ uri: "security://rules", text: SECURITY_RULES_CONTENT, mimeType: "text/markdown" }]
|
|
19157
19731
|
})
|