aura-security 0.4.0

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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +446 -0
  3. package/deploy/AWS-DEPLOYMENT.md +358 -0
  4. package/deploy/terraform/main.tf +362 -0
  5. package/deploy/terraform/terraform.tfvars.example +6 -0
  6. package/dist/agents/base.d.ts +44 -0
  7. package/dist/agents/base.js +96 -0
  8. package/dist/agents/index.d.ts +14 -0
  9. package/dist/agents/index.js +17 -0
  10. package/dist/agents/policy/evaluator.d.ts +15 -0
  11. package/dist/agents/policy/evaluator.js +183 -0
  12. package/dist/agents/policy/index.d.ts +12 -0
  13. package/dist/agents/policy/index.js +15 -0
  14. package/dist/agents/policy/validator.d.ts +15 -0
  15. package/dist/agents/policy/validator.js +182 -0
  16. package/dist/agents/scanners/gitleaks.d.ts +14 -0
  17. package/dist/agents/scanners/gitleaks.js +155 -0
  18. package/dist/agents/scanners/grype.d.ts +14 -0
  19. package/dist/agents/scanners/grype.js +109 -0
  20. package/dist/agents/scanners/index.d.ts +15 -0
  21. package/dist/agents/scanners/index.js +27 -0
  22. package/dist/agents/scanners/npm-audit.d.ts +13 -0
  23. package/dist/agents/scanners/npm-audit.js +129 -0
  24. package/dist/agents/scanners/semgrep.d.ts +14 -0
  25. package/dist/agents/scanners/semgrep.js +131 -0
  26. package/dist/agents/scanners/trivy.d.ts +14 -0
  27. package/dist/agents/scanners/trivy.js +122 -0
  28. package/dist/agents/types.d.ts +137 -0
  29. package/dist/agents/types.js +91 -0
  30. package/dist/auditor/index.d.ts +3 -0
  31. package/dist/auditor/index.js +2 -0
  32. package/dist/auditor/pipeline.d.ts +19 -0
  33. package/dist/auditor/pipeline.js +240 -0
  34. package/dist/auditor/validator.d.ts +17 -0
  35. package/dist/auditor/validator.js +58 -0
  36. package/dist/aura/client.d.ts +29 -0
  37. package/dist/aura/client.js +125 -0
  38. package/dist/aura/index.d.ts +4 -0
  39. package/dist/aura/index.js +2 -0
  40. package/dist/aura/server.d.ts +45 -0
  41. package/dist/aura/server.js +343 -0
  42. package/dist/cli.d.ts +17 -0
  43. package/dist/cli.js +1433 -0
  44. package/dist/client/index.d.ts +41 -0
  45. package/dist/client/index.js +170 -0
  46. package/dist/compliance/index.d.ts +40 -0
  47. package/dist/compliance/index.js +292 -0
  48. package/dist/database/index.d.ts +77 -0
  49. package/dist/database/index.js +395 -0
  50. package/dist/index.d.ts +25 -0
  51. package/dist/index.js +762 -0
  52. package/dist/integrations/aura-scanner.d.ts +69 -0
  53. package/dist/integrations/aura-scanner.js +155 -0
  54. package/dist/integrations/aws-scanner.d.ts +63 -0
  55. package/dist/integrations/aws-scanner.js +624 -0
  56. package/dist/integrations/config.d.ts +69 -0
  57. package/dist/integrations/config.js +212 -0
  58. package/dist/integrations/github.d.ts +45 -0
  59. package/dist/integrations/github.js +201 -0
  60. package/dist/integrations/gitlab.d.ts +36 -0
  61. package/dist/integrations/gitlab.js +110 -0
  62. package/dist/integrations/index.d.ts +11 -0
  63. package/dist/integrations/index.js +11 -0
  64. package/dist/integrations/local-scanner.d.ts +146 -0
  65. package/dist/integrations/local-scanner.js +1654 -0
  66. package/dist/integrations/notifications.d.ts +99 -0
  67. package/dist/integrations/notifications.js +305 -0
  68. package/dist/integrations/scanners.d.ts +57 -0
  69. package/dist/integrations/scanners.js +217 -0
  70. package/dist/integrations/slop-scanner.d.ts +69 -0
  71. package/dist/integrations/slop-scanner.js +155 -0
  72. package/dist/integrations/webhook.d.ts +37 -0
  73. package/dist/integrations/webhook.js +256 -0
  74. package/dist/orchestrator/index.d.ts +72 -0
  75. package/dist/orchestrator/index.js +187 -0
  76. package/dist/output/index.d.ts +152 -0
  77. package/dist/output/index.js +399 -0
  78. package/dist/pipeline/index.d.ts +72 -0
  79. package/dist/pipeline/index.js +313 -0
  80. package/dist/sbom/index.d.ts +94 -0
  81. package/dist/sbom/index.js +298 -0
  82. package/dist/schemas/index.d.ts +2 -0
  83. package/dist/schemas/index.js +2 -0
  84. package/dist/schemas/input.schema.d.ts +87 -0
  85. package/dist/schemas/input.schema.js +44 -0
  86. package/dist/schemas/output.schema.d.ts +115 -0
  87. package/dist/schemas/output.schema.js +64 -0
  88. package/dist/serve-visualizer.d.ts +2 -0
  89. package/dist/serve-visualizer.js +78 -0
  90. package/dist/slop/client.d.ts +29 -0
  91. package/dist/slop/client.js +125 -0
  92. package/dist/slop/index.d.ts +4 -0
  93. package/dist/slop/index.js +2 -0
  94. package/dist/slop/server.d.ts +45 -0
  95. package/dist/slop/server.js +343 -0
  96. package/dist/types/events.d.ts +62 -0
  97. package/dist/types/events.js +2 -0
  98. package/dist/types/index.d.ts +1 -0
  99. package/dist/types/index.js +1 -0
  100. package/dist/visualizer/index.d.ts +4 -0
  101. package/dist/visualizer/index.js +181 -0
  102. package/dist/websocket/index.d.ts +88 -0
  103. package/dist/websocket/index.js +195 -0
  104. package/dist/zones/index.d.ts +7 -0
  105. package/dist/zones/index.js +7 -0
  106. package/dist/zones/manager.d.ts +101 -0
  107. package/dist/zones/manager.js +304 -0
  108. package/dist/zones/types.d.ts +78 -0
  109. package/dist/zones/types.js +33 -0
  110. package/package.json +84 -0
  111. package/visualizer/app.js +0 -0
  112. package/visualizer/index-minimal.html +1771 -0
  113. package/visualizer/index.html +2933 -0
  114. package/visualizer/landing.html +1328 -0
  115. package/visualizer/styles.css +0 -0
