infrawise 0.8.2 → 0.8.3
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 +1 -47
- package/dist/adapters/aws/logs.js +3 -1
- package/dist/adapters/aws/s3.js +14 -5
- package/dist/adapters/aws/services.js +12 -7
- package/dist/adapters/db/mongodb.js +1 -3
- package/dist/adapters/iac/terraform.js +52 -25
- package/dist/analyzers/aws-services.js +5 -1
- package/dist/analyzers/rds.js +10 -2
- package/dist/analyzers/terraform.js +18 -3
- package/dist/cli/commands/analyze.js +149 -102
- package/dist/cli/commands/auth.js +4 -1
- package/dist/cli/commands/dev.js +4 -2
- package/dist/cli/commands/doctor.js +64 -24
- package/dist/cli/commands/init.js +25 -7
- package/dist/cli/utils.js +23 -9
- package/dist/context/index.js +36 -9
- package/dist/core/config.js +38 -16
- package/dist/core/index.js +1 -1
- package/dist/graph/index.js +63 -9
- package/dist/server/index.js +170 -51
- package/package.json +18 -5
|
@@ -39,7 +39,9 @@ function buildMarkdownReport(findings, projectName) {
|
|
|
39
39
|
renderGroup('Verify (check intent)', '🔵', verify),
|
|
40
40
|
'',
|
|
41
41
|
'_Generated by [infrawise](https://github.com/Sidd27/infrawise) — MCP server for AWS infrastructure analysis_',
|
|
42
|
-
]
|
|
42
|
+
]
|
|
43
|
+
.filter((l) => l !== undefined)
|
|
44
|
+
.join('\n');
|
|
43
45
|
}
|
|
44
46
|
function mkSpinner(text) {
|
|
45
47
|
return ora({ text: chalk.dim(text), color: 'cyan' }).start();
|
|
@@ -56,8 +58,12 @@ export async function runAnalyze(options = {}) {
|
|
|
56
58
|
process.exit(1);
|
|
57
59
|
}
|
|
58
60
|
const repoPath = options.repo ?? process.cwd();
|
|
59
|
-
const minSeverity = options.severity ? SEVERITY_ORDER[options.severity] ?? 1 : 0;
|
|
60
|
-
const awsCfg = {
|
|
61
|
+
const minSeverity = options.severity ? (SEVERITY_ORDER[options.severity] ?? 1) : 0;
|
|
62
|
+
const awsCfg = {
|
|
63
|
+
region: config.aws?.region,
|
|
64
|
+
profile: config.aws?.profile,
|
|
65
|
+
endpoint: config.aws?.endpoint,
|
|
66
|
+
};
|
|
61
67
|
const dynamoMeta = [];
|
|
62
68
|
const postgresMeta = [];
|
|
63
69
|
const mysqlMeta = [];
|
|
@@ -72,7 +78,8 @@ export async function runAnalyze(options = {}) {
|
|
|
72
78
|
s.succeed(chalk.green('DynamoDB') + chalk.dim(` ${result.length} table(s)`));
|
|
73
79
|
}
|
|
74
80
|
catch (err) {
|
|
75
|
-
s.warn(chalk.yellow('DynamoDB skipped') +
|
|
81
|
+
s.warn(chalk.yellow('DynamoDB skipped') +
|
|
82
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
76
83
|
}
|
|
77
84
|
}
|
|
78
85
|
// ── PostgreSQL ──────────────────────────────────────────────────────────────
|
|
@@ -84,7 +91,8 @@ export async function runAnalyze(options = {}) {
|
|
|
84
91
|
s.succeed(chalk.green('PostgreSQL') + chalk.dim(` ${result.length} table(s)`));
|
|
85
92
|
}
|
|
86
93
|
catch (err) {
|
|
87
|
-
s.warn(chalk.yellow('PostgreSQL skipped') +
|
|
94
|
+
s.warn(chalk.yellow('PostgreSQL skipped') +
|
|
95
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
88
96
|
}
|
|
89
97
|
}
|
|
90
98
|
// ── MySQL ───────────────────────────────────────────────────────────────────
|
|
@@ -96,7 +104,8 @@ export async function runAnalyze(options = {}) {
|
|
|
96
104
|
s.succeed(chalk.green('MySQL') + chalk.dim(` ${result.length} table(s)`));
|
|
97
105
|
}
|
|
98
106
|
catch (err) {
|
|
99
|
-
s.warn(chalk.yellow('MySQL skipped') +
|
|
107
|
+
s.warn(chalk.yellow('MySQL skipped') +
|
|
108
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
100
109
|
}
|
|
101
110
|
}
|
|
102
111
|
// ── MongoDB ─────────────────────────────────────────────────────────────────
|
|
@@ -108,7 +117,8 @@ export async function runAnalyze(options = {}) {
|
|
|
108
117
|
s.succeed(chalk.green('MongoDB') + chalk.dim(` ${result.length} collection(s)`));
|
|
109
118
|
}
|
|
110
119
|
catch (err) {
|
|
111
|
-
s.warn(chalk.yellow('MongoDB skipped') +
|
|
120
|
+
s.warn(chalk.yellow('MongoDB skipped') +
|
|
121
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
112
122
|
}
|
|
113
123
|
}
|
|
114
124
|
// ── SQS ─────────────────────────────────────────────────────────────────────
|
|
@@ -120,7 +130,8 @@ export async function runAnalyze(options = {}) {
|
|
|
120
130
|
s.succeed(chalk.green('SQS') + chalk.dim(` ${result.length} queue(s)`));
|
|
121
131
|
}
|
|
122
132
|
catch (err) {
|
|
123
|
-
s.warn(chalk.yellow('SQS skipped') +
|
|
133
|
+
s.warn(chalk.yellow('SQS skipped') +
|
|
134
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
124
135
|
}
|
|
125
136
|
}
|
|
126
137
|
// ── SNS ─────────────────────────────────────────────────────────────────────
|
|
@@ -132,7 +143,8 @@ export async function runAnalyze(options = {}) {
|
|
|
132
143
|
s.succeed(chalk.green('SNS') + chalk.dim(` ${result.length} topic(s)`));
|
|
133
144
|
}
|
|
134
145
|
catch (err) {
|
|
135
|
-
s.warn(chalk.yellow('SNS skipped') +
|
|
146
|
+
s.warn(chalk.yellow('SNS skipped') +
|
|
147
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
136
148
|
}
|
|
137
149
|
}
|
|
138
150
|
// ── SSM Parameter Store ──────────────────────────────────────────────────────
|
|
@@ -141,10 +153,13 @@ export async function runAnalyze(options = {}) {
|
|
|
141
153
|
try {
|
|
142
154
|
const result = await extractSSMMetadata({ ...awsCfg, paths: config.ssm?.paths });
|
|
143
155
|
servicesMeta.ssm = result;
|
|
144
|
-
s.succeed(chalk.green('SSM') +
|
|
156
|
+
s.succeed(chalk.green('SSM') +
|
|
157
|
+
chalk.dim(` ${result.length} parameter(s) `) +
|
|
158
|
+
chalk.dim('(metadata only, no values)'));
|
|
145
159
|
}
|
|
146
160
|
catch (err) {
|
|
147
|
-
s.warn(chalk.yellow('SSM skipped') +
|
|
161
|
+
s.warn(chalk.yellow('SSM skipped') +
|
|
162
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
148
163
|
}
|
|
149
164
|
}
|
|
150
165
|
// ── Secrets Manager ──────────────────────────────────────────────────────────
|
|
@@ -153,10 +168,13 @@ export async function runAnalyze(options = {}) {
|
|
|
153
168
|
try {
|
|
154
169
|
const result = await extractSecretsMetadata(awsCfg);
|
|
155
170
|
servicesMeta.secrets = result;
|
|
156
|
-
s.succeed(chalk.green('Secrets Manager') +
|
|
171
|
+
s.succeed(chalk.green('Secrets Manager') +
|
|
172
|
+
chalk.dim(` ${result.length} secret(s) `) +
|
|
173
|
+
chalk.dim('(names/rotation only, no values)'));
|
|
157
174
|
}
|
|
158
175
|
catch (err) {
|
|
159
|
-
s.warn(chalk.yellow('Secrets Manager skipped') +
|
|
176
|
+
s.warn(chalk.yellow('Secrets Manager skipped') +
|
|
177
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
160
178
|
}
|
|
161
179
|
}
|
|
162
180
|
// ── Lambda ───────────────────────────────────────────────────────────────────
|
|
@@ -168,7 +186,8 @@ export async function runAnalyze(options = {}) {
|
|
|
168
186
|
s.succeed(chalk.green('Lambda') + chalk.dim(` ${result.length} function(s)`));
|
|
169
187
|
}
|
|
170
188
|
catch (err) {
|
|
171
|
-
s.warn(chalk.yellow('Lambda skipped') +
|
|
189
|
+
s.warn(chalk.yellow('Lambda skipped') +
|
|
190
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
172
191
|
}
|
|
173
192
|
}
|
|
174
193
|
// ── EventBridge ──────────────────────────────────────────────────────────────
|
|
@@ -180,7 +199,8 @@ export async function runAnalyze(options = {}) {
|
|
|
180
199
|
s.succeed(chalk.green('EventBridge') + chalk.dim(` ${result.length} rule(s)`));
|
|
181
200
|
}
|
|
182
201
|
catch (err) {
|
|
183
|
-
s.warn(chalk.yellow('EventBridge skipped') +
|
|
202
|
+
s.warn(chalk.yellow('EventBridge skipped') +
|
|
203
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
184
204
|
}
|
|
185
205
|
}
|
|
186
206
|
// ── RDS ──────────────────────────────────────────────────────────────────────
|
|
@@ -192,7 +212,8 @@ export async function runAnalyze(options = {}) {
|
|
|
192
212
|
s.succeed(chalk.green('RDS') + chalk.dim(` ${result.length} instance(s)`));
|
|
193
213
|
}
|
|
194
214
|
catch (err) {
|
|
195
|
-
s.warn(chalk.yellow('RDS skipped') +
|
|
215
|
+
s.warn(chalk.yellow('RDS skipped') +
|
|
216
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
196
217
|
}
|
|
197
218
|
}
|
|
198
219
|
// ── S3 ────────────────────────────────────────────────────────────────────────
|
|
@@ -204,7 +225,8 @@ export async function runAnalyze(options = {}) {
|
|
|
204
225
|
s.succeed(chalk.green('S3') + chalk.dim(` ${result.length} bucket(s)`));
|
|
205
226
|
}
|
|
206
227
|
catch (err) {
|
|
207
|
-
s.warn(chalk.yellow('S3 skipped') +
|
|
228
|
+
s.warn(chalk.yellow('S3 skipped') +
|
|
229
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
208
230
|
}
|
|
209
231
|
}
|
|
210
232
|
// ── CloudWatch Logs ──────────────────────────────────────────────────────────
|
|
@@ -218,10 +240,12 @@ export async function runAnalyze(options = {}) {
|
|
|
218
240
|
});
|
|
219
241
|
servicesMeta.logs = result;
|
|
220
242
|
const errorGroups = result.filter((lg) => lg.errorCount > 0).length;
|
|
221
|
-
s.succeed(chalk.green('CloudWatch Logs') +
|
|
243
|
+
s.succeed(chalk.green('CloudWatch Logs') +
|
|
244
|
+
chalk.dim(` ${result.length} group(s), ${errorGroups} with errors`));
|
|
222
245
|
}
|
|
223
246
|
catch (err) {
|
|
224
|
-
s.warn(chalk.yellow('CloudWatch Logs skipped') +
|
|
247
|
+
s.warn(chalk.yellow('CloudWatch Logs skipped') +
|
|
248
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
225
249
|
}
|
|
226
250
|
}
|
|
227
251
|
// ── IaC schema (Terraform / CloudFormation / CDK) ────────────────────────────
|
|
@@ -230,16 +254,23 @@ export async function runAnalyze(options = {}) {
|
|
|
230
254
|
const s = mkSpinner('Extracting IaC schema (Terraform / CloudFormation / CDK)...');
|
|
231
255
|
try {
|
|
232
256
|
const iacSchema = await extractIaCSchema(repoPath);
|
|
233
|
-
const total = iacSchema.dynamoTables.length +
|
|
234
|
-
iacSchema.
|
|
235
|
-
iacSchema.
|
|
236
|
-
iacSchema.
|
|
257
|
+
const total = iacSchema.dynamoTables.length +
|
|
258
|
+
iacSchema.rdsInstances.length +
|
|
259
|
+
iacSchema.mongoClusters.length +
|
|
260
|
+
iacSchema.queues.length +
|
|
261
|
+
iacSchema.topics.length +
|
|
262
|
+
iacSchema.lambdas.length +
|
|
263
|
+
iacSchema.buckets.length +
|
|
264
|
+
iacSchema.parameters.length +
|
|
265
|
+
iacSchema.secrets.length +
|
|
266
|
+
iacSchema.apiGateways.length;
|
|
237
267
|
iacDriftAnalyzer = new IaCDriftAnalyzer();
|
|
238
268
|
iacDriftAnalyzer.setIaCSchema(iacSchema);
|
|
239
269
|
s.succeed(chalk.green('IaC schema') + chalk.dim(` ${total} resource(s) across TF/CFN/CDK`));
|
|
240
270
|
}
|
|
241
271
|
catch (err) {
|
|
242
|
-
s.warn(chalk.yellow('IaC scan skipped') +
|
|
272
|
+
s.warn(chalk.yellow('IaC scan skipped') +
|
|
273
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
243
274
|
}
|
|
244
275
|
}
|
|
245
276
|
// ── Repository scan ──────────────────────────────────────────────────────────
|
|
@@ -248,10 +279,12 @@ export async function runAnalyze(options = {}) {
|
|
|
248
279
|
const s = mkSpinner(`Scanning ${path.basename(repoPath)} for service usage...`);
|
|
249
280
|
try {
|
|
250
281
|
operations = await scanRepository(repoPath);
|
|
251
|
-
s.succeed(chalk.green('Repository scanned') +
|
|
282
|
+
s.succeed(chalk.green('Repository scanned') +
|
|
283
|
+
chalk.dim(` ${operations.length} service operation(s) found`));
|
|
252
284
|
}
|
|
253
285
|
catch (err) {
|
|
254
|
-
s.warn(chalk.yellow('Repository scan failed') +
|
|
286
|
+
s.warn(chalk.yellow('Repository scan failed') +
|
|
287
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
255
288
|
operations = [];
|
|
256
289
|
}
|
|
257
290
|
}
|
|
@@ -260,59 +293,58 @@ export async function runAnalyze(options = {}) {
|
|
|
260
293
|
{
|
|
261
294
|
const s = mkSpinner('Building infrastructure graph...');
|
|
262
295
|
graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
|
|
263
|
-
s.succeed(chalk.green('Graph built') +
|
|
296
|
+
s.succeed(chalk.green('Graph built') +
|
|
297
|
+
chalk.dim(` ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
|
|
264
298
|
}
|
|
265
299
|
// ── Run analyzers ────────────────────────────────────────────────────────────
|
|
266
300
|
let findings;
|
|
267
301
|
{
|
|
268
302
|
const s = mkSpinner('Running analyzers...');
|
|
269
303
|
const analyzers = [
|
|
270
|
-
...(config.dynamodb?.enabled === true
|
|
271
|
-
new FullTableScanAnalyzer(),
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
new
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
new S3UnencryptedAnalyzer(),
|
|
315
|
-
] : []),
|
|
304
|
+
...(config.dynamodb?.enabled === true
|
|
305
|
+
? [new FullTableScanAnalyzer(), new MissingGSIAnalyzer(), new HotPartitionAnalyzer()]
|
|
306
|
+
: []),
|
|
307
|
+
...(config.postgres?.enabled
|
|
308
|
+
? [new MissingIndexAnalyzer(), new NplusOneAnalyzer(), new LargeSelectAnalyzer()]
|
|
309
|
+
: []),
|
|
310
|
+
...(config.mysql?.enabled
|
|
311
|
+
? [new MissingMySQLIndexAnalyzer(), new MySQLFullTableScanAnalyzer()]
|
|
312
|
+
: []),
|
|
313
|
+
...(config.mongodb?.enabled
|
|
314
|
+
? [new MissingMongoIndexAnalyzer(), new MongoCollectionScanAnalyzer()]
|
|
315
|
+
: []),
|
|
316
|
+
...(config.sqs?.enabled === true
|
|
317
|
+
? [
|
|
318
|
+
new MissingDLQAnalyzer(),
|
|
319
|
+
new UnencryptedQueueAnalyzer(),
|
|
320
|
+
new LargeQueueBacklogAnalyzer(),
|
|
321
|
+
]
|
|
322
|
+
: []),
|
|
323
|
+
...(config.secretsManager?.enabled === true ? [new MissingSecretRotationAnalyzer()] : []),
|
|
324
|
+
...(config.cloudwatchLogs?.enabled ? [new MissingLogRetentionAnalyzer()] : []),
|
|
325
|
+
...(config.lambda?.enabled === true
|
|
326
|
+
? [
|
|
327
|
+
new LambdaDefaultMemoryAnalyzer(),
|
|
328
|
+
new LambdaHighTimeoutAnalyzer(),
|
|
329
|
+
new LambdaMissingTriggerDLQAnalyzer(),
|
|
330
|
+
]
|
|
331
|
+
: []),
|
|
332
|
+
...(config.rds?.enabled === true
|
|
333
|
+
? [
|
|
334
|
+
new RDSPubliclyAccessibleAnalyzer(),
|
|
335
|
+
new RDSNoBackupAnalyzer(),
|
|
336
|
+
new RDSUnencryptedAnalyzer(),
|
|
337
|
+
new RDSNoDeletionProtectionAnalyzer(),
|
|
338
|
+
new RDSNoMultiAZAnalyzer(),
|
|
339
|
+
]
|
|
340
|
+
: []),
|
|
341
|
+
...(config.s3?.enabled === true
|
|
342
|
+
? [
|
|
343
|
+
new S3PublicAccessAnalyzer(),
|
|
344
|
+
new S3MissingVersioningAnalyzer(),
|
|
345
|
+
new S3UnencryptedAnalyzer(),
|
|
346
|
+
]
|
|
347
|
+
: []),
|
|
316
348
|
...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
|
|
317
349
|
];
|
|
318
350
|
findings = await runAllAnalyzers(graph, analyzers);
|
|
@@ -322,14 +354,22 @@ export async function runAnalyze(options = {}) {
|
|
|
322
354
|
writeCache('graph', graph);
|
|
323
355
|
writeCache('findings', findings);
|
|
324
356
|
writeCache('operations', operations);
|
|
325
|
-
writeCache('meta', {
|
|
357
|
+
writeCache('meta', {
|
|
358
|
+
dynamoMeta,
|
|
359
|
+
postgresMeta,
|
|
360
|
+
mysqlMeta,
|
|
361
|
+
mongoMeta,
|
|
362
|
+
servicesMeta,
|
|
363
|
+
});
|
|
326
364
|
// ── Output ────────────────────────────────────────────────────────────────────
|
|
327
365
|
const displayFindings = minSeverity > 0
|
|
328
366
|
? findings.filter((f) => (SEVERITY_ORDER[f.severity] ?? 0) >= minSeverity)
|
|
329
367
|
: findings;
|
|
330
368
|
console.log('');
|
|
331
369
|
if (displayFindings.length === 0) {
|
|
332
|
-
const msg = minSeverity > 0
|
|
370
|
+
const msg = minSeverity > 0
|
|
371
|
+
? `No ${options.severity} (or higher) severity issues found.`
|
|
372
|
+
: 'Your infrastructure looks clean.';
|
|
333
373
|
console.log(` ${chalk.green.bold('✓ No issues found!')} ${chalk.dim(msg)}`);
|
|
334
374
|
}
|
|
335
375
|
else {
|
|
@@ -380,35 +420,42 @@ export async function runCodeRefresh(repoPath, config) {
|
|
|
380
420
|
}
|
|
381
421
|
const graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
|
|
382
422
|
const analyzers = [
|
|
383
|
-
...(config.dynamodb?.enabled === true
|
|
384
|
-
new FullTableScanAnalyzer(), new MissingGSIAnalyzer(), new HotPartitionAnalyzer()
|
|
385
|
-
|
|
386
|
-
...(config.postgres?.enabled
|
|
387
|
-
new MissingIndexAnalyzer(), new NplusOneAnalyzer(), new LargeSelectAnalyzer()
|
|
388
|
-
|
|
389
|
-
...(config.mysql?.enabled
|
|
390
|
-
new MissingMySQLIndexAnalyzer(), new MySQLFullTableScanAnalyzer()
|
|
391
|
-
|
|
392
|
-
...(config.mongodb?.enabled
|
|
393
|
-
new MissingMongoIndexAnalyzer(), new MongoCollectionScanAnalyzer()
|
|
394
|
-
|
|
395
|
-
...(config.sqs?.enabled === true
|
|
396
|
-
new MissingDLQAnalyzer(), new UnencryptedQueueAnalyzer(), new LargeQueueBacklogAnalyzer()
|
|
397
|
-
|
|
423
|
+
...(config.dynamodb?.enabled === true
|
|
424
|
+
? [new FullTableScanAnalyzer(), new MissingGSIAnalyzer(), new HotPartitionAnalyzer()]
|
|
425
|
+
: []),
|
|
426
|
+
...(config.postgres?.enabled
|
|
427
|
+
? [new MissingIndexAnalyzer(), new NplusOneAnalyzer(), new LargeSelectAnalyzer()]
|
|
428
|
+
: []),
|
|
429
|
+
...(config.mysql?.enabled
|
|
430
|
+
? [new MissingMySQLIndexAnalyzer(), new MySQLFullTableScanAnalyzer()]
|
|
431
|
+
: []),
|
|
432
|
+
...(config.mongodb?.enabled
|
|
433
|
+
? [new MissingMongoIndexAnalyzer(), new MongoCollectionScanAnalyzer()]
|
|
434
|
+
: []),
|
|
435
|
+
...(config.sqs?.enabled === true
|
|
436
|
+
? [new MissingDLQAnalyzer(), new UnencryptedQueueAnalyzer(), new LargeQueueBacklogAnalyzer()]
|
|
437
|
+
: []),
|
|
398
438
|
...(config.secretsManager?.enabled === true ? [new MissingSecretRotationAnalyzer()] : []),
|
|
399
439
|
...(config.cloudwatchLogs?.enabled ? [new MissingLogRetentionAnalyzer()] : []),
|
|
400
|
-
...(config.lambda?.enabled === true
|
|
401
|
-
new LambdaDefaultMemoryAnalyzer(), new LambdaHighTimeoutAnalyzer()
|
|
402
|
-
|
|
403
|
-
...(config.rds?.enabled === true
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
440
|
+
...(config.lambda?.enabled === true
|
|
441
|
+
? [new LambdaDefaultMemoryAnalyzer(), new LambdaHighTimeoutAnalyzer()]
|
|
442
|
+
: []),
|
|
443
|
+
...(config.rds?.enabled === true
|
|
444
|
+
? [
|
|
445
|
+
new RDSPubliclyAccessibleAnalyzer(),
|
|
446
|
+
new RDSNoBackupAnalyzer(),
|
|
447
|
+
new RDSUnencryptedAnalyzer(),
|
|
448
|
+
new RDSNoDeletionProtectionAnalyzer(),
|
|
449
|
+
new RDSNoMultiAZAnalyzer(),
|
|
450
|
+
]
|
|
451
|
+
: []),
|
|
452
|
+
...(config.s3?.enabled === true
|
|
453
|
+
? [
|
|
454
|
+
new S3PublicAccessAnalyzer(),
|
|
455
|
+
new S3MissingVersioningAnalyzer(),
|
|
456
|
+
new S3UnencryptedAnalyzer(),
|
|
457
|
+
]
|
|
458
|
+
: []),
|
|
412
459
|
...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
|
|
413
460
|
];
|
|
414
461
|
const findings = await runAllAnalyzers(graph, analyzers);
|
|
@@ -25,7 +25,10 @@ export async function runAuth() {
|
|
|
25
25
|
},
|
|
26
26
|
]);
|
|
27
27
|
console.log('');
|
|
28
|
-
const spin = ora({
|
|
28
|
+
const spin = ora({
|
|
29
|
+
text: chalk.dim(`Validating "${selectedProfile}"...`),
|
|
30
|
+
color: 'cyan',
|
|
31
|
+
}).start();
|
|
29
32
|
const testConfig = {
|
|
30
33
|
project: 'auth-test',
|
|
31
34
|
aws: { profile: selectedProfile, region: 'us-east-1' },
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -147,10 +147,12 @@ export async function runDev(options = {}) {
|
|
|
147
147
|
try {
|
|
148
148
|
const { graph, findings } = await runCodeRefresh(repoPath, config);
|
|
149
149
|
setGraphState(graph, findings, config);
|
|
150
|
-
spin.succeed(chalk.green('Analysis refreshed') +
|
|
150
|
+
spin.succeed(chalk.green('Analysis refreshed') +
|
|
151
|
+
chalk.dim(` ${graph.nodes.length} nodes · ${findings.length} finding(s)`));
|
|
151
152
|
}
|
|
152
153
|
catch (err) {
|
|
153
|
-
spin.warn(chalk.yellow('Refresh failed') +
|
|
154
|
+
spin.warn(chalk.yellow('Refresh failed') +
|
|
155
|
+
chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
154
156
|
}
|
|
155
157
|
finally {
|
|
156
158
|
refreshing = false;
|
|
@@ -58,7 +58,9 @@ export async function runDoctor(options = {}) {
|
|
|
58
58
|
}
|
|
59
59
|
catch (err) {
|
|
60
60
|
return {
|
|
61
|
-
name: 'Config validation',
|
|
61
|
+
name: 'Config validation',
|
|
62
|
+
status: 'fail',
|
|
63
|
+
message: 'Invalid config',
|
|
62
64
|
detail: err instanceof Error ? err.message : String(err),
|
|
63
65
|
};
|
|
64
66
|
}
|
|
@@ -75,7 +77,11 @@ export async function runDoctor(options = {}) {
|
|
|
75
77
|
detail: hasCreds ? undefined : 'Run: aws configure',
|
|
76
78
|
};
|
|
77
79
|
}));
|
|
78
|
-
const awsCfg = {
|
|
80
|
+
const awsCfg = {
|
|
81
|
+
region: config?.aws?.region,
|
|
82
|
+
profile: config?.aws?.profile,
|
|
83
|
+
endpoint: config?.aws?.endpoint,
|
|
84
|
+
};
|
|
79
85
|
// DynamoDB
|
|
80
86
|
results.push(await runCheck('Testing DynamoDB access...', async () => {
|
|
81
87
|
if (!config)
|
|
@@ -84,11 +90,16 @@ export async function runDoctor(options = {}) {
|
|
|
84
90
|
return { name: 'DynamoDB', status: 'skip', message: 'Disabled in config' };
|
|
85
91
|
try {
|
|
86
92
|
await probeDynamoAccess(config);
|
|
87
|
-
return {
|
|
93
|
+
return {
|
|
94
|
+
name: 'DynamoDB',
|
|
95
|
+
status: 'pass',
|
|
96
|
+
message: `Connected (profile: ${config.aws?.profile ?? 'default'})`,
|
|
97
|
+
};
|
|
88
98
|
}
|
|
89
99
|
catch (err) {
|
|
90
100
|
return {
|
|
91
|
-
name: 'DynamoDB',
|
|
101
|
+
name: 'DynamoDB',
|
|
102
|
+
status: 'warn',
|
|
92
103
|
message: err instanceof Error ? err.message : String(err),
|
|
93
104
|
detail: 'Check IAM: dynamodb:ListTables, dynamodb:DescribeTable',
|
|
94
105
|
};
|
|
@@ -104,7 +115,8 @@ export async function runDoctor(options = {}) {
|
|
|
104
115
|
}
|
|
105
116
|
catch (err) {
|
|
106
117
|
return {
|
|
107
|
-
name: 'SQS',
|
|
118
|
+
name: 'SQS',
|
|
119
|
+
status: 'warn',
|
|
108
120
|
message: err instanceof Error ? err.message : String(err),
|
|
109
121
|
detail: 'Check IAM: sqs:ListQueues, sqs:GetQueueAttributes',
|
|
110
122
|
};
|
|
@@ -120,7 +132,8 @@ export async function runDoctor(options = {}) {
|
|
|
120
132
|
}
|
|
121
133
|
catch (err) {
|
|
122
134
|
return {
|
|
123
|
-
name: 'SNS',
|
|
135
|
+
name: 'SNS',
|
|
136
|
+
status: 'warn',
|
|
124
137
|
message: err instanceof Error ? err.message : String(err),
|
|
125
138
|
detail: 'Check IAM: sns:ListTopics, sns:GetTopicAttributes, sns:ListSubscriptionsByTopic',
|
|
126
139
|
};
|
|
@@ -136,7 +149,8 @@ export async function runDoctor(options = {}) {
|
|
|
136
149
|
}
|
|
137
150
|
catch (err) {
|
|
138
151
|
return {
|
|
139
|
-
name: 'SSM',
|
|
152
|
+
name: 'SSM',
|
|
153
|
+
status: 'warn',
|
|
140
154
|
message: err instanceof Error ? err.message : String(err),
|
|
141
155
|
detail: 'Check IAM: ssm:DescribeParameters',
|
|
142
156
|
};
|
|
@@ -148,11 +162,16 @@ export async function runDoctor(options = {}) {
|
|
|
148
162
|
return { name: 'Secrets Manager', status: 'skip', message: 'Disabled in config' };
|
|
149
163
|
try {
|
|
150
164
|
await validateSecretsAccess(awsCfg);
|
|
151
|
-
return {
|
|
165
|
+
return {
|
|
166
|
+
name: 'Secrets Manager',
|
|
167
|
+
status: 'pass',
|
|
168
|
+
message: 'Connected (names/rotation only)',
|
|
169
|
+
};
|
|
152
170
|
}
|
|
153
171
|
catch (err) {
|
|
154
172
|
return {
|
|
155
|
-
name: 'Secrets Manager',
|
|
173
|
+
name: 'Secrets Manager',
|
|
174
|
+
status: 'warn',
|
|
156
175
|
message: err instanceof Error ? err.message : String(err),
|
|
157
176
|
detail: 'Check IAM: secretsmanager:ListSecrets',
|
|
158
177
|
};
|
|
@@ -168,7 +187,8 @@ export async function runDoctor(options = {}) {
|
|
|
168
187
|
}
|
|
169
188
|
catch (err) {
|
|
170
189
|
return {
|
|
171
|
-
name: 'Lambda',
|
|
190
|
+
name: 'Lambda',
|
|
191
|
+
status: 'warn',
|
|
172
192
|
message: err instanceof Error ? err.message : String(err),
|
|
173
193
|
detail: 'Check IAM: lambda:ListFunctions',
|
|
174
194
|
};
|
|
@@ -184,7 +204,8 @@ export async function runDoctor(options = {}) {
|
|
|
184
204
|
}
|
|
185
205
|
catch (err) {
|
|
186
206
|
return {
|
|
187
|
-
name: 'EventBridge',
|
|
207
|
+
name: 'EventBridge',
|
|
208
|
+
status: 'warn',
|
|
188
209
|
message: err instanceof Error ? err.message : String(err),
|
|
189
210
|
detail: 'Check IAM: events:ListRules, events:ListTargetsByRule',
|
|
190
211
|
};
|
|
@@ -200,7 +221,8 @@ export async function runDoctor(options = {}) {
|
|
|
200
221
|
}
|
|
201
222
|
catch (err) {
|
|
202
223
|
return {
|
|
203
|
-
name: 'S3',
|
|
224
|
+
name: 'S3',
|
|
225
|
+
status: 'warn',
|
|
204
226
|
message: err instanceof Error ? err.message : String(err),
|
|
205
227
|
detail: 'Check IAM: s3:ListBuckets, s3:GetBucketNotificationConfiguration, s3:GetBucketVersioning, s3:GetBucketEncryption, s3:GetPublicAccessBlock',
|
|
206
228
|
};
|
|
@@ -216,7 +238,8 @@ export async function runDoctor(options = {}) {
|
|
|
216
238
|
}
|
|
217
239
|
catch (err) {
|
|
218
240
|
return {
|
|
219
|
-
name: 'CloudWatch Logs',
|
|
241
|
+
name: 'CloudWatch Logs',
|
|
242
|
+
status: 'warn',
|
|
220
243
|
message: err instanceof Error ? err.message : String(err),
|
|
221
244
|
detail: 'Check IAM: logs:DescribeLogGroups, logs:FilterLogEvents',
|
|
222
245
|
};
|
|
@@ -230,13 +253,18 @@ export async function runDoctor(options = {}) {
|
|
|
230
253
|
try {
|
|
231
254
|
const ok = await validatePostgresAccess(config.postgres.connectionString);
|
|
232
255
|
return {
|
|
233
|
-
name: 'PostgreSQL',
|
|
256
|
+
name: 'PostgreSQL',
|
|
257
|
+
status: ok ? 'pass' : 'fail',
|
|
234
258
|
message: ok ? 'Connected' : 'Cannot connect',
|
|
235
259
|
detail: ok ? undefined : 'Check connection string and security group',
|
|
236
260
|
};
|
|
237
261
|
}
|
|
238
262
|
catch (err) {
|
|
239
|
-
return {
|
|
263
|
+
return {
|
|
264
|
+
name: 'PostgreSQL',
|
|
265
|
+
status: 'fail',
|
|
266
|
+
message: err instanceof Error ? err.message : String(err),
|
|
267
|
+
};
|
|
240
268
|
}
|
|
241
269
|
}));
|
|
242
270
|
// MySQL
|
|
@@ -247,13 +275,18 @@ export async function runDoctor(options = {}) {
|
|
|
247
275
|
try {
|
|
248
276
|
const ok = await validateMySQLAccess(config.mysql.connectionString);
|
|
249
277
|
return {
|
|
250
|
-
name: 'MySQL',
|
|
278
|
+
name: 'MySQL',
|
|
279
|
+
status: ok ? 'pass' : 'fail',
|
|
251
280
|
message: ok ? 'Connected' : 'Cannot connect',
|
|
252
281
|
detail: ok ? undefined : 'Check connection string, host, port 3306',
|
|
253
282
|
};
|
|
254
283
|
}
|
|
255
284
|
catch (err) {
|
|
256
|
-
return {
|
|
285
|
+
return {
|
|
286
|
+
name: 'MySQL',
|
|
287
|
+
status: 'fail',
|
|
288
|
+
message: err instanceof Error ? err.message : String(err),
|
|
289
|
+
};
|
|
257
290
|
}
|
|
258
291
|
}));
|
|
259
292
|
// MongoDB
|
|
@@ -264,13 +297,18 @@ export async function runDoctor(options = {}) {
|
|
|
264
297
|
try {
|
|
265
298
|
const ok = await validateMongoAccess(config.mongodb.connectionString);
|
|
266
299
|
return {
|
|
267
|
-
name: 'MongoDB',
|
|
300
|
+
name: 'MongoDB',
|
|
301
|
+
status: ok ? 'pass' : 'fail',
|
|
268
302
|
message: ok ? 'Connected' : 'Cannot connect',
|
|
269
303
|
detail: ok ? undefined : 'Check connection string and port 27017',
|
|
270
304
|
};
|
|
271
305
|
}
|
|
272
306
|
catch (err) {
|
|
273
|
-
return {
|
|
307
|
+
return {
|
|
308
|
+
name: 'MongoDB',
|
|
309
|
+
status: 'fail',
|
|
310
|
+
message: err instanceof Error ? err.message : String(err),
|
|
311
|
+
};
|
|
274
312
|
}
|
|
275
313
|
}));
|
|
276
314
|
// Project type
|
|
@@ -288,11 +326,13 @@ export async function runDoctor(options = {}) {
|
|
|
288
326
|
results.push(await runCheck('Detecting IaC files...', async () => {
|
|
289
327
|
const cwd = process.cwd();
|
|
290
328
|
const hasTfFile = (dir) => fs.existsSync(dir) && fs.readdirSync(dir).some((f) => f.endsWith('.tf'));
|
|
291
|
-
const hasTF = hasTfFile(cwd) ||
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
329
|
+
const hasTF = hasTfFile(cwd) ||
|
|
330
|
+
fs.readdirSync(cwd).some((entry) => {
|
|
331
|
+
const full = path.join(cwd, entry);
|
|
332
|
+
return fs.statSync(full).isDirectory() && hasTfFile(full);
|
|
333
|
+
});
|
|
334
|
+
const hasCFN = fs.existsSync(path.join(cwd, 'template.yaml')) ||
|
|
335
|
+
fs.existsSync(path.join(cwd, 'template.json'));
|
|
296
336
|
const hasCDK = fs.existsSync(path.join(cwd, 'cdk.json'));
|
|
297
337
|
const hasCDKOut = fs.existsSync(path.join(cwd, 'cdk.out'));
|
|
298
338
|
const found = [];
|