@nahisaho/musubix-security 1.8.0 → 1.8.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/README.md +27 -0
- package/dist/analyzers/ai/index.d.ts +6 -0
- package/dist/analyzers/ai/index.d.ts.map +1 -0
- package/dist/analyzers/ai/index.js +6 -0
- package/dist/analyzers/ai/index.js.map +1 -0
- package/dist/analyzers/ai/prompt-injection-detector.d.ts +152 -0
- package/dist/analyzers/ai/prompt-injection-detector.d.ts.map +1 -0
- package/dist/analyzers/ai/prompt-injection-detector.js +468 -0
- package/dist/analyzers/ai/prompt-injection-detector.js.map +1 -0
- package/dist/analyzers/api/api-security-analyzer.d.ts +263 -0
- package/dist/analyzers/api/api-security-analyzer.d.ts.map +1 -0
- package/dist/analyzers/api/api-security-analyzer.js +581 -0
- package/dist/analyzers/api/api-security-analyzer.js.map +1 -0
- package/dist/analyzers/compliance/compliance-checker.d.ts +201 -0
- package/dist/analyzers/compliance/compliance-checker.d.ts.map +1 -0
- package/dist/analyzers/compliance/compliance-checker.js +772 -0
- package/dist/analyzers/compliance/compliance-checker.js.map +1 -0
- package/dist/analyzers/container/image-scanner.d.ts +163 -0
- package/dist/analyzers/container/image-scanner.d.ts.map +1 -0
- package/dist/analyzers/container/image-scanner.js +459 -0
- package/dist/analyzers/container/image-scanner.js.map +1 -0
- package/dist/analyzers/container/index.d.ts +6 -0
- package/dist/analyzers/container/index.d.ts.map +1 -0
- package/dist/analyzers/container/index.js +6 -0
- package/dist/analyzers/container/index.js.map +1 -0
- package/dist/analyzers/dashboard/security-dashboard.d.ts +286 -0
- package/dist/analyzers/dashboard/security-dashboard.d.ts.map +1 -0
- package/dist/analyzers/dashboard/security-dashboard.js +796 -0
- package/dist/analyzers/dashboard/security-dashboard.js.map +1 -0
- package/dist/analyzers/iac/iac-checker.d.ts +124 -0
- package/dist/analyzers/iac/iac-checker.d.ts.map +1 -0
- package/dist/analyzers/iac/iac-checker.js +755 -0
- package/dist/analyzers/iac/iac-checker.js.map +1 -0
- package/dist/analyzers/iac/index.d.ts +6 -0
- package/dist/analyzers/iac/index.d.ts.map +1 -0
- package/dist/analyzers/iac/index.js +6 -0
- package/dist/analyzers/iac/index.js.map +1 -0
- package/dist/analyzers/index.d.ts +9 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +13 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/analyzers/monitor/realtime-monitor.d.ts +216 -0
- package/dist/analyzers/monitor/realtime-monitor.d.ts.map +1 -0
- package/dist/analyzers/monitor/realtime-monitor.js +601 -0
- package/dist/analyzers/monitor/realtime-monitor.js.map +1 -0
- package/dist/analyzers/sast/index.d.ts +7 -0
- package/dist/analyzers/sast/index.d.ts.map +1 -0
- package/dist/analyzers/sast/index.js +7 -0
- package/dist/analyzers/sast/index.js.map +1 -0
- package/dist/analyzers/sast/interprocedural-analyzer.d.ts +276 -0
- package/dist/analyzers/sast/interprocedural-analyzer.d.ts.map +1 -0
- package/dist/analyzers/sast/interprocedural-analyzer.js +635 -0
- package/dist/analyzers/sast/interprocedural-analyzer.js.map +1 -0
- package/dist/analyzers/sast/zero-day-detector.d.ts +183 -0
- package/dist/analyzers/sast/zero-day-detector.d.ts.map +1 -0
- package/dist/analyzers/sast/zero-day-detector.js +593 -0
- package/dist/analyzers/sast/zero-day-detector.js.map +1 -0
- package/dist/analyzers/sca/dependency-scanner.d.ts +275 -0
- package/dist/analyzers/sca/dependency-scanner.d.ts.map +1 -0
- package/dist/analyzers/sca/dependency-scanner.js +642 -0
- package/dist/analyzers/sca/dependency-scanner.js.map +1 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +10 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/pipeline-manager.d.ts +105 -0
- package/dist/core/pipeline-manager.d.ts.map +1 -0
- package/dist/core/pipeline-manager.js +449 -0
- package/dist/core/pipeline-manager.js.map +1 -0
- package/dist/core/result-aggregator.d.ts +96 -0
- package/dist/core/result-aggregator.d.ts.map +1 -0
- package/dist/core/result-aggregator.js +462 -0
- package/dist/core/result-aggregator.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/ci-integration.d.ts +227 -0
- package/dist/integrations/ci-integration.d.ts.map +1 -0
- package/dist/integrations/ci-integration.js +472 -0
- package/dist/integrations/ci-integration.js.map +1 -0
- package/dist/integrations/git-hooks.d.ts +155 -0
- package/dist/integrations/git-hooks.d.ts.map +1 -0
- package/dist/integrations/git-hooks.js +425 -0
- package/dist/integrations/git-hooks.js.map +1 -0
- package/dist/integrations/index.d.ts +9 -0
- package/dist/integrations/index.d.ts.map +1 -0
- package/dist/integrations/index.js +9 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/integrations/report-aggregator.d.ts +250 -0
- package/dist/integrations/report-aggregator.d.ts.map +1 -0
- package/dist/integrations/report-aggregator.js +488 -0
- package/dist/integrations/report-aggregator.js.map +1 -0
- package/dist/integrations/vscode-integration.d.ts +245 -0
- package/dist/integrations/vscode-integration.d.ts.map +1 -0
- package/dist/integrations/vscode-integration.js +449 -0
- package/dist/integrations/vscode-integration.js.map +1 -0
- package/dist/intelligence/attack-pattern-matcher.d.ts +217 -0
- package/dist/intelligence/attack-pattern-matcher.d.ts.map +1 -0
- package/dist/intelligence/attack-pattern-matcher.js +887 -0
- package/dist/intelligence/attack-pattern-matcher.js.map +1 -0
- package/dist/intelligence/index.d.ts +12 -0
- package/dist/intelligence/index.d.ts.map +1 -0
- package/dist/intelligence/index.js +18 -0
- package/dist/intelligence/index.js.map +1 -0
- package/dist/intelligence/neuro-symbolic-core.d.ts +88 -0
- package/dist/intelligence/neuro-symbolic-core.d.ts.map +1 -0
- package/dist/intelligence/neuro-symbolic-core.js +403 -0
- package/dist/intelligence/neuro-symbolic-core.js.map +1 -0
- package/dist/intelligence/predictive-analyzer.d.ts +317 -0
- package/dist/intelligence/predictive-analyzer.d.ts.map +1 -0
- package/dist/intelligence/predictive-analyzer.js +714 -0
- package/dist/intelligence/predictive-analyzer.js.map +1 -0
- package/dist/intelligence/risk-scorer.d.ts +333 -0
- package/dist/intelligence/risk-scorer.d.ts.map +1 -0
- package/dist/intelligence/risk-scorer.js +824 -0
- package/dist/intelligence/risk-scorer.js.map +1 -0
- package/dist/intelligence/security-analytics.d.ts +349 -0
- package/dist/intelligence/security-analytics.d.ts.map +1 -0
- package/dist/intelligence/security-analytics.js +813 -0
- package/dist/intelligence/security-analytics.js.map +1 -0
- package/dist/intelligence/threat-intelligence.d.ts +288 -0
- package/dist/intelligence/threat-intelligence.d.ts.map +1 -0
- package/dist/intelligence/threat-intelligence.js +639 -0
- package/dist/intelligence/threat-intelligence.js.map +1 -0
- package/dist/policy/index.d.ts +6 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +6 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/policy/policy-engine.d.ts +254 -0
- package/dist/policy/policy-engine.d.ts.map +1 -0
- package/dist/policy/policy-engine.js +651 -0
- package/dist/policy/policy-engine.js.map +1 -0
- package/dist/remediation/auto-fixer.d.ts +179 -0
- package/dist/remediation/auto-fixer.d.ts.map +1 -0
- package/dist/remediation/auto-fixer.js +540 -0
- package/dist/remediation/auto-fixer.js.map +1 -0
- package/dist/remediation/fix-validator.d.ts +195 -0
- package/dist/remediation/fix-validator.d.ts.map +1 -0
- package/dist/remediation/fix-validator.js +462 -0
- package/dist/remediation/fix-validator.js.map +1 -0
- package/dist/remediation/index.d.ts +10 -0
- package/dist/remediation/index.d.ts.map +1 -0
- package/dist/remediation/index.js +15 -0
- package/dist/remediation/index.js.map +1 -0
- package/dist/remediation/patch-generator.d.ts +203 -0
- package/dist/remediation/patch-generator.d.ts.map +1 -0
- package/dist/remediation/patch-generator.js +533 -0
- package/dist/remediation/patch-generator.js.map +1 -0
- package/dist/remediation/remediation-planner.d.ts +262 -0
- package/dist/remediation/remediation-planner.d.ts.map +1 -0
- package/dist/remediation/remediation-planner.js +531 -0
- package/dist/remediation/remediation-planner.js.map +1 -0
- package/dist/remediation/secure-code-transformer.d.ts +222 -0
- package/dist/remediation/secure-code-transformer.d.ts.map +1 -0
- package/dist/remediation/secure-code-transformer.js +625 -0
- package/dist/remediation/secure-code-transformer.js.map +1 -0
- package/dist/types/fix.d.ts +3 -1
- package/dist/types/fix.d.ts.map +1 -1
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/interprocedural.d.ts +203 -0
- package/dist/types/interprocedural.d.ts.map +1 -0
- package/dist/types/interprocedural.js +7 -0
- package/dist/types/interprocedural.js.map +1 -0
- package/dist/types/neuro-symbolic.d.ts +179 -0
- package/dist/types/neuro-symbolic.d.ts.map +1 -0
- package/dist/types/neuro-symbolic.js +7 -0
- package/dist/types/neuro-symbolic.js.map +1 -0
- package/dist/types/pipeline.d.ts +173 -0
- package/dist/types/pipeline.d.ts.map +1 -0
- package/dist/types/pipeline.js +7 -0
- package/dist/types/pipeline.js.map +1 -0
- package/dist/types/result.d.ts +134 -0
- package/dist/types/result.d.ts.map +1 -0
- package/dist/types/result.js +25 -0
- package/dist/types/result.js.map +1 -0
- package/dist/types/vulnerability.d.ts +2 -2
- package/dist/types/vulnerability.d.ts.map +1 -1
- package/dist/types/zero-day.d.ts +146 -0
- package/dist/types/zero-day.d.ts.map +1 -0
- package/dist/types/zero-day.js +7 -0
- package/dist/types/zero-day.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Infrastructure as Code (IaC) Checker
|
|
3
|
+
* @module @nahisaho/musubix-security/analyzers/iac/iac-checker
|
|
4
|
+
* @trace DES-SEC2-IAC-001, REQ-SEC2-IAC-001
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
/**
|
|
9
|
+
* Built-in Terraform rules
|
|
10
|
+
*/
|
|
11
|
+
const TERRAFORM_RULES = [
|
|
12
|
+
{
|
|
13
|
+
id: 'TF-001',
|
|
14
|
+
name: 'AWS S3 Bucket Public Access',
|
|
15
|
+
description: 'S3 bucket should not allow public access',
|
|
16
|
+
severity: 'critical',
|
|
17
|
+
fileTypes: ['terraform'],
|
|
18
|
+
cwes: ['CWE-284'],
|
|
19
|
+
references: ['https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html'],
|
|
20
|
+
remediation: 'Add block_public_acls = true, block_public_policy = true, ignore_public_acls = true, restrict_public_buckets = true',
|
|
21
|
+
check: (content, _filePath) => {
|
|
22
|
+
const matches = [];
|
|
23
|
+
const lines = content.split('\n');
|
|
24
|
+
// Find S3 bucket resources without public access block
|
|
25
|
+
let inS3Bucket = false;
|
|
26
|
+
let bucketStartLine = 0;
|
|
27
|
+
let hasPublicAccessBlock = false;
|
|
28
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29
|
+
const line = lines[i];
|
|
30
|
+
if (line.includes('resource "aws_s3_bucket"')) {
|
|
31
|
+
inS3Bucket = true;
|
|
32
|
+
bucketStartLine = i + 1;
|
|
33
|
+
hasPublicAccessBlock = false;
|
|
34
|
+
}
|
|
35
|
+
if (inS3Bucket && line.includes('aws_s3_bucket_public_access_block')) {
|
|
36
|
+
hasPublicAccessBlock = true;
|
|
37
|
+
}
|
|
38
|
+
if (inS3Bucket && line.trim() === '}' && !line.includes('{')) {
|
|
39
|
+
if (!hasPublicAccessBlock) {
|
|
40
|
+
matches.push({
|
|
41
|
+
line: bucketStartLine,
|
|
42
|
+
matchedText: 'aws_s3_bucket without public access block',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
inS3Bucket = false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Also check for explicit public ACLs
|
|
49
|
+
for (let i = 0; i < lines.length; i++) {
|
|
50
|
+
if (lines[i].match(/acl\s*=\s*["']public-(read|read-write)["']/)) {
|
|
51
|
+
matches.push({
|
|
52
|
+
line: i + 1,
|
|
53
|
+
matchedText: lines[i].trim(),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return matches;
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'TF-002',
|
|
62
|
+
name: 'AWS Security Group Open to World',
|
|
63
|
+
description: 'Security group should not allow unrestricted inbound access',
|
|
64
|
+
severity: 'high',
|
|
65
|
+
fileTypes: ['terraform'],
|
|
66
|
+
cwes: ['CWE-284'],
|
|
67
|
+
references: ['https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-rules-reference.html'],
|
|
68
|
+
remediation: 'Restrict cidr_blocks to specific IP ranges instead of 0.0.0.0/0',
|
|
69
|
+
check: (content, _filePath) => {
|
|
70
|
+
const matches = [];
|
|
71
|
+
const lines = content.split('\n');
|
|
72
|
+
for (let i = 0; i < lines.length; i++) {
|
|
73
|
+
if (lines[i].match(/cidr_blocks\s*=\s*\[\s*["']0\.0\.0\.0\/0["']\s*\]/)) {
|
|
74
|
+
matches.push({
|
|
75
|
+
line: i + 1,
|
|
76
|
+
matchedText: lines[i].trim(),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return matches;
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'TF-003',
|
|
85
|
+
name: 'AWS RDS Public Accessibility',
|
|
86
|
+
description: 'RDS instance should not be publicly accessible',
|
|
87
|
+
severity: 'high',
|
|
88
|
+
fileTypes: ['terraform'],
|
|
89
|
+
cwes: ['CWE-284'],
|
|
90
|
+
references: ['https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_VPC.WorkingWithRDSInstanceinaVPC.html'],
|
|
91
|
+
remediation: 'Set publicly_accessible = false',
|
|
92
|
+
check: (content, _filePath) => {
|
|
93
|
+
const matches = [];
|
|
94
|
+
const lines = content.split('\n');
|
|
95
|
+
for (let i = 0; i < lines.length; i++) {
|
|
96
|
+
if (lines[i].match(/publicly_accessible\s*=\s*true/)) {
|
|
97
|
+
matches.push({
|
|
98
|
+
line: i + 1,
|
|
99
|
+
matchedText: lines[i].trim(),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return matches;
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'TF-004',
|
|
108
|
+
name: 'AWS EC2 Unencrypted EBS',
|
|
109
|
+
description: 'EBS volumes should be encrypted',
|
|
110
|
+
severity: 'medium',
|
|
111
|
+
fileTypes: ['terraform'],
|
|
112
|
+
cwes: ['CWE-311'],
|
|
113
|
+
references: ['https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html'],
|
|
114
|
+
remediation: 'Set encrypted = true in ebs_block_device',
|
|
115
|
+
check: (content, _filePath) => {
|
|
116
|
+
const matches = [];
|
|
117
|
+
const lines = content.split('\n');
|
|
118
|
+
let inEbsBlock = false;
|
|
119
|
+
let ebsStartLine = 0;
|
|
120
|
+
let hasEncryption = false;
|
|
121
|
+
for (let i = 0; i < lines.length; i++) {
|
|
122
|
+
const line = lines[i];
|
|
123
|
+
if (line.includes('ebs_block_device') || line.includes('aws_ebs_volume')) {
|
|
124
|
+
inEbsBlock = true;
|
|
125
|
+
ebsStartLine = i + 1;
|
|
126
|
+
hasEncryption = false;
|
|
127
|
+
}
|
|
128
|
+
if (inEbsBlock && line.includes('encrypted')) {
|
|
129
|
+
hasEncryption = true;
|
|
130
|
+
}
|
|
131
|
+
if (inEbsBlock && line.trim() === '}') {
|
|
132
|
+
if (!hasEncryption) {
|
|
133
|
+
matches.push({
|
|
134
|
+
line: ebsStartLine,
|
|
135
|
+
matchedText: 'EBS volume without encryption',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
inEbsBlock = false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return matches;
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'TF-005',
|
|
146
|
+
name: 'Hardcoded Credentials',
|
|
147
|
+
description: 'Credentials should not be hardcoded',
|
|
148
|
+
severity: 'critical',
|
|
149
|
+
fileTypes: ['terraform'],
|
|
150
|
+
cwes: ['CWE-798'],
|
|
151
|
+
references: ['https://owasp.org/www-community/vulnerabilities/Use_of_hard-coded_password'],
|
|
152
|
+
remediation: 'Use variables, environment variables, or secret management services',
|
|
153
|
+
check: (content, _filePath) => {
|
|
154
|
+
const matches = [];
|
|
155
|
+
const lines = content.split('\n');
|
|
156
|
+
const patterns = [
|
|
157
|
+
/password\s*=\s*["'][^"']+["']/i,
|
|
158
|
+
/secret\s*=\s*["'][^"']+["']/i,
|
|
159
|
+
/api_key\s*=\s*["'][^"']+["']/i,
|
|
160
|
+
/access_key\s*=\s*["']AKIA[0-9A-Z]+["']/i,
|
|
161
|
+
];
|
|
162
|
+
for (let i = 0; i < lines.length; i++) {
|
|
163
|
+
for (const pattern of patterns) {
|
|
164
|
+
if (lines[i].match(pattern) && !lines[i].includes('var.')) {
|
|
165
|
+
matches.push({
|
|
166
|
+
line: i + 1,
|
|
167
|
+
matchedText: lines[i].trim().replace(/["'][^"']{10,}["']/, '"***"'),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return matches;
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: 'TF-006',
|
|
177
|
+
name: 'AWS CloudWatch Logs Not Encrypted',
|
|
178
|
+
description: 'CloudWatch log groups should be encrypted',
|
|
179
|
+
severity: 'medium',
|
|
180
|
+
fileTypes: ['terraform'],
|
|
181
|
+
cwes: ['CWE-311'],
|
|
182
|
+
references: ['https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html'],
|
|
183
|
+
remediation: 'Add kms_key_id to aws_cloudwatch_log_group',
|
|
184
|
+
check: (content, _filePath) => {
|
|
185
|
+
const matches = [];
|
|
186
|
+
const lines = content.split('\n');
|
|
187
|
+
let inLogGroup = false;
|
|
188
|
+
let logGroupStartLine = 0;
|
|
189
|
+
let hasKmsKey = false;
|
|
190
|
+
for (let i = 0; i < lines.length; i++) {
|
|
191
|
+
const line = lines[i];
|
|
192
|
+
if (line.includes('resource "aws_cloudwatch_log_group"')) {
|
|
193
|
+
inLogGroup = true;
|
|
194
|
+
logGroupStartLine = i + 1;
|
|
195
|
+
hasKmsKey = false;
|
|
196
|
+
}
|
|
197
|
+
if (inLogGroup && line.includes('kms_key_id')) {
|
|
198
|
+
hasKmsKey = true;
|
|
199
|
+
}
|
|
200
|
+
if (inLogGroup && line.trim() === '}' && !line.includes('{')) {
|
|
201
|
+
if (!hasKmsKey) {
|
|
202
|
+
matches.push({
|
|
203
|
+
line: logGroupStartLine,
|
|
204
|
+
matchedText: 'CloudWatch log group without KMS encryption',
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
inLogGroup = false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return matches;
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
];
|
|
214
|
+
/**
|
|
215
|
+
* Built-in Kubernetes rules
|
|
216
|
+
*/
|
|
217
|
+
const KUBERNETES_RULES = [
|
|
218
|
+
{
|
|
219
|
+
id: 'K8S-001',
|
|
220
|
+
name: 'Container Running as Root',
|
|
221
|
+
description: 'Containers should not run as root',
|
|
222
|
+
severity: 'high',
|
|
223
|
+
fileTypes: ['kubernetes'],
|
|
224
|
+
cwes: ['CWE-250'],
|
|
225
|
+
references: ['https://kubernetes.io/docs/concepts/security/pod-security-standards/'],
|
|
226
|
+
remediation: 'Set securityContext.runAsNonRoot = true',
|
|
227
|
+
check: (content, _filePath) => {
|
|
228
|
+
const matches = [];
|
|
229
|
+
const lines = content.split('\n');
|
|
230
|
+
// Check for missing runAsNonRoot or runAsUser: 0
|
|
231
|
+
let inContainer = false;
|
|
232
|
+
let hasSecurityContext = false;
|
|
233
|
+
let containerStartLine = 0;
|
|
234
|
+
for (let i = 0; i < lines.length; i++) {
|
|
235
|
+
const line = lines[i];
|
|
236
|
+
if (line.match(/^\s*-?\s*name:\s*\S+/) && lines[i - 1]?.includes('containers:')) {
|
|
237
|
+
inContainer = true;
|
|
238
|
+
containerStartLine = i + 1;
|
|
239
|
+
hasSecurityContext = false;
|
|
240
|
+
}
|
|
241
|
+
if (inContainer && line.includes('securityContext:')) {
|
|
242
|
+
hasSecurityContext = true;
|
|
243
|
+
}
|
|
244
|
+
if (line.match(/runAsUser:\s*0/)) {
|
|
245
|
+
matches.push({
|
|
246
|
+
line: i + 1,
|
|
247
|
+
matchedText: line.trim(),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (inContainer && line.match(/^\s*-?\s*name:/) && i > containerStartLine) {
|
|
251
|
+
if (!hasSecurityContext) {
|
|
252
|
+
matches.push({
|
|
253
|
+
line: containerStartLine,
|
|
254
|
+
matchedText: 'Container without securityContext',
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
containerStartLine = i + 1;
|
|
258
|
+
hasSecurityContext = false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return matches;
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: 'K8S-002',
|
|
266
|
+
name: 'Privileged Container',
|
|
267
|
+
description: 'Containers should not run in privileged mode',
|
|
268
|
+
severity: 'critical',
|
|
269
|
+
fileTypes: ['kubernetes'],
|
|
270
|
+
cwes: ['CWE-250'],
|
|
271
|
+
references: ['https://kubernetes.io/docs/concepts/security/pod-security-standards/'],
|
|
272
|
+
remediation: 'Set securityContext.privileged = false or remove it',
|
|
273
|
+
check: (content, _filePath) => {
|
|
274
|
+
const matches = [];
|
|
275
|
+
const lines = content.split('\n');
|
|
276
|
+
for (let i = 0; i < lines.length; i++) {
|
|
277
|
+
if (lines[i].match(/privileged:\s*true/)) {
|
|
278
|
+
matches.push({
|
|
279
|
+
line: i + 1,
|
|
280
|
+
matchedText: lines[i].trim(),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return matches;
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
id: 'K8S-003',
|
|
289
|
+
name: 'Host Network Enabled',
|
|
290
|
+
description: 'Pods should not use host network',
|
|
291
|
+
severity: 'high',
|
|
292
|
+
fileTypes: ['kubernetes'],
|
|
293
|
+
cwes: ['CWE-668'],
|
|
294
|
+
references: ['https://kubernetes.io/docs/concepts/security/pod-security-standards/'],
|
|
295
|
+
remediation: 'Set hostNetwork = false or remove it',
|
|
296
|
+
check: (content, _filePath) => {
|
|
297
|
+
const matches = [];
|
|
298
|
+
const lines = content.split('\n');
|
|
299
|
+
for (let i = 0; i < lines.length; i++) {
|
|
300
|
+
if (lines[i].match(/hostNetwork:\s*true/)) {
|
|
301
|
+
matches.push({
|
|
302
|
+
line: i + 1,
|
|
303
|
+
matchedText: lines[i].trim(),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return matches;
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: 'K8S-004',
|
|
312
|
+
name: 'No Resource Limits',
|
|
313
|
+
description: 'Containers should have resource limits',
|
|
314
|
+
severity: 'medium',
|
|
315
|
+
fileTypes: ['kubernetes'],
|
|
316
|
+
cwes: ['CWE-400'],
|
|
317
|
+
references: ['https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'],
|
|
318
|
+
remediation: 'Add resources.limits for CPU and memory',
|
|
319
|
+
check: (content, _filePath) => {
|
|
320
|
+
const matches = [];
|
|
321
|
+
const lines = content.split('\n');
|
|
322
|
+
let inContainer = false;
|
|
323
|
+
let hasLimits = false;
|
|
324
|
+
let containerStartLine = 0;
|
|
325
|
+
for (let i = 0; i < lines.length; i++) {
|
|
326
|
+
const line = lines[i];
|
|
327
|
+
if (line.match(/^\s*-?\s*name:/) && lines[i - 1]?.includes('containers:')) {
|
|
328
|
+
if (inContainer && !hasLimits) {
|
|
329
|
+
matches.push({
|
|
330
|
+
line: containerStartLine,
|
|
331
|
+
matchedText: 'Container without resource limits',
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
inContainer = true;
|
|
335
|
+
containerStartLine = i + 1;
|
|
336
|
+
hasLimits = false;
|
|
337
|
+
}
|
|
338
|
+
if (inContainer && line.includes('limits:')) {
|
|
339
|
+
hasLimits = true;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Check last container
|
|
343
|
+
if (inContainer && !hasLimits) {
|
|
344
|
+
matches.push({
|
|
345
|
+
line: containerStartLine,
|
|
346
|
+
matchedText: 'Container without resource limits',
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return matches;
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
id: 'K8S-005',
|
|
354
|
+
name: 'Secrets in Environment Variables',
|
|
355
|
+
description: 'Secrets should not be exposed as environment variables',
|
|
356
|
+
severity: 'medium',
|
|
357
|
+
fileTypes: ['kubernetes'],
|
|
358
|
+
cwes: ['CWE-200'],
|
|
359
|
+
references: ['https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets'],
|
|
360
|
+
remediation: 'Use secretKeyRef with volume mounts instead',
|
|
361
|
+
check: (content, _filePath) => {
|
|
362
|
+
const matches = [];
|
|
363
|
+
const lines = content.split('\n');
|
|
364
|
+
for (let i = 0; i < lines.length; i++) {
|
|
365
|
+
// Check for inline secret values in env vars
|
|
366
|
+
if (lines[i].match(/^\s*-?\s*name:\s*(password|secret|key|token)/i) &&
|
|
367
|
+
lines[i + 1]?.match(/^\s*value:/)) {
|
|
368
|
+
matches.push({
|
|
369
|
+
line: i + 1,
|
|
370
|
+
matchedText: `${lines[i].trim()} with inline value`,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return matches;
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
];
|
|
378
|
+
/**
|
|
379
|
+
* Built-in CloudFormation rules
|
|
380
|
+
*/
|
|
381
|
+
const CLOUDFORMATION_RULES = [
|
|
382
|
+
{
|
|
383
|
+
id: 'CFN-001',
|
|
384
|
+
name: 'S3 Bucket Without Encryption',
|
|
385
|
+
description: 'S3 buckets should have encryption enabled',
|
|
386
|
+
severity: 'high',
|
|
387
|
+
fileTypes: ['cloudformation'],
|
|
388
|
+
cwes: ['CWE-311'],
|
|
389
|
+
references: ['https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html'],
|
|
390
|
+
remediation: 'Add BucketEncryption property with ServerSideEncryptionConfiguration',
|
|
391
|
+
check: (content, _filePath) => {
|
|
392
|
+
const matches = [];
|
|
393
|
+
const lines = content.split('\n');
|
|
394
|
+
let inS3Bucket = false;
|
|
395
|
+
let bucketStartLine = 0;
|
|
396
|
+
let hasEncryption = false;
|
|
397
|
+
for (let i = 0; i < lines.length; i++) {
|
|
398
|
+
const line = lines[i];
|
|
399
|
+
if (line.includes('AWS::S3::Bucket')) {
|
|
400
|
+
inS3Bucket = true;
|
|
401
|
+
bucketStartLine = i + 1;
|
|
402
|
+
hasEncryption = false;
|
|
403
|
+
}
|
|
404
|
+
if (inS3Bucket && line.includes('BucketEncryption')) {
|
|
405
|
+
hasEncryption = true;
|
|
406
|
+
}
|
|
407
|
+
// Simple heuristic for resource end
|
|
408
|
+
if (inS3Bucket && line.match(/^\s{2}\w+:/) && i > bucketStartLine + 2) {
|
|
409
|
+
if (!hasEncryption) {
|
|
410
|
+
matches.push({
|
|
411
|
+
line: bucketStartLine,
|
|
412
|
+
matchedText: 'S3 bucket without encryption',
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
inS3Bucket = false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return matches;
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
id: 'CFN-002',
|
|
423
|
+
name: 'Security Group Unrestricted Ingress',
|
|
424
|
+
description: 'Security groups should not allow unrestricted ingress',
|
|
425
|
+
severity: 'high',
|
|
426
|
+
fileTypes: ['cloudformation'],
|
|
427
|
+
cwes: ['CWE-284'],
|
|
428
|
+
references: ['https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html'],
|
|
429
|
+
remediation: 'Restrict CidrIp to specific IP ranges',
|
|
430
|
+
check: (content, _filePath) => {
|
|
431
|
+
const matches = [];
|
|
432
|
+
const lines = content.split('\n');
|
|
433
|
+
for (let i = 0; i < lines.length; i++) {
|
|
434
|
+
if (lines[i].match(/CidrIp:\s*['"]?0\.0\.0\.0\/0['"]?/)) {
|
|
435
|
+
matches.push({
|
|
436
|
+
line: i + 1,
|
|
437
|
+
matchedText: lines[i].trim(),
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return matches;
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
];
|
|
445
|
+
/**
|
|
446
|
+
* All built-in rules
|
|
447
|
+
*/
|
|
448
|
+
const BUILTIN_RULES = [
|
|
449
|
+
...TERRAFORM_RULES,
|
|
450
|
+
...KUBERNETES_RULES,
|
|
451
|
+
...CLOUDFORMATION_RULES,
|
|
452
|
+
];
|
|
453
|
+
/**
|
|
454
|
+
* IaC Checker implementation
|
|
455
|
+
* @trace DES-SEC2-IAC-001
|
|
456
|
+
*/
|
|
457
|
+
export class IaCChecker {
|
|
458
|
+
options;
|
|
459
|
+
rules;
|
|
460
|
+
constructor(options = {}) {
|
|
461
|
+
this.options = {
|
|
462
|
+
fileTypes: options.fileTypes ?? ['terraform', 'cloudformation', 'kubernetes'],
|
|
463
|
+
minSeverity: options.minSeverity ?? 'low',
|
|
464
|
+
skipRules: options.skipRules ?? [],
|
|
465
|
+
};
|
|
466
|
+
// Build rule set
|
|
467
|
+
this.rules = [
|
|
468
|
+
...BUILTIN_RULES.filter(r => !this.options.skipRules?.includes(r.id)),
|
|
469
|
+
...(options.customRules ?? []),
|
|
470
|
+
];
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Analyze IaC files in a directory
|
|
474
|
+
* @trace REQ-SEC2-IAC-001
|
|
475
|
+
*/
|
|
476
|
+
async analyze(dirPath, options) {
|
|
477
|
+
const mergedOptions = { ...this.options, ...options };
|
|
478
|
+
const results = [];
|
|
479
|
+
// Find IaC files
|
|
480
|
+
const files = this.findIaCFiles(dirPath, mergedOptions.fileTypes ?? []);
|
|
481
|
+
for (const file of files) {
|
|
482
|
+
const result = await this.analyzeFile(file.path, file.type, mergedOptions);
|
|
483
|
+
results.push(result);
|
|
484
|
+
}
|
|
485
|
+
return results;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Analyze a single IaC file
|
|
489
|
+
*/
|
|
490
|
+
async analyzeFile(filePath, fileType, options) {
|
|
491
|
+
const startTime = Date.now();
|
|
492
|
+
const mergedOptions = { ...this.options, ...options };
|
|
493
|
+
if (!existsSync(filePath)) {
|
|
494
|
+
throw new Error(`File not found: ${filePath}`);
|
|
495
|
+
}
|
|
496
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
497
|
+
const issues = [];
|
|
498
|
+
const resources = [];
|
|
499
|
+
// Get applicable rules
|
|
500
|
+
const applicableRules = this.rules.filter(r => r.fileTypes.includes(fileType) && !mergedOptions.skipRules?.includes(r.id));
|
|
501
|
+
// Run rules
|
|
502
|
+
const minSeverityLevel = this.getSeverityLevel(mergedOptions.minSeverity ?? 'low');
|
|
503
|
+
for (const rule of applicableRules) {
|
|
504
|
+
if (this.getSeverityLevel(rule.severity) < minSeverityLevel) {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const matches = rule.check(content, filePath);
|
|
508
|
+
for (const match of matches) {
|
|
509
|
+
issues.push({
|
|
510
|
+
id: `${rule.id}-${match.line}`,
|
|
511
|
+
ruleId: rule.id,
|
|
512
|
+
severity: rule.severity,
|
|
513
|
+
title: rule.name,
|
|
514
|
+
description: rule.description,
|
|
515
|
+
location: {
|
|
516
|
+
file: filePath,
|
|
517
|
+
startLine: match.line,
|
|
518
|
+
endLine: match.endLine ?? match.line,
|
|
519
|
+
startColumn: match.column ?? 0,
|
|
520
|
+
endColumn: match.endColumn ?? 0,
|
|
521
|
+
},
|
|
522
|
+
remediation: rule.remediation,
|
|
523
|
+
references: rule.references,
|
|
524
|
+
cwes: rule.cwes,
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// Extract resources (simplified)
|
|
529
|
+
resources.push(...this.extractResources(content, fileType, filePath));
|
|
530
|
+
return {
|
|
531
|
+
filePath,
|
|
532
|
+
fileType,
|
|
533
|
+
issues,
|
|
534
|
+
resources,
|
|
535
|
+
analysisTime: Date.now() - startTime,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Convert IaC issues to standard vulnerability format
|
|
540
|
+
*/
|
|
541
|
+
toVulnerabilities(results) {
|
|
542
|
+
const vulnerabilities = [];
|
|
543
|
+
for (const result of results) {
|
|
544
|
+
for (const issue of result.issues) {
|
|
545
|
+
vulnerabilities.push({
|
|
546
|
+
id: issue.id,
|
|
547
|
+
type: 'configuration',
|
|
548
|
+
severity: issue.severity,
|
|
549
|
+
cwes: issue.cwes ?? [],
|
|
550
|
+
owasp: ['A05:2021'], // Security Misconfiguration
|
|
551
|
+
location: issue.location,
|
|
552
|
+
description: `${issue.title}: ${issue.description}`,
|
|
553
|
+
recommendation: issue.remediation,
|
|
554
|
+
confidence: 0.9,
|
|
555
|
+
ruleId: issue.ruleId,
|
|
556
|
+
codeSnippet: '',
|
|
557
|
+
detectedAt: new Date(),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return vulnerabilities;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Find IaC files in a directory
|
|
565
|
+
*/
|
|
566
|
+
findIaCFiles(dirPath, fileTypes) {
|
|
567
|
+
const files = [];
|
|
568
|
+
if (!existsSync(dirPath)) {
|
|
569
|
+
return files;
|
|
570
|
+
}
|
|
571
|
+
const scan = (dir) => {
|
|
572
|
+
const entries = readdirSync(dir);
|
|
573
|
+
for (const entry of entries) {
|
|
574
|
+
const fullPath = path.join(dir, entry);
|
|
575
|
+
const stat = statSync(fullPath);
|
|
576
|
+
if (stat.isDirectory()) {
|
|
577
|
+
// Skip common non-IaC directories
|
|
578
|
+
if (!['node_modules', '.git', '.terraform', 'vendor'].includes(entry)) {
|
|
579
|
+
scan(fullPath);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
const type = this.detectFileType(entry, fullPath);
|
|
584
|
+
if (type && fileTypes.includes(type)) {
|
|
585
|
+
files.push({ path: fullPath, type });
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
const stat = statSync(dirPath);
|
|
591
|
+
if (stat.isFile()) {
|
|
592
|
+
const type = this.detectFileType(path.basename(dirPath), dirPath);
|
|
593
|
+
if (type && fileTypes.includes(type)) {
|
|
594
|
+
files.push({ path: dirPath, type });
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
scan(dirPath);
|
|
599
|
+
}
|
|
600
|
+
return files;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Detect IaC file type
|
|
604
|
+
*/
|
|
605
|
+
detectFileType(filename, filePath) {
|
|
606
|
+
// Terraform
|
|
607
|
+
if (filename.endsWith('.tf') || filename.endsWith('.tf.json')) {
|
|
608
|
+
return 'terraform';
|
|
609
|
+
}
|
|
610
|
+
// Kubernetes
|
|
611
|
+
if (filename.endsWith('.yaml') || filename.endsWith('.yml')) {
|
|
612
|
+
try {
|
|
613
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
614
|
+
if (content.includes('apiVersion:') && content.includes('kind:')) {
|
|
615
|
+
return 'kubernetes';
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
catch {
|
|
619
|
+
// Ignore read errors
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
// CloudFormation
|
|
623
|
+
if (filename.includes('cloudformation') || filename.includes('cfn')) {
|
|
624
|
+
return 'cloudformation';
|
|
625
|
+
}
|
|
626
|
+
if ((filename.endsWith('.yaml') || filename.endsWith('.yml') || filename.endsWith('.json'))) {
|
|
627
|
+
try {
|
|
628
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
629
|
+
if (content.includes('AWSTemplateFormatVersion') || content.includes('AWS::')) {
|
|
630
|
+
return 'cloudformation';
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
catch {
|
|
634
|
+
// Ignore read errors
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
// Docker Compose
|
|
638
|
+
if (filename === 'docker-compose.yml' || filename === 'docker-compose.yaml') {
|
|
639
|
+
return 'docker-compose';
|
|
640
|
+
}
|
|
641
|
+
// Helm
|
|
642
|
+
if (filename === 'Chart.yaml' || (filename.endsWith('.yaml') && filePath.includes('/templates/'))) {
|
|
643
|
+
return 'helm';
|
|
644
|
+
}
|
|
645
|
+
// Ansible
|
|
646
|
+
if (filename.endsWith('.yml') || filename.endsWith('.yaml')) {
|
|
647
|
+
try {
|
|
648
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
649
|
+
if (content.includes('- hosts:') || content.includes('- name:') && content.includes('tasks:')) {
|
|
650
|
+
return 'ansible';
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
catch {
|
|
654
|
+
// Ignore read errors
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Extract resources from IaC content
|
|
661
|
+
*/
|
|
662
|
+
extractResources(content, fileType, filePath) {
|
|
663
|
+
const resources = [];
|
|
664
|
+
const lines = content.split('\n');
|
|
665
|
+
if (fileType === 'terraform') {
|
|
666
|
+
// Extract Terraform resources
|
|
667
|
+
const resourcePattern = /resource\s+"([^"]+)"\s+"([^"]+)"/;
|
|
668
|
+
for (let i = 0; i < lines.length; i++) {
|
|
669
|
+
const match = lines[i].match(resourcePattern);
|
|
670
|
+
if (match) {
|
|
671
|
+
resources.push({
|
|
672
|
+
type: match[1],
|
|
673
|
+
name: match[2],
|
|
674
|
+
location: {
|
|
675
|
+
file: filePath,
|
|
676
|
+
startLine: i + 1,
|
|
677
|
+
endLine: i + 1,
|
|
678
|
+
startColumn: 0,
|
|
679
|
+
endColumn: lines[i].length,
|
|
680
|
+
},
|
|
681
|
+
properties: {},
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
else if (fileType === 'kubernetes') {
|
|
687
|
+
// Extract Kubernetes resources
|
|
688
|
+
let currentKind = '';
|
|
689
|
+
let currentName = '';
|
|
690
|
+
let kindLine = 0;
|
|
691
|
+
for (let i = 0; i < lines.length; i++) {
|
|
692
|
+
const kindMatch = lines[i].match(/^kind:\s*(\S+)/);
|
|
693
|
+
const nameMatch = lines[i].match(/^\s*name:\s*(\S+)/);
|
|
694
|
+
if (kindMatch) {
|
|
695
|
+
if (currentKind && currentName) {
|
|
696
|
+
resources.push({
|
|
697
|
+
type: currentKind,
|
|
698
|
+
name: currentName,
|
|
699
|
+
location: {
|
|
700
|
+
file: filePath,
|
|
701
|
+
startLine: kindLine,
|
|
702
|
+
endLine: i,
|
|
703
|
+
startColumn: 0,
|
|
704
|
+
endColumn: 0,
|
|
705
|
+
},
|
|
706
|
+
properties: {},
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
currentKind = kindMatch[1];
|
|
710
|
+
kindLine = i + 1;
|
|
711
|
+
currentName = '';
|
|
712
|
+
}
|
|
713
|
+
if (nameMatch && currentKind && !currentName) {
|
|
714
|
+
currentName = nameMatch[1];
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
// Add last resource
|
|
718
|
+
if (currentKind && currentName) {
|
|
719
|
+
resources.push({
|
|
720
|
+
type: currentKind,
|
|
721
|
+
name: currentName,
|
|
722
|
+
location: {
|
|
723
|
+
file: filePath,
|
|
724
|
+
startLine: kindLine,
|
|
725
|
+
endLine: lines.length,
|
|
726
|
+
startColumn: 0,
|
|
727
|
+
endColumn: 0,
|
|
728
|
+
},
|
|
729
|
+
properties: {},
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return resources;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Get numeric severity level
|
|
737
|
+
*/
|
|
738
|
+
getSeverityLevel(severity) {
|
|
739
|
+
const levels = {
|
|
740
|
+
critical: 4,
|
|
741
|
+
high: 3,
|
|
742
|
+
medium: 2,
|
|
743
|
+
low: 1,
|
|
744
|
+
info: 0,
|
|
745
|
+
};
|
|
746
|
+
return levels[severity] ?? 0;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Create IaC checker instance
|
|
751
|
+
*/
|
|
752
|
+
export function createIaCChecker(options) {
|
|
753
|
+
return new IaCChecker(options);
|
|
754
|
+
}
|
|
755
|
+
//# sourceMappingURL=iac-checker.js.map
|