@@ -0,0 +1,624 @@
1
+ /**
2
+ * AWS Security Scanner
3
+ * Scans AWS infrastructure for security issues:
4
+ * - IAM: overly permissive policies, unused credentials, MFA status
5
+ * - S3: public buckets, unencrypted buckets, versioning
6
+ * - EC2: security groups, public IPs, unencrypted volumes
7
+ * - Lambda: public functions, environment secrets
8
+ * - RDS: public instances, unencrypted databases
9
+ */
10
+ import { IAMClient, ListUsersCommand, ListAccessKeysCommand, GetAccessKeyLastUsedCommand, ListMFADevicesCommand, GetPolicyVersionCommand, ListPoliciesCommand, } from '@aws-sdk/client-iam';
11
+ import { S3Client, ListBucketsCommand, GetBucketEncryptionCommand, GetBucketVersioningCommand, GetBucketPolicyStatusCommand, GetPublicAccessBlockCommand, } from '@aws-sdk/client-s3';
12
+ import { EC2Client, DescribeSecurityGroupsCommand, DescribeInstancesCommand, DescribeVolumesCommand, } from '@aws-sdk/client-ec2';
13
+ import { LambdaClient, ListFunctionsCommand, GetPolicyCommand, } from '@aws-sdk/client-lambda';
14
+ import { RDSClient, DescribeDBInstancesCommand, } from '@aws-sdk/client-rds';
15
+ import { fromEnv, fromIni } from '@aws-sdk/credential-providers';
16
+ // ============ SCANNER CLASS ============
17
+ export class AWSScanner {
18
+ region;
19
+ iamClient;
20
+ s3Client;
21
+ ec2Client;
22
+ lambdaClient;
23
+ rdsClient;
24
+ config;
25
+ constructor(config = {}) {
26
+ this.config = config;
27
+ this.region = config.region || process.env.AWS_REGION || 'us-east-1';
28
+ // Configure credentials
29
+ const credentialProvider = config.profile
30
+ ? fromIni({ profile: config.profile })
31
+ : fromEnv();
32
+ const clientConfig = {
33
+ region: this.region,
34
+ credentials: credentialProvider,
35
+ };
36
+ // Initialize clients
37
+ this.iamClient = new IAMClient(clientConfig);
38
+ this.s3Client = new S3Client(clientConfig);
39
+ this.ec2Client = new EC2Client(clientConfig);
40
+ this.lambdaClient = new LambdaClient(clientConfig);
41
+ this.rdsClient = new RDSClient(clientConfig);
42
+ }
43
+ async scan() {
44
+ const findings = [];
45
+ const errors = [];
46
+ const scannedServices = [];
47
+ const services = this.config.services || ['iam', 's3', 'ec2', 'lambda', 'rds'];
48
+ const skip = this.config.skipServices || [];
49
+ console.log(`[AWS] Starting scan in region: ${this.region}`);
50
+ // IAM Scan
51
+ if (services.includes('iam') && !skip.includes('iam')) {
52
+ try {
53
+ console.log('[AWS] Scanning IAM...');
54
+ const iamFindings = await this.scanIAM();
55
+ findings.push(...iamFindings);
56
+ scannedServices.push('iam');
57
+ console.log(`[AWS] IAM: found ${iamFindings.length} findings`);
58
+ }
59
+ catch (err) {
60
+ const errorMsg = err instanceof Error ? err.message : String(err);
61
+ errors.push({ service: 'iam', error: errorMsg });
62
+ console.error(`[AWS] IAM scan error: ${errorMsg}`);
63
+ }
64
+ }
65
+ // S3 Scan
66
+ if (services.includes('s3') && !skip.includes('s3')) {
67
+ try {
68
+ console.log('[AWS] Scanning S3...');
69
+ const s3Findings = await this.scanS3();
70
+ findings.push(...s3Findings);
71
+ scannedServices.push('s3');
72
+ console.log(`[AWS] S3: found ${s3Findings.length} findings`);
73
+ }
74
+ catch (err) {
75
+ const errorMsg = err instanceof Error ? err.message : String(err);
76
+ errors.push({ service: 's3', error: errorMsg });
77
+ console.error(`[AWS] S3 scan error: ${errorMsg}`);
78
+ }
79
+ }
80
+ // EC2 Scan
81
+ if (services.includes('ec2') && !skip.includes('ec2')) {
82
+ try {
83
+ console.log('[AWS] Scanning EC2...');
84
+ const ec2Findings = await this.scanEC2();
85
+ findings.push(...ec2Findings);
86
+ scannedServices.push('ec2');
87
+ console.log(`[AWS] EC2: found ${ec2Findings.length} findings`);
88
+ }
89
+ catch (err) {
90
+ const errorMsg = err instanceof Error ? err.message : String(err);
91
+ errors.push({ service: 'ec2', error: errorMsg });
92
+ console.error(`[AWS] EC2 scan error: ${errorMsg}`);
93
+ }
94
+ }
95
+ // Lambda Scan
96
+ if (services.includes('lambda') && !skip.includes('lambda')) {
97
+ try {
98
+ console.log('[AWS] Scanning Lambda...');
99
+ const lambdaFindings = await this.scanLambda();
100
+ findings.push(...lambdaFindings);
101
+ scannedServices.push('lambda');
102
+ console.log(`[AWS] Lambda: found ${lambdaFindings.length} findings`);
103
+ }
104
+ catch (err) {
105
+ const errorMsg = err instanceof Error ? err.message : String(err);
106
+ errors.push({ service: 'lambda', error: errorMsg });
107
+ console.error(`[AWS] Lambda scan error: ${errorMsg}`);
108
+ }
109
+ }
110
+ // RDS Scan
111
+ if (services.includes('rds') && !skip.includes('rds')) {
112
+ try {
113
+ console.log('[AWS] Scanning RDS...');
114
+ const rdsFindings = await this.scanRDS();
115
+ findings.push(...rdsFindings);
116
+ scannedServices.push('rds');
117
+ console.log(`[AWS] RDS: found ${rdsFindings.length} findings`);
118
+ }
119
+ catch (err) {
120
+ const errorMsg = err instanceof Error ? err.message : String(err);
121
+ errors.push({ service: 'rds', error: errorMsg });
122
+ console.error(`[AWS] RDS scan error: ${errorMsg}`);
123
+ }
124
+ }
125
+ // Calculate summary
126
+ const summary = {
127
+ critical: findings.filter(f => f.severity === 'critical').length,
128
+ high: findings.filter(f => f.severity === 'high').length,
129
+ medium: findings.filter(f => f.severity === 'medium').length,
130
+ low: findings.filter(f => f.severity === 'low').length,
131
+ info: findings.filter(f => f.severity === 'info').length,
132
+ total: findings.length,
133
+ };
134
+ console.log(`[AWS] Scan complete. Total findings: ${findings.length}`);
135
+ return {
136
+ timestamp: new Date().toISOString(),
137
+ region: this.region,
138
+ findings,
139
+ summary,
140
+ scannedServices,
141
+ errors,
142
+ };
143
+ }
144
+ // ============ IAM SCANNING ============
145
+ async scanIAM() {
146
+ const findings = [];
147
+ // List all users
148
+ const usersResponse = await this.iamClient.send(new ListUsersCommand({}));
149
+ const users = usersResponse.Users || [];
150
+ for (const user of users) {
151
+ if (!user.UserName)
152
+ continue;
153
+ // Check for MFA
154
+ const mfaResponse = await this.iamClient.send(new ListMFADevicesCommand({ UserName: user.UserName }));
155
+ if (!mfaResponse.MFADevices || mfaResponse.MFADevices.length === 0) {
156
+ findings.push({
157
+ service: 'iam',
158
+ resourceType: 'User',
159
+ resourceId: user.UserName,
160
+ resourceArn: user.Arn,
161
+ severity: 'high',
162
+ title: 'IAM User without MFA',
163
+ description: `User ${user.UserName} does not have MFA enabled`,
164
+ remediation: 'Enable MFA for this IAM user',
165
+ metadata: { userId: user.UserId },
166
+ });
167
+ }
168
+ // Check access keys
169
+ const keysResponse = await this.iamClient.send(new ListAccessKeysCommand({ UserName: user.UserName }));
170
+ const accessKeys = keysResponse.AccessKeyMetadata || [];
171
+ for (const key of accessKeys) {
172
+ if (!key.AccessKeyId)
173
+ continue;
174
+ // Check key age (over 90 days is a concern)
175
+ if (key.CreateDate) {
176
+ const keyAge = Date.now() - key.CreateDate.getTime();
177
+ const daysOld = Math.floor(keyAge / (1000 * 60 * 60 * 24));
178
+ if (daysOld > 90) {
179
+ findings.push({
180
+ service: 'iam',
181
+ resourceType: 'AccessKey',
182
+ resourceId: key.AccessKeyId,
183
+ severity: 'medium',
184
+ title: 'Old IAM Access Key',
185
+ description: `Access key ${key.AccessKeyId} for user ${user.UserName} is ${daysOld} days old`,
186
+ remediation: 'Rotate access keys regularly (every 90 days)',
187
+ metadata: { userName: user.UserName, daysOld },
188
+ });
189
+ }
190
+ }
191
+ // Check if key was recently used
192
+ try {
193
+ const lastUsedResponse = await this.iamClient.send(new GetAccessKeyLastUsedCommand({ AccessKeyId: key.AccessKeyId }));
194
+ const lastUsed = lastUsedResponse.AccessKeyLastUsed?.LastUsedDate;
195
+ if (lastUsed) {
196
+ const daysSinceUse = Math.floor((Date.now() - lastUsed.getTime()) / (1000 * 60 * 60 * 24));
197
+ if (daysSinceUse > 90) {
198
+ findings.push({
199
+ service: 'iam',
200
+ resourceType: 'AccessKey',
201
+ resourceId: key.AccessKeyId,
202
+ severity: 'medium',
203
+ title: 'Unused IAM Access Key',
204
+ description: `Access key ${key.AccessKeyId} for user ${user.UserName} has not been used in ${daysSinceUse} days`,
205
+ remediation: 'Delete unused access keys',
206
+ metadata: { userName: user.UserName, daysSinceUse },
207
+ });
208
+ }
209
+ }
210
+ }
211
+ catch {
212
+ // Ignore errors checking last used
213
+ }
214
+ }
215
+ }
216
+ // Check for overly permissive policies
217
+ try {
218
+ const policiesResponse = await this.iamClient.send(new ListPoliciesCommand({ Scope: 'Local' }));
219
+ const policies = policiesResponse.Policies || [];
220
+ for (const policy of policies) {
221
+ if (!policy.Arn || !policy.DefaultVersionId)
222
+ continue;
223
+ try {
224
+ const versionResponse = await this.iamClient.send(new GetPolicyVersionCommand({
225
+ PolicyArn: policy.Arn,
226
+ VersionId: policy.DefaultVersionId,
227
+ }));
228
+ const document = versionResponse.PolicyVersion?.Document;
229
+ if (document) {
230
+ const policyDoc = JSON.parse(decodeURIComponent(document));
231
+ const statements = policyDoc.Statement || [];
232
+ for (const statement of statements) {
233
+ if (statement.Effect === 'Allow' &&
234
+ statement.Action === '*' &&
235
+ statement.Resource === '*') {
236
+ findings.push({
237
+ service: 'iam',
238
+ resourceType: 'Policy',
239
+ resourceId: policy.PolicyName || policy.Arn,
240
+ resourceArn: policy.Arn,
241
+ severity: 'critical',
242
+ title: 'Overly Permissive IAM Policy',
243
+ description: `Policy ${policy.PolicyName} grants full access (Action: *, Resource: *)`,
244
+ remediation: 'Apply least privilege principle - limit actions and resources',
245
+ });
246
+ }
247
+ }
248
+ }
249
+ }
250
+ catch {
251
+ // Ignore policy parsing errors
252
+ }
253
+ }
254
+ }
255
+ catch {
256
+ // Ignore errors listing policies
257
+ }
258
+ return findings;
259
+ }
260
+ // ============ S3 SCANNING ============
261
+ async scanS3() {
262
+ const findings = [];
263
+ // List all buckets
264
+ const bucketsResponse = await this.s3Client.send(new ListBucketsCommand({}));
265
+ const buckets = bucketsResponse.Buckets || [];
266
+ for (const bucket of buckets) {
267
+ if (!bucket.Name)
268
+ continue;
269
+ // Check public access block
270
+ try {
271
+ const publicAccessResponse = await this.s3Client.send(new GetPublicAccessBlockCommand({ Bucket: bucket.Name }));
272
+ const config = publicAccessResponse.PublicAccessBlockConfiguration;
273
+ if (!config?.BlockPublicAcls ||
274
+ !config?.BlockPublicPolicy ||
275
+ !config?.IgnorePublicAcls ||
276
+ !config?.RestrictPublicBuckets) {
277
+ findings.push({
278
+ service: 's3',
279
+ resourceType: 'Bucket',
280
+ resourceId: bucket.Name,
281
+ severity: 'high',
282
+ title: 'S3 Bucket Public Access Not Fully Blocked',
283
+ description: `Bucket ${bucket.Name} does not have all public access blocks enabled`,
284
+ remediation: 'Enable all public access block settings',
285
+ metadata: {
286
+ blockPublicAcls: config?.BlockPublicAcls,
287
+ blockPublicPolicy: config?.BlockPublicPolicy,
288
+ ignorePublicAcls: config?.IgnorePublicAcls,
289
+ restrictPublicBuckets: config?.RestrictPublicBuckets,
290
+ },
291
+ });
292
+ }
293
+ }
294
+ catch (err) {
295
+ // If public access block is not configured, it's a finding
296
+ if (err.name === 'NoSuchPublicAccessBlockConfiguration') {
297
+ findings.push({
298
+ service: 's3',
299
+ resourceType: 'Bucket',
300
+ resourceId: bucket.Name,
301
+ severity: 'high',
302
+ title: 'S3 Bucket No Public Access Block',
303
+ description: `Bucket ${bucket.Name} has no public access block configuration`,
304
+ remediation: 'Configure public access block for this bucket',
305
+ });
306
+ }
307
+ }
308
+ // Check encryption
309
+ try {
310
+ await this.s3Client.send(new GetBucketEncryptionCommand({ Bucket: bucket.Name }));
311
+ // If we get here, encryption is configured
312
+ }
313
+ catch (err) {
314
+ if (err.name === 'ServerSideEncryptionConfigurationNotFoundError') {
315
+ findings.push({
316
+ service: 's3',
317
+ resourceType: 'Bucket',
318
+ resourceId: bucket.Name,
319
+ severity: 'medium',
320
+ title: 'S3 Bucket Not Encrypted',
321
+ description: `Bucket ${bucket.Name} does not have default encryption enabled`,
322
+ remediation: 'Enable server-side encryption for this bucket',
323
+ });
324
+ }
325
+ }
326
+ // Check versioning
327
+ try {
328
+ const versioningResponse = await this.s3Client.send(new GetBucketVersioningCommand({ Bucket: bucket.Name }));
329
+ if (versioningResponse.Status !== 'Enabled') {
330
+ findings.push({
331
+ service: 's3',
332
+ resourceType: 'Bucket',
333
+ resourceId: bucket.Name,
334
+ severity: 'low',
335
+ title: 'S3 Bucket Versioning Disabled',
336
+ description: `Bucket ${bucket.Name} does not have versioning enabled`,
337
+ remediation: 'Enable versioning for data protection and recovery',
338
+ });
339
+ }
340
+ }
341
+ catch {
342
+ // Ignore versioning check errors
343
+ }
344
+ // Check for public bucket policy
345
+ try {
346
+ const policyStatusResponse = await this.s3Client.send(new GetBucketPolicyStatusCommand({ Bucket: bucket.Name }));
347
+ if (policyStatusResponse.PolicyStatus?.IsPublic) {
348
+ findings.push({
349
+ service: 's3',
350
+ resourceType: 'Bucket',
351
+ resourceId: bucket.Name,
352
+ severity: 'critical',
353
+ title: 'S3 Bucket Has Public Policy',
354
+ description: `Bucket ${bucket.Name} has a policy that makes it publicly accessible`,
355
+ remediation: 'Review and restrict the bucket policy',
356
+ });
357
+ }
358
+ }
359
+ catch {
360
+ // No policy or error - skip
361
+ }
362
+ }
363
+ return findings;
364
+ }
365
+ // ============ EC2 SCANNING ============
366
+ async scanEC2() {
367
+ const findings = [];
368
+ // Check security groups
369
+ const sgResponse = await this.ec2Client.send(new DescribeSecurityGroupsCommand({}));
370
+ const securityGroups = sgResponse.SecurityGroups || [];
371
+ for (const sg of securityGroups) {
372
+ if (!sg.GroupId)
373
+ continue;
374
+ // Check for overly permissive inbound rules
375
+ for (const rule of sg.IpPermissions || []) {
376
+ for (const ipRange of rule.IpRanges || []) {
377
+ if (ipRange.CidrIp === '0.0.0.0/0') {
378
+ // Check if it's a sensitive port
379
+ const fromPort = rule.FromPort || 0;
380
+ const toPort = rule.ToPort || 65535;
381
+ const sensitivePort = this.isSensitivePort(fromPort, toPort);
382
+ if (sensitivePort) {
383
+ findings.push({
384
+ service: 'ec2',
385
+ resourceType: 'SecurityGroup',
386
+ resourceId: sg.GroupId,
387
+ severity: 'critical',
388
+ title: 'Security Group Allows Public Access to Sensitive Port',
389
+ description: `Security group ${sg.GroupName || sg.GroupId} allows 0.0.0.0/0 access to port ${fromPort}-${toPort}`,
390
+ remediation: 'Restrict access to specific IP ranges',
391
+ metadata: {
392
+ groupName: sg.GroupName,
393
+ fromPort,
394
+ toPort,
395
+ protocol: rule.IpProtocol,
396
+ },
397
+ });
398
+ }
399
+ else if (fromPort === 0 && toPort === 65535) {
400
+ findings.push({
401
+ service: 'ec2',
402
+ resourceType: 'SecurityGroup',
403
+ resourceId: sg.GroupId,
404
+ severity: 'high',
405
+ title: 'Security Group Allows All Traffic from Internet',
406
+ description: `Security group ${sg.GroupName || sg.GroupId} allows all inbound traffic from 0.0.0.0/0`,
407
+ remediation: 'Restrict to specific ports and IP ranges',
408
+ metadata: { groupName: sg.GroupName },
409
+ });
410
+ }
411
+ }
412
+ }
413
+ }
414
+ }
415
+ // Check instances
416
+ const instancesResponse = await this.ec2Client.send(new DescribeInstancesCommand({}));
417
+ const reservations = instancesResponse.Reservations || [];
418
+ for (const reservation of reservations) {
419
+ for (const instance of reservation.Instances || []) {
420
+ if (!instance.InstanceId)
421
+ continue;
422
+ // Check for public IP
423
+ if (instance.PublicIpAddress) {
424
+ findings.push({
425
+ service: 'ec2',
426
+ resourceType: 'Instance',
427
+ resourceId: instance.InstanceId,
428
+ severity: 'info',
429
+ title: 'EC2 Instance Has Public IP',
430
+ description: `Instance ${instance.InstanceId} has public IP ${instance.PublicIpAddress}`,
431
+ remediation: 'Verify this instance needs public access',
432
+ metadata: {
433
+ publicIp: instance.PublicIpAddress,
434
+ instanceType: instance.InstanceType,
435
+ },
436
+ });
437
+ }
438
+ }
439
+ }
440
+ // Check for unencrypted volumes
441
+ const volumesResponse = await this.ec2Client.send(new DescribeVolumesCommand({}));
442
+ const volumes = volumesResponse.Volumes || [];
443
+ for (const volume of volumes) {
444
+ if (!volume.VolumeId)
445
+ continue;
446
+ if (!volume.Encrypted) {
447
+ findings.push({
448
+ service: 'ec2',
449
+ resourceType: 'Volume',
450
+ resourceId: volume.VolumeId,
451
+ severity: 'medium',
452
+ title: 'EBS Volume Not Encrypted',
453
+ description: `Volume ${volume.VolumeId} is not encrypted`,
454
+ remediation: 'Enable encryption for EBS volumes',
455
+ metadata: {
456
+ size: volume.Size,
457
+ state: volume.State,
458
+ },
459
+ });
460
+ }
461
+ }
462
+ return findings;
463
+ }
464
+ isSensitivePort(fromPort, toPort) {
465
+ const sensitivePorts = [22, 23, 3389, 3306, 5432, 1433, 27017, 6379];
466
+ return sensitivePorts.some(p => p >= fromPort && p <= toPort);
467
+ }
468
+ // ============ LAMBDA SCANNING ============
469
+ async scanLambda() {
470
+ const findings = [];
471
+ // List functions
472
+ const functionsResponse = await this.lambdaClient.send(new ListFunctionsCommand({}));
473
+ const functions = functionsResponse.Functions || [];
474
+ for (const func of functions) {
475
+ if (!func.FunctionName || !func.FunctionArn)
476
+ continue;
477
+ // Check for environment variables that look like secrets
478
+ const envVars = func.Environment?.Variables || {};
479
+ for (const [key, value] of Object.entries(envVars)) {
480
+ const keyLower = key.toLowerCase();
481
+ if (keyLower.includes('secret') ||
482
+ keyLower.includes('password') ||
483
+ keyLower.includes('key') ||
484
+ keyLower.includes('token')) {
485
+ findings.push({
486
+ service: 'lambda',
487
+ resourceType: 'Function',
488
+ resourceId: func.FunctionName,
489
+ resourceArn: func.FunctionArn,
490
+ severity: 'high',
491
+ title: 'Lambda Function Has Sensitive Environment Variable',
492
+ description: `Function ${func.FunctionName} has environment variable "${key}" that may contain secrets`,
493
+ remediation: 'Use AWS Secrets Manager or Parameter Store for sensitive values',
494
+ metadata: { envVarName: key },
495
+ });
496
+ }
497
+ }
498
+ // Check for public resource policy
499
+ try {
500
+ const policyResponse = await this.lambdaClient.send(new GetPolicyCommand({ FunctionName: func.FunctionName }));
501
+ if (policyResponse.Policy) {
502
+ const policy = JSON.parse(policyResponse.Policy);
503
+ for (const statement of policy.Statement || []) {
504
+ if (statement.Principal === '*') {
505
+ findings.push({
506
+ service: 'lambda',
507
+ resourceType: 'Function',
508
+ resourceId: func.FunctionName,
509
+ resourceArn: func.FunctionArn,
510
+ severity: 'critical',
511
+ title: 'Lambda Function Has Public Access',
512
+ description: `Function ${func.FunctionName} has a resource policy allowing public access`,
513
+ remediation: 'Restrict the resource policy to specific principals',
514
+ });
515
+ break;
516
+ }
517
+ }
518
+ }
519
+ }
520
+ catch {
521
+ // No policy - that's fine
522
+ }
523
+ // Check runtime (old runtimes are security risks)
524
+ const runtime = func.Runtime || '';
525
+ const deprecatedRuntimes = [
526
+ 'nodejs12.x',
527
+ 'nodejs10.x',
528
+ 'python2.7',
529
+ 'python3.6',
530
+ 'ruby2.5',
531
+ ];
532
+ if (deprecatedRuntimes.some(r => runtime.includes(r))) {
533
+ findings.push({
534
+ service: 'lambda',
535
+ resourceType: 'Function',
536
+ resourceId: func.FunctionName,
537
+ resourceArn: func.FunctionArn,
538
+ severity: 'medium',
539
+ title: 'Lambda Function Uses Deprecated Runtime',
540
+ description: `Function ${func.FunctionName} uses deprecated runtime ${runtime}`,
541
+ remediation: 'Upgrade to a supported runtime version',
542
+ metadata: { runtime },
543
+ });
544
+ }
545
+ }
546
+ return findings;
547
+ }
548
+ // ============ RDS SCANNING ============
549
+ async scanRDS() {
550
+ const findings = [];
551
+ // List DB instances
552
+ const dbResponse = await this.rdsClient.send(new DescribeDBInstancesCommand({}));
553
+ const instances = dbResponse.DBInstances || [];
554
+ for (const db of instances) {
555
+ if (!db.DBInstanceIdentifier)
556
+ continue;
557
+ // Check for public access
558
+ if (db.PubliclyAccessible) {
559
+ findings.push({
560
+ service: 'rds',
561
+ resourceType: 'DBInstance',
562
+ resourceId: db.DBInstanceIdentifier,
563
+ resourceArn: db.DBInstanceArn,
564
+ severity: 'critical',
565
+ title: 'RDS Instance Publicly Accessible',
566
+ description: `Database ${db.DBInstanceIdentifier} is publicly accessible`,
567
+ remediation: 'Disable public accessibility unless required',
568
+ metadata: {
569
+ engine: db.Engine,
570
+ endpoint: db.Endpoint?.Address,
571
+ },
572
+ });
573
+ }
574
+ // Check for encryption
575
+ if (!db.StorageEncrypted) {
576
+ findings.push({
577
+ service: 'rds',
578
+ resourceType: 'DBInstance',
579
+ resourceId: db.DBInstanceIdentifier,
580
+ resourceArn: db.DBInstanceArn,
581
+ severity: 'high',
582
+ title: 'RDS Instance Not Encrypted',
583
+ description: `Database ${db.DBInstanceIdentifier} storage is not encrypted`,
584
+ remediation: 'Enable storage encryption for the database',
585
+ metadata: { engine: db.Engine },
586
+ });
587
+ }
588
+ // Check for automated backups
589
+ if (db.BackupRetentionPeriod === 0) {
590
+ findings.push({
591
+ service: 'rds',
592
+ resourceType: 'DBInstance',
593
+ resourceId: db.DBInstanceIdentifier,
594
+ resourceArn: db.DBInstanceArn,
595
+ severity: 'medium',
596
+ title: 'RDS Instance Has No Automated Backups',
597
+ description: `Database ${db.DBInstanceIdentifier} has automated backups disabled`,
598
+ remediation: 'Enable automated backups with appropriate retention period',
599
+ metadata: { engine: db.Engine },
600
+ });
601
+ }
602
+ // Check for deletion protection
603
+ if (!db.DeletionProtection) {
604
+ findings.push({
605
+ service: 'rds',
606
+ resourceType: 'DBInstance',
607
+ resourceId: db.DBInstanceIdentifier,
608
+ resourceArn: db.DBInstanceArn,
609
+ severity: 'low',
610
+ title: 'RDS Instance Has No Deletion Protection',
611
+ description: `Database ${db.DBInstanceIdentifier} does not have deletion protection enabled`,
612
+ remediation: 'Enable deletion protection for production databases',
613
+ metadata: { engine: db.Engine },
614
+ });
615
+ }
616
+ }
617
+ return findings;
618
+ }
619
+ }
620
+ // Quick scan function
621
+ export async function scanAWS(config) {
622
+ const scanner = new AWSScanner(config);
623
+ return scanner.scan();
624
+ }