infrawise 0.4.2 → 0.6.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.
- package/README.md +21 -48
- package/dist/adapters/aws.js +50 -64
- package/dist/adapters/dynamodb.js +17 -22
- package/dist/adapters/logs.js +12 -16
- package/dist/adapters/mongodb.js +10 -16
- package/dist/adapters/mysql.js +8 -17
- package/dist/adapters/postgres.js +9 -13
- package/dist/adapters/terraform.js +14 -56
- package/dist/analyzers/aws-services.js +7 -17
- package/dist/analyzers/dynamodb.js +6 -12
- package/dist/analyzers/index.js +13 -41
- package/dist/analyzers/mongodb.js +4 -9
- package/dist/analyzers/mysql.js +4 -9
- package/dist/analyzers/postgres.js +3 -9
- package/dist/analyzers/rds.js +5 -13
- package/dist/analyzers/terraform.js +1 -5
- package/dist/cli/commands/analyze.js +178 -215
- package/dist/cli/commands/auth.js +24 -30
- package/dist/cli/commands/dev.js +84 -43
- package/dist/cli/commands/doctor.js +35 -74
- package/dist/cli/commands/init.js +33 -72
- package/dist/cli/index.js +21 -28
- package/dist/cli/utils.js +40 -86
- package/dist/context/index.js +49 -85
- package/dist/core/cache.js +5 -43
- package/dist/core/config.js +43 -81
- package/dist/core/errors.js +7 -17
- package/dist/core/index.js +4 -22
- package/dist/core/logger.js +4 -10
- package/dist/graph/index.js +15 -32
- package/dist/server/index.js +236 -302
- package/dist/types.js +1 -2
- package/package.json +34 -31
|
@@ -1,124 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.runAnalyze = runAnalyze;
|
|
40
|
-
const fs = __importStar(require("fs"));
|
|
41
|
-
const path = __importStar(require("path"));
|
|
42
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
-
const ora_1 = __importDefault(require("ora"));
|
|
44
|
-
const core_1 = require("../../core");
|
|
45
|
-
const dynamodb_1 = require("../../adapters/dynamodb");
|
|
46
|
-
const postgres_1 = require("../../adapters/postgres");
|
|
47
|
-
const mysql_1 = require("../../adapters/mysql");
|
|
48
|
-
const mongodb_1 = require("../../adapters/mongodb");
|
|
49
|
-
const terraform_1 = require("../../adapters/terraform");
|
|
50
|
-
const aws_1 = require("../../adapters/aws");
|
|
51
|
-
const logs_1 = require("../../adapters/logs");
|
|
52
|
-
const context_1 = require("../../context");
|
|
53
|
-
const graph_1 = require("../../graph");
|
|
54
|
-
const analyzers_1 = require("../../analyzers");
|
|
55
|
-
const utils_1 = require("../utils");
|
|
56
|
-
const SEVERITY_RANK = { high: 3, medium: 2, low: 1, never: 0 };
|
|
57
|
-
function mkSpinner(text, ci) {
|
|
58
|
-
if (ci) {
|
|
59
|
-
return {
|
|
60
|
-
succeed: (msg) => console.log(` ✓ ${msg}`),
|
|
61
|
-
warn: (msg) => console.log(` ⚠ ${msg}`),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
return (0, ora_1.default)({ text: chalk_1.default.dim(text), color: 'cyan' }).start();
|
|
65
|
-
}
|
|
66
|
-
function emitCIOutput(findings, failOn) {
|
|
67
|
-
for (const f of findings) {
|
|
68
|
-
const level = f.severity === 'high' ? 'error' : f.severity === 'medium' ? 'warning' : 'notice';
|
|
69
|
-
console.log(`::${level} title=Infrawise [${f.severity.toUpperCase()}]::${f.issue} — ${f.description}`);
|
|
70
|
-
}
|
|
71
|
-
const summaryPath = process.env['GITHUB_STEP_SUMMARY'];
|
|
72
|
-
if (summaryPath) {
|
|
73
|
-
const high = findings.filter((f) => f.severity === 'high').length;
|
|
74
|
-
const medium = findings.filter((f) => f.severity === 'medium').length;
|
|
75
|
-
const low = findings.filter((f) => f.severity === 'low').length;
|
|
76
|
-
const rows = findings.map((f) => {
|
|
77
|
-
const icon = f.severity === 'high' ? '🔴' : f.severity === 'medium' ? '🟡' : '🔵';
|
|
78
|
-
return `| ${icon} ${f.severity.toUpperCase()} | ${f.issue} | ${f.recommendation} |`;
|
|
79
|
-
}).join('\n');
|
|
80
|
-
const summary = findings.length === 0
|
|
81
|
-
? '## ✅ Infrawise — no issues found\n'
|
|
82
|
-
: [
|
|
83
|
-
'## Infrawise Analysis',
|
|
84
|
-
'',
|
|
85
|
-
`**${high} high · ${medium} medium · ${low} low**`,
|
|
86
|
-
'',
|
|
87
|
-
'| Severity | Issue | Recommendation |',
|
|
88
|
-
'|---|---|---|',
|
|
89
|
-
rows,
|
|
90
|
-
].join('\n');
|
|
91
|
-
fs.appendFileSync(summaryPath, summary + '\n');
|
|
92
|
-
}
|
|
93
|
-
const threshold = SEVERITY_RANK[failOn] ?? 3;
|
|
94
|
-
const maxFound = Math.max(0, ...findings.map((f) => SEVERITY_RANK[f.severity] ?? 0));
|
|
95
|
-
if (maxFound >= threshold && threshold > 0) {
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { loadConfig, formatError, writeCache, readCache } from '../../core/index.js';
|
|
5
|
+
import { extractDynamoMetadata } from '../../adapters/dynamodb.js';
|
|
6
|
+
import { extractPostgresMetadata } from '../../adapters/postgres.js';
|
|
7
|
+
import { extractMySQLMetadata } from '../../adapters/mysql.js';
|
|
8
|
+
import { extractMongoMetadata } from '../../adapters/mongodb.js';
|
|
9
|
+
import { extractIaCSchema } from '../../adapters/terraform.js';
|
|
10
|
+
import { extractSQSMetadata, extractSNSMetadata, extractSSMMetadata, extractSecretsMetadata, extractLambdaMetadata, extractRDSMetadata, } from '../../adapters/aws.js';
|
|
11
|
+
import { extractLogsMetadata } from '../../adapters/logs.js';
|
|
12
|
+
import { scanRepository } from '../../context/index.js';
|
|
13
|
+
import { buildGraph } from '../../graph/index.js';
|
|
14
|
+
import { runAllAnalyzers, IaCDriftAnalyzer, FullTableScanAnalyzer, MissingGSIAnalyzer, HotPartitionAnalyzer, MissingIndexAnalyzer, NplusOneAnalyzer, LargeSelectAnalyzer, MissingMySQLIndexAnalyzer, MySQLFullTableScanAnalyzer, MissingMongoIndexAnalyzer, MongoCollectionScanAnalyzer, MissingDLQAnalyzer, UnencryptedQueueAnalyzer, LargeQueueBacklogAnalyzer, MissingSecretRotationAnalyzer, MissingLogRetentionAnalyzer, LambdaDefaultMemoryAnalyzer, LambdaHighTimeoutAnalyzer, RDSPubliclyAccessibleAnalyzer, RDSNoBackupAnalyzer, RDSUnencryptedAnalyzer, RDSNoDeletionProtectionAnalyzer, RDSNoMultiAZAnalyzer, } from '../../analyzers/index.js';
|
|
15
|
+
import { printFinding, printSummaryBox, log, printHeader } from '../utils.js';
|
|
16
|
+
function mkSpinner(text) {
|
|
17
|
+
return ora({ text: chalk.dim(text), color: 'cyan' }).start();
|
|
98
18
|
}
|
|
99
|
-
async function runAnalyze(options = {}) {
|
|
100
|
-
|
|
101
|
-
const failOn = options.failOn ?? 'high';
|
|
102
|
-
if (!ci)
|
|
103
|
-
(0, utils_1.printHeader)('Running Analysis');
|
|
19
|
+
export async function runAnalyze(options = {}) {
|
|
20
|
+
printHeader('Running Analysis');
|
|
104
21
|
let config;
|
|
105
|
-
let isFallback = false;
|
|
106
22
|
try {
|
|
107
|
-
config =
|
|
108
|
-
|
|
109
|
-
utils_1.log.success('Config loaded', options.config ?? 'infrawise.yaml');
|
|
23
|
+
config = loadConfig(options.config);
|
|
24
|
+
log.success('Config loaded', options.config ?? 'infrawise.yaml');
|
|
110
25
|
}
|
|
111
26
|
catch (err) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
config = { project: path.basename(process.cwd()) };
|
|
115
|
-
isFallback = true;
|
|
116
|
-
console.log(' ✓ No infrawise.yaml — running code-only analysis (repo scan + IaC)');
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
console.error((0, core_1.formatError)(err));
|
|
120
|
-
process.exit(1);
|
|
121
|
-
}
|
|
27
|
+
console.error(formatError(err));
|
|
28
|
+
process.exit(1);
|
|
122
29
|
}
|
|
123
30
|
const repoPath = options.repo ?? process.cwd();
|
|
124
31
|
const awsCfg = { region: config.aws?.region, profile: config.aws?.profile, endpoint: config.aws?.endpoint };
|
|
@@ -129,252 +36,308 @@ async function runAnalyze(options = {}) {
|
|
|
129
36
|
const servicesMeta = {};
|
|
130
37
|
// ── DynamoDB ────────────────────────────────────────────────────────────────
|
|
131
38
|
if (config.dynamodb?.enabled === true) {
|
|
132
|
-
const s = mkSpinner('Extracting DynamoDB tables...'
|
|
39
|
+
const s = mkSpinner('Extracting DynamoDB tables...');
|
|
133
40
|
try {
|
|
134
|
-
const result = await
|
|
41
|
+
const result = await extractDynamoMetadata(config);
|
|
135
42
|
dynamoMeta.push(...result);
|
|
136
|
-
s.succeed(
|
|
43
|
+
s.succeed(chalk.green('DynamoDB') + chalk.dim(` ${result.length} table(s)`));
|
|
137
44
|
}
|
|
138
45
|
catch (err) {
|
|
139
|
-
s.warn(
|
|
46
|
+
s.warn(chalk.yellow('DynamoDB skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
140
47
|
}
|
|
141
48
|
}
|
|
142
49
|
// ── PostgreSQL ──────────────────────────────────────────────────────────────
|
|
143
50
|
if (config.postgres?.enabled && config.postgres.connectionString) {
|
|
144
|
-
const s = mkSpinner('Extracting PostgreSQL schema...'
|
|
51
|
+
const s = mkSpinner('Extracting PostgreSQL schema...');
|
|
145
52
|
try {
|
|
146
|
-
const result = await
|
|
53
|
+
const result = await extractPostgresMetadata(config.postgres.connectionString);
|
|
147
54
|
postgresMeta.push(...result);
|
|
148
|
-
s.succeed(
|
|
55
|
+
s.succeed(chalk.green('PostgreSQL') + chalk.dim(` ${result.length} table(s)`));
|
|
149
56
|
}
|
|
150
57
|
catch (err) {
|
|
151
|
-
s.warn(
|
|
58
|
+
s.warn(chalk.yellow('PostgreSQL skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
152
59
|
}
|
|
153
60
|
}
|
|
154
61
|
// ── MySQL ───────────────────────────────────────────────────────────────────
|
|
155
62
|
if (config.mysql?.enabled && config.mysql.connectionString) {
|
|
156
|
-
const s = mkSpinner('Extracting MySQL schema...'
|
|
63
|
+
const s = mkSpinner('Extracting MySQL schema...');
|
|
157
64
|
try {
|
|
158
|
-
const result = await
|
|
65
|
+
const result = await extractMySQLMetadata(config.mysql.connectionString);
|
|
159
66
|
mysqlMeta.push(...result);
|
|
160
|
-
s.succeed(
|
|
67
|
+
s.succeed(chalk.green('MySQL') + chalk.dim(` ${result.length} table(s)`));
|
|
161
68
|
}
|
|
162
69
|
catch (err) {
|
|
163
|
-
s.warn(
|
|
70
|
+
s.warn(chalk.yellow('MySQL skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
164
71
|
}
|
|
165
72
|
}
|
|
166
73
|
// ── MongoDB ─────────────────────────────────────────────────────────────────
|
|
167
74
|
if (config.mongodb?.enabled && config.mongodb.connectionString) {
|
|
168
|
-
const s = mkSpinner('Extracting MongoDB schema...'
|
|
75
|
+
const s = mkSpinner('Extracting MongoDB schema...');
|
|
169
76
|
try {
|
|
170
|
-
const result = await
|
|
77
|
+
const result = await extractMongoMetadata(config.mongodb.connectionString, config.mongodb.databases);
|
|
171
78
|
mongoMeta.push(...result);
|
|
172
|
-
s.succeed(
|
|
79
|
+
s.succeed(chalk.green('MongoDB') + chalk.dim(` ${result.length} collection(s)`));
|
|
173
80
|
}
|
|
174
81
|
catch (err) {
|
|
175
|
-
s.warn(
|
|
82
|
+
s.warn(chalk.yellow('MongoDB skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
176
83
|
}
|
|
177
84
|
}
|
|
178
85
|
// ── SQS ─────────────────────────────────────────────────────────────────────
|
|
179
86
|
if (config.sqs?.enabled === true) {
|
|
180
|
-
const s = mkSpinner('Extracting SQS queues...'
|
|
87
|
+
const s = mkSpinner('Extracting SQS queues...');
|
|
181
88
|
try {
|
|
182
|
-
const result = await
|
|
89
|
+
const result = await extractSQSMetadata(awsCfg);
|
|
183
90
|
servicesMeta.sqs = result;
|
|
184
|
-
s.succeed(
|
|
91
|
+
s.succeed(chalk.green('SQS') + chalk.dim(` ${result.length} queue(s)`));
|
|
185
92
|
}
|
|
186
93
|
catch (err) {
|
|
187
|
-
s.warn(
|
|
94
|
+
s.warn(chalk.yellow('SQS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
188
95
|
}
|
|
189
96
|
}
|
|
190
97
|
// ── SNS ─────────────────────────────────────────────────────────────────────
|
|
191
98
|
if (config.sns?.enabled === true) {
|
|
192
|
-
const s = mkSpinner('Extracting SNS topics...'
|
|
99
|
+
const s = mkSpinner('Extracting SNS topics...');
|
|
193
100
|
try {
|
|
194
|
-
const result = await
|
|
101
|
+
const result = await extractSNSMetadata(awsCfg);
|
|
195
102
|
servicesMeta.sns = result;
|
|
196
|
-
s.succeed(
|
|
103
|
+
s.succeed(chalk.green('SNS') + chalk.dim(` ${result.length} topic(s)`));
|
|
197
104
|
}
|
|
198
105
|
catch (err) {
|
|
199
|
-
s.warn(
|
|
106
|
+
s.warn(chalk.yellow('SNS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
200
107
|
}
|
|
201
108
|
}
|
|
202
109
|
// ── SSM Parameter Store ──────────────────────────────────────────────────────
|
|
203
110
|
if (config.ssm?.enabled === true) {
|
|
204
|
-
const s = mkSpinner('Extracting SSM parameters...'
|
|
111
|
+
const s = mkSpinner('Extracting SSM parameters...');
|
|
205
112
|
try {
|
|
206
|
-
const result = await
|
|
113
|
+
const result = await extractSSMMetadata({ ...awsCfg, paths: config.ssm?.paths });
|
|
207
114
|
servicesMeta.ssm = result;
|
|
208
|
-
s.succeed(
|
|
115
|
+
s.succeed(chalk.green('SSM') + chalk.dim(` ${result.length} parameter(s) `) + chalk.dim('(metadata only, no values)'));
|
|
209
116
|
}
|
|
210
117
|
catch (err) {
|
|
211
|
-
s.warn(
|
|
118
|
+
s.warn(chalk.yellow('SSM skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
212
119
|
}
|
|
213
120
|
}
|
|
214
121
|
// ── Secrets Manager ──────────────────────────────────────────────────────────
|
|
215
122
|
if (config.secretsManager?.enabled === true) {
|
|
216
|
-
const s = mkSpinner('Extracting Secrets Manager metadata...'
|
|
123
|
+
const s = mkSpinner('Extracting Secrets Manager metadata...');
|
|
217
124
|
try {
|
|
218
|
-
const result = await
|
|
125
|
+
const result = await extractSecretsMetadata(awsCfg);
|
|
219
126
|
servicesMeta.secrets = result;
|
|
220
|
-
s.succeed(
|
|
127
|
+
s.succeed(chalk.green('Secrets Manager') + chalk.dim(` ${result.length} secret(s) `) + chalk.dim('(names/rotation only, no values)'));
|
|
221
128
|
}
|
|
222
129
|
catch (err) {
|
|
223
|
-
s.warn(
|
|
130
|
+
s.warn(chalk.yellow('Secrets Manager skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
224
131
|
}
|
|
225
132
|
}
|
|
226
133
|
// ── Lambda ───────────────────────────────────────────────────────────────────
|
|
227
134
|
if (config.lambda?.enabled === true) {
|
|
228
|
-
const s = mkSpinner('Extracting Lambda functions...'
|
|
135
|
+
const s = mkSpinner('Extracting Lambda functions...');
|
|
229
136
|
try {
|
|
230
|
-
const result = await
|
|
137
|
+
const result = await extractLambdaMetadata(awsCfg);
|
|
231
138
|
servicesMeta.lambda = result;
|
|
232
|
-
s.succeed(
|
|
139
|
+
s.succeed(chalk.green('Lambda') + chalk.dim(` ${result.length} function(s)`));
|
|
233
140
|
}
|
|
234
141
|
catch (err) {
|
|
235
|
-
s.warn(
|
|
142
|
+
s.warn(chalk.yellow('Lambda skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
236
143
|
}
|
|
237
144
|
}
|
|
238
145
|
// ── RDS ──────────────────────────────────────────────────────────────────────
|
|
239
146
|
if (config.rds?.enabled === true) {
|
|
240
|
-
const s = mkSpinner('Extracting RDS instances...'
|
|
147
|
+
const s = mkSpinner('Extracting RDS instances...');
|
|
241
148
|
try {
|
|
242
|
-
const result = await
|
|
149
|
+
const result = await extractRDSMetadata(awsCfg);
|
|
243
150
|
servicesMeta.rds = result;
|
|
244
|
-
s.succeed(
|
|
151
|
+
s.succeed(chalk.green('RDS') + chalk.dim(` ${result.length} instance(s)`));
|
|
245
152
|
}
|
|
246
153
|
catch (err) {
|
|
247
|
-
s.warn(
|
|
154
|
+
s.warn(chalk.yellow('RDS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
248
155
|
}
|
|
249
156
|
}
|
|
250
157
|
// ── CloudWatch Logs ──────────────────────────────────────────────────────────
|
|
251
158
|
if (config.cloudwatchLogs?.enabled) {
|
|
252
|
-
const s = mkSpinner('Sampling CloudWatch Logs (errors only, max 50 groups)...'
|
|
159
|
+
const s = mkSpinner('Sampling CloudWatch Logs (errors only, max 50 groups)...');
|
|
253
160
|
try {
|
|
254
|
-
const result = await
|
|
161
|
+
const result = await extractLogsMetadata({
|
|
255
162
|
...awsCfg,
|
|
256
163
|
logGroupPrefixes: config.cloudwatchLogs.logGroupPrefixes,
|
|
257
164
|
windowHours: config.cloudwatchLogs.windowHours,
|
|
258
165
|
});
|
|
259
166
|
servicesMeta.logs = result;
|
|
260
167
|
const errorGroups = result.filter((lg) => lg.errorCount > 0).length;
|
|
261
|
-
s.succeed(
|
|
168
|
+
s.succeed(chalk.green('CloudWatch Logs') + chalk.dim(` ${result.length} group(s), ${errorGroups} with errors`));
|
|
262
169
|
}
|
|
263
170
|
catch (err) {
|
|
264
|
-
s.warn(
|
|
171
|
+
s.warn(chalk.yellow('CloudWatch Logs skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
265
172
|
}
|
|
266
173
|
}
|
|
267
174
|
// ── IaC schema (Terraform / CloudFormation / CDK) ────────────────────────────
|
|
268
175
|
let iacDriftAnalyzer;
|
|
269
176
|
{
|
|
270
|
-
const s = mkSpinner('Extracting IaC schema (Terraform / CloudFormation / CDK)...'
|
|
177
|
+
const s = mkSpinner('Extracting IaC schema (Terraform / CloudFormation / CDK)...');
|
|
271
178
|
try {
|
|
272
|
-
const iacSchema = await
|
|
179
|
+
const iacSchema = await extractIaCSchema(repoPath);
|
|
273
180
|
const total = iacSchema.dynamoTables.length + iacSchema.rdsInstances.length +
|
|
274
181
|
iacSchema.mongoClusters.length + iacSchema.queues.length + iacSchema.topics.length +
|
|
275
182
|
iacSchema.lambdas.length + iacSchema.buckets.length + iacSchema.parameters.length +
|
|
276
183
|
iacSchema.secrets.length + iacSchema.apiGateways.length;
|
|
277
|
-
iacDriftAnalyzer = new
|
|
184
|
+
iacDriftAnalyzer = new IaCDriftAnalyzer();
|
|
278
185
|
iacDriftAnalyzer.setIaCSchema(iacSchema);
|
|
279
|
-
s.succeed(
|
|
186
|
+
s.succeed(chalk.green('IaC schema') + chalk.dim(` ${total} resource(s) across TF/CFN/CDK`));
|
|
280
187
|
}
|
|
281
188
|
catch (err) {
|
|
282
|
-
s.warn(
|
|
189
|
+
s.warn(chalk.yellow('IaC scan skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
283
190
|
}
|
|
284
191
|
}
|
|
285
192
|
// ── Repository scan ──────────────────────────────────────────────────────────
|
|
286
193
|
let operations;
|
|
287
194
|
{
|
|
288
|
-
const s = mkSpinner(`Scanning ${path.basename(repoPath)} for service usage
|
|
195
|
+
const s = mkSpinner(`Scanning ${path.basename(repoPath)} for service usage...`);
|
|
289
196
|
try {
|
|
290
|
-
operations = await
|
|
291
|
-
s.succeed(
|
|
197
|
+
operations = await scanRepository(repoPath);
|
|
198
|
+
s.succeed(chalk.green('Repository scanned') + chalk.dim(` ${operations.length} service operation(s) found`));
|
|
292
199
|
}
|
|
293
200
|
catch (err) {
|
|
294
|
-
s.warn(
|
|
201
|
+
s.warn(chalk.yellow('Repository scan failed') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
295
202
|
operations = [];
|
|
296
203
|
}
|
|
297
204
|
}
|
|
298
205
|
// ── Build graph ──────────────────────────────────────────────────────────────
|
|
299
206
|
let graph;
|
|
300
207
|
{
|
|
301
|
-
const s = mkSpinner('Building infrastructure graph...'
|
|
302
|
-
graph =
|
|
303
|
-
s.succeed(
|
|
208
|
+
const s = mkSpinner('Building infrastructure graph...');
|
|
209
|
+
graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
|
|
210
|
+
s.succeed(chalk.green('Graph built') + chalk.dim(` ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
|
|
304
211
|
}
|
|
305
212
|
// ── Run analyzers ────────────────────────────────────────────────────────────
|
|
306
213
|
let findings;
|
|
307
214
|
{
|
|
308
|
-
const s = mkSpinner('Running analyzers...'
|
|
215
|
+
const s = mkSpinner('Running analyzers...');
|
|
309
216
|
const analyzers = [
|
|
310
|
-
...(config.dynamodb?.enabled === true
|
|
311
|
-
new
|
|
312
|
-
new
|
|
313
|
-
new
|
|
217
|
+
...(config.dynamodb?.enabled === true ? [
|
|
218
|
+
new FullTableScanAnalyzer(),
|
|
219
|
+
new MissingGSIAnalyzer(),
|
|
220
|
+
new HotPartitionAnalyzer(),
|
|
314
221
|
] : []),
|
|
315
|
-
...(config.postgres?.enabled
|
|
316
|
-
new
|
|
317
|
-
new
|
|
318
|
-
new
|
|
222
|
+
...(config.postgres?.enabled ? [
|
|
223
|
+
new MissingIndexAnalyzer(),
|
|
224
|
+
new NplusOneAnalyzer(),
|
|
225
|
+
new LargeSelectAnalyzer(),
|
|
319
226
|
] : []),
|
|
320
|
-
...(config.mysql?.enabled
|
|
321
|
-
new
|
|
322
|
-
new
|
|
227
|
+
...(config.mysql?.enabled ? [
|
|
228
|
+
new MissingMySQLIndexAnalyzer(),
|
|
229
|
+
new MySQLFullTableScanAnalyzer(),
|
|
323
230
|
] : []),
|
|
324
|
-
...(config.mongodb?.enabled
|
|
325
|
-
new
|
|
326
|
-
new
|
|
231
|
+
...(config.mongodb?.enabled ? [
|
|
232
|
+
new MissingMongoIndexAnalyzer(),
|
|
233
|
+
new MongoCollectionScanAnalyzer(),
|
|
327
234
|
] : []),
|
|
328
235
|
...(config.sqs?.enabled === true ? [
|
|
329
|
-
new
|
|
330
|
-
new
|
|
331
|
-
new
|
|
236
|
+
new MissingDLQAnalyzer(),
|
|
237
|
+
new UnencryptedQueueAnalyzer(),
|
|
238
|
+
new LargeQueueBacklogAnalyzer(),
|
|
332
239
|
] : []),
|
|
333
240
|
...(config.secretsManager?.enabled === true ? [
|
|
334
|
-
new
|
|
241
|
+
new MissingSecretRotationAnalyzer(),
|
|
335
242
|
] : []),
|
|
336
243
|
...(config.cloudwatchLogs?.enabled ? [
|
|
337
|
-
new
|
|
244
|
+
new MissingLogRetentionAnalyzer(),
|
|
338
245
|
] : []),
|
|
339
246
|
...(config.lambda?.enabled === true ? [
|
|
340
|
-
new
|
|
341
|
-
new
|
|
247
|
+
new LambdaDefaultMemoryAnalyzer(),
|
|
248
|
+
new LambdaHighTimeoutAnalyzer(),
|
|
342
249
|
] : []),
|
|
343
250
|
...(config.rds?.enabled === true ? [
|
|
344
|
-
new
|
|
345
|
-
new
|
|
346
|
-
new
|
|
347
|
-
new
|
|
348
|
-
new
|
|
251
|
+
new RDSPubliclyAccessibleAnalyzer(),
|
|
252
|
+
new RDSNoBackupAnalyzer(),
|
|
253
|
+
new RDSUnencryptedAnalyzer(),
|
|
254
|
+
new RDSNoDeletionProtectionAnalyzer(),
|
|
255
|
+
new RDSNoMultiAZAnalyzer(),
|
|
349
256
|
] : []),
|
|
350
257
|
...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
|
|
351
258
|
];
|
|
352
|
-
findings = await
|
|
353
|
-
s.succeed(
|
|
259
|
+
findings = await runAllAnalyzers(graph, analyzers);
|
|
260
|
+
s.succeed(chalk.green('Analysis complete') + chalk.dim(` ${findings.length} finding(s)`));
|
|
354
261
|
}
|
|
355
262
|
// ── Cache ─────────────────────────────────────────────────────────────────────
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
263
|
+
writeCache('graph', graph);
|
|
264
|
+
writeCache('findings', findings);
|
|
265
|
+
writeCache('operations', operations);
|
|
266
|
+
writeCache('meta', { dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta });
|
|
359
267
|
// ── Output ────────────────────────────────────────────────────────────────────
|
|
360
|
-
if (ci) {
|
|
361
|
-
emitCIOutput(findings, failOn);
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
268
|
console.log('');
|
|
365
269
|
if (findings.length === 0) {
|
|
366
|
-
console.log(` ${
|
|
270
|
+
console.log(` ${chalk.green.bold('✓ No issues found!')} ${chalk.dim('Your infrastructure looks clean.')}`);
|
|
367
271
|
}
|
|
368
272
|
else {
|
|
369
|
-
console.log(
|
|
370
|
-
findings.forEach((f, i) =>
|
|
371
|
-
|
|
273
|
+
console.log(chalk.bold(` Findings`) + chalk.dim(` ${findings.length} total`));
|
|
274
|
+
findings.forEach((f, i) => printFinding(f, i));
|
|
275
|
+
printSummaryBox(findings);
|
|
372
276
|
if (findings.some((f) => f.severity === 'high')) {
|
|
373
|
-
console.log(`\n ${
|
|
277
|
+
console.log(`\n ${chalk.red.bold('Action required:')} ${chalk.red('High severity issues detected.')}`);
|
|
374
278
|
}
|
|
375
279
|
}
|
|
376
280
|
console.log('');
|
|
377
|
-
|
|
378
|
-
|
|
281
|
+
log.dim(`Results cached in .infrawise/cache/`);
|
|
282
|
+
log.info(`Run ${chalk.cyan('infrawise dev')} to explore via the MCP server`);
|
|
379
283
|
console.log('');
|
|
380
284
|
}
|
|
285
|
+
export async function runCodeRefresh(repoPath, config) {
|
|
286
|
+
const cached = readCache('meta', Infinity);
|
|
287
|
+
const dynamoMeta = cached?.dynamoMeta ?? [];
|
|
288
|
+
const postgresMeta = cached?.postgresMeta ?? [];
|
|
289
|
+
const mysqlMeta = cached?.mysqlMeta ?? [];
|
|
290
|
+
const mongoMeta = cached?.mongoMeta ?? [];
|
|
291
|
+
const servicesMeta = cached?.servicesMeta ?? {};
|
|
292
|
+
// Re-run IaC schema (pure file scan, no AWS calls)
|
|
293
|
+
let iacDriftAnalyzer;
|
|
294
|
+
try {
|
|
295
|
+
const iacSchema = await extractIaCSchema(repoPath);
|
|
296
|
+
iacDriftAnalyzer = new IaCDriftAnalyzer();
|
|
297
|
+
iacDriftAnalyzer.setIaCSchema(iacSchema);
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// IaC scan is best-effort
|
|
301
|
+
}
|
|
302
|
+
// Re-run repo scan
|
|
303
|
+
let operations;
|
|
304
|
+
try {
|
|
305
|
+
operations = await scanRepository(repoPath);
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
operations = [];
|
|
309
|
+
}
|
|
310
|
+
const graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
|
|
311
|
+
const analyzers = [
|
|
312
|
+
...(config.dynamodb?.enabled === true ? [
|
|
313
|
+
new FullTableScanAnalyzer(), new MissingGSIAnalyzer(), new HotPartitionAnalyzer(),
|
|
314
|
+
] : []),
|
|
315
|
+
...(config.postgres?.enabled ? [
|
|
316
|
+
new MissingIndexAnalyzer(), new NplusOneAnalyzer(), new LargeSelectAnalyzer(),
|
|
317
|
+
] : []),
|
|
318
|
+
...(config.mysql?.enabled ? [
|
|
319
|
+
new MissingMySQLIndexAnalyzer(), new MySQLFullTableScanAnalyzer(),
|
|
320
|
+
] : []),
|
|
321
|
+
...(config.mongodb?.enabled ? [
|
|
322
|
+
new MissingMongoIndexAnalyzer(), new MongoCollectionScanAnalyzer(),
|
|
323
|
+
] : []),
|
|
324
|
+
...(config.sqs?.enabled === true ? [
|
|
325
|
+
new MissingDLQAnalyzer(), new UnencryptedQueueAnalyzer(), new LargeQueueBacklogAnalyzer(),
|
|
326
|
+
] : []),
|
|
327
|
+
...(config.secretsManager?.enabled === true ? [new MissingSecretRotationAnalyzer()] : []),
|
|
328
|
+
...(config.cloudwatchLogs?.enabled ? [new MissingLogRetentionAnalyzer()] : []),
|
|
329
|
+
...(config.lambda?.enabled === true ? [
|
|
330
|
+
new LambdaDefaultMemoryAnalyzer(), new LambdaHighTimeoutAnalyzer(),
|
|
331
|
+
] : []),
|
|
332
|
+
...(config.rds?.enabled === true ? [
|
|
333
|
+
new RDSPubliclyAccessibleAnalyzer(), new RDSNoBackupAnalyzer(), new RDSUnencryptedAnalyzer(),
|
|
334
|
+
new RDSNoDeletionProtectionAnalyzer(), new RDSNoMultiAZAnalyzer(),
|
|
335
|
+
] : []),
|
|
336
|
+
...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
|
|
337
|
+
];
|
|
338
|
+
const findings = await runAllAnalyzers(graph, analyzers);
|
|
339
|
+
writeCache('graph', graph);
|
|
340
|
+
writeCache('findings', findings);
|
|
341
|
+
writeCache('operations', operations);
|
|
342
|
+
return { graph, findings };
|
|
343
|
+
}
|