infrawise 0.8.3 → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/aws/s3.js +23 -2
- package/dist/adapters/aws/services.js +16 -2
- package/dist/adapters/db/mongodb.js +4 -1
- package/dist/adapters/db/mysql.js +4 -1
- package/dist/cli/commands/analyze.js +20 -13
- package/dist/cli/commands/dev.js +2 -1
- package/dist/core/cache.js +12 -9
- package/dist/core/index.js +1 -1
- package/package.json +1 -1
package/dist/adapters/aws/s3.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { S3Client, ListBucketsCommand, GetBucketNotificationConfigurationCommand, GetBucketVersioningCommand, GetBucketEncryptionCommand, GetPublicAccessBlockCommand, } from '@aws-sdk/client-s3';
|
|
2
2
|
import { fromIni } from '@aws-sdk/credential-providers';
|
|
3
3
|
import { logger } from '../../core/index.js';
|
|
4
|
+
function validateEndpoint(endpoint) {
|
|
5
|
+
let url;
|
|
6
|
+
try {
|
|
7
|
+
url = new URL(endpoint);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
throw new Error(`Invalid aws.endpoint URL: "${endpoint}"`);
|
|
11
|
+
}
|
|
12
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
13
|
+
throw new Error(`aws.endpoint must use http:// or https://, got "${url.protocol}//"`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
4
16
|
function clientConfig(cfg) {
|
|
5
17
|
const region = cfg.region ?? 'us-east-1';
|
|
6
18
|
const base = { region };
|
|
7
|
-
if (cfg.endpoint)
|
|
19
|
+
if (cfg.endpoint) {
|
|
20
|
+
validateEndpoint(cfg.endpoint);
|
|
8
21
|
base.endpoint = cfg.endpoint;
|
|
22
|
+
}
|
|
9
23
|
if (cfg.profile)
|
|
10
24
|
base.credentials = fromIni({ profile: cfg.profile });
|
|
11
25
|
return base;
|
|
@@ -52,7 +66,7 @@ export async function extractS3Metadata(cfg = {}) {
|
|
|
52
66
|
const encrypted = encryptResult.status === 'fulfilled'
|
|
53
67
|
? (encryptResult.value.ServerSideEncryptionConfiguration?.Rules?.length ?? 0) > 0
|
|
54
68
|
: false;
|
|
55
|
-
let publicAccessBlocked =
|
|
69
|
+
let publicAccessBlocked = null;
|
|
56
70
|
if (pabResult.status === 'fulfilled') {
|
|
57
71
|
const pab = pabResult.value.PublicAccessBlockConfiguration ?? {};
|
|
58
72
|
publicAccessBlocked = !!(pab.BlockPublicAcls &&
|
|
@@ -60,6 +74,13 @@ export async function extractS3Metadata(cfg = {}) {
|
|
|
60
74
|
pab.BlockPublicPolicy &&
|
|
61
75
|
pab.RestrictPublicBuckets);
|
|
62
76
|
}
|
|
77
|
+
else {
|
|
78
|
+
const httpStatus = pabResult.reason
|
|
79
|
+
?.$metadata?.httpStatusCode;
|
|
80
|
+
if (httpStatus !== 403)
|
|
81
|
+
publicAccessBlocked = false;
|
|
82
|
+
// 403 AccessDenied: leave as null — insufficient permissions, not a public access finding
|
|
83
|
+
}
|
|
63
84
|
buckets.push({
|
|
64
85
|
name,
|
|
65
86
|
arn,
|
|
@@ -7,11 +7,25 @@ import { EventBridgeClient, ListRulesCommand, ListTargetsByRuleCommand, } from '
|
|
|
7
7
|
import { RDSClient, DescribeDBInstancesCommand } from '@aws-sdk/client-rds';
|
|
8
8
|
import { fromIni } from '@aws-sdk/credential-providers';
|
|
9
9
|
import { logger } from '../../core/index.js';
|
|
10
|
+
function validateEndpoint(endpoint) {
|
|
11
|
+
let url;
|
|
12
|
+
try {
|
|
13
|
+
url = new URL(endpoint);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error(`Invalid aws.endpoint URL: "${endpoint}"`);
|
|
17
|
+
}
|
|
18
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
19
|
+
throw new Error(`aws.endpoint must use http:// or https://, got "${url.protocol}//"`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
10
22
|
function clientConfig(cfg) {
|
|
11
23
|
const region = cfg.region ?? 'us-east-1';
|
|
12
24
|
const base = { region };
|
|
13
|
-
if (cfg.endpoint)
|
|
25
|
+
if (cfg.endpoint) {
|
|
26
|
+
validateEndpoint(cfg.endpoint);
|
|
14
27
|
base.endpoint = cfg.endpoint;
|
|
28
|
+
}
|
|
15
29
|
if (cfg.profile)
|
|
16
30
|
base.credentials = fromIni({ profile: cfg.profile });
|
|
17
31
|
return base;
|
|
@@ -298,7 +312,7 @@ export async function extractLambdaMetadata(cfg = {}, includeFunctions) {
|
|
|
298
312
|
});
|
|
299
313
|
}
|
|
300
314
|
marker = res.NextMarker;
|
|
301
|
-
} while (marker
|
|
315
|
+
} while (marker);
|
|
302
316
|
// Fetch all event source mappings in one paginated call and attach to functions
|
|
303
317
|
const triggerMap = await fetchAllEventSourceMappings(cfg);
|
|
304
318
|
for (const fn of functions) {
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { MongoClient } from 'mongodb';
|
|
2
2
|
import { InfrawiseError, logger } from '../../core/index.js';
|
|
3
3
|
const SYSTEM_DATABASES = new Set(['admin', 'local', 'config']);
|
|
4
|
+
function sanitizeConnectionDetail(s) {
|
|
5
|
+
return s.replace(/\/\/[^:/@]+:[^@]+@/g, '//***:***@');
|
|
6
|
+
}
|
|
4
7
|
export class MongoConnectionError extends InfrawiseError {
|
|
5
8
|
constructor(details) {
|
|
6
9
|
super('Unable to connect to MongoDB.\n\nPossible reasons:\n- invalid connection string\n- port 27017 not accessible\n- wrong credentials\n\nRun: infrawise doctor', undefined, undefined);
|
|
7
10
|
this.name = 'MongoConnectionError';
|
|
8
11
|
if (details) {
|
|
9
|
-
this.message = `Unable to connect to MongoDB.\n\nPossible reasons:\n- invalid connection string\n- port 27017 not accessible\n- wrong credentials\n\nRun: infrawise doctor\n\nDetail: ${details}`;
|
|
12
|
+
this.message = `Unable to connect to MongoDB.\n\nPossible reasons:\n- invalid connection string\n- port 27017 not accessible\n- wrong credentials\n\nRun: infrawise doctor\n\nDetail: ${sanitizeConnectionDetail(details)}`;
|
|
10
13
|
}
|
|
11
14
|
}
|
|
12
15
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import mysql from 'mysql2/promise';
|
|
2
2
|
import { InfrawiseError, logger } from '../../core/index.js';
|
|
3
3
|
const SYSTEM_SCHEMAS = new Set(['information_schema', 'performance_schema', 'mysql', 'sys']);
|
|
4
|
+
function sanitizeConnectionDetail(s) {
|
|
5
|
+
return s.replace(/\/\/[^:/@]+:[^@]+@/g, '//***:***@');
|
|
6
|
+
}
|
|
4
7
|
export class MySQLConnectionError extends InfrawiseError {
|
|
5
8
|
constructor(details) {
|
|
6
9
|
super('Unable to connect to MySQL.\n\nPossible reasons:\n- invalid connection string\n- port 3306 not accessible\n- wrong credentials\n\nRun: infrawise doctor', undefined, undefined);
|
|
7
10
|
this.name = 'MySQLConnectionError';
|
|
8
11
|
if (details) {
|
|
9
|
-
this.message = `Unable to connect to MySQL.\n\nPossible reasons:\n- invalid connection string\n- port 3306 not accessible\n- wrong credentials\n\nRun: infrawise doctor\n\nDetail: ${details}`;
|
|
12
|
+
this.message = `Unable to connect to MySQL.\n\nPossible reasons:\n- invalid connection string\n- port 3306 not accessible\n- wrong credentials\n\nRun: infrawise doctor\n\nDetail: ${sanitizeConnectionDetail(details)}`;
|
|
10
13
|
}
|
|
11
14
|
}
|
|
12
15
|
}
|
|
@@ -2,7 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import ora from 'ora';
|
|
5
|
-
import { loadConfig, formatError, writeCache, readCache } from '../../core/index.js';
|
|
5
|
+
import { loadConfig, formatError, writeCache, readCache, setCacheDir } from '../../core/index.js';
|
|
6
6
|
import { extractDynamoMetadata } from '../../adapters/aws/dynamodb.js';
|
|
7
7
|
import { extractPostgresMetadata } from '../../adapters/db/postgres.js';
|
|
8
8
|
import { extractMySQLMetadata } from '../../adapters/db/mysql.js';
|
|
@@ -51,6 +51,7 @@ export async function runAnalyze(options = {}) {
|
|
|
51
51
|
let config;
|
|
52
52
|
try {
|
|
53
53
|
config = loadConfig(options.config);
|
|
54
|
+
setCacheDir(path.dirname(path.resolve(options.config ?? 'infrawise.yaml')));
|
|
54
55
|
log.success('Config loaded', options.config ?? 'infrawise.yaml');
|
|
55
56
|
}
|
|
56
57
|
catch (err) {
|
|
@@ -351,16 +352,18 @@ export async function runAnalyze(options = {}) {
|
|
|
351
352
|
s.succeed(chalk.green('Analysis complete') + chalk.dim(` ${findings.length} finding(s)`));
|
|
352
353
|
}
|
|
353
354
|
// ── Cache ─────────────────────────────────────────────────────────────────────
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
355
|
+
if (!options.noCache) {
|
|
356
|
+
writeCache('graph', graph);
|
|
357
|
+
writeCache('findings', findings);
|
|
358
|
+
writeCache('operations', operations);
|
|
359
|
+
writeCache('meta', {
|
|
360
|
+
dynamoMeta,
|
|
361
|
+
postgresMeta,
|
|
362
|
+
mysqlMeta,
|
|
363
|
+
mongoMeta,
|
|
364
|
+
servicesMeta,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
364
367
|
// ── Output ────────────────────────────────────────────────────────────────────
|
|
365
368
|
const displayFindings = minSeverity > 0
|
|
366
369
|
? findings.filter((f) => (SEVERITY_ORDER[f.severity] ?? 0) >= minSeverity)
|
|
@@ -394,7 +397,7 @@ export async function runAnalyze(options = {}) {
|
|
|
394
397
|
console.log('');
|
|
395
398
|
}
|
|
396
399
|
export async function runCodeRefresh(repoPath, config) {
|
|
397
|
-
const cached = readCache('meta',
|
|
400
|
+
const cached = readCache('meta', 60 * 60 * 1000);
|
|
398
401
|
const dynamoMeta = cached?.dynamoMeta ?? [];
|
|
399
402
|
const postgresMeta = cached?.postgresMeta ?? [];
|
|
400
403
|
const mysqlMeta = cached?.mysqlMeta ?? [];
|
|
@@ -438,7 +441,11 @@ export async function runCodeRefresh(repoPath, config) {
|
|
|
438
441
|
...(config.secretsManager?.enabled === true ? [new MissingSecretRotationAnalyzer()] : []),
|
|
439
442
|
...(config.cloudwatchLogs?.enabled ? [new MissingLogRetentionAnalyzer()] : []),
|
|
440
443
|
...(config.lambda?.enabled === true
|
|
441
|
-
? [
|
|
444
|
+
? [
|
|
445
|
+
new LambdaDefaultMemoryAnalyzer(),
|
|
446
|
+
new LambdaHighTimeoutAnalyzer(),
|
|
447
|
+
new LambdaMissingTriggerDLQAnalyzer(),
|
|
448
|
+
]
|
|
442
449
|
: []),
|
|
443
450
|
...(config.rds?.enabled === true
|
|
444
451
|
? [
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import ora from 'ora';
|
|
5
|
-
import { loadConfig, formatError, readCache } from '../../core/index.js';
|
|
5
|
+
import { loadConfig, formatError, readCache, setCacheDir } from '../../core/index.js';
|
|
6
6
|
import { createServer, setGraphState } from '../../server/index.js';
|
|
7
7
|
import { log, printHeader } from '../utils.js';
|
|
8
8
|
import { runAnalyze, runCodeRefresh } from './analyze.js';
|
|
@@ -59,6 +59,7 @@ export async function runDev(options = {}) {
|
|
|
59
59
|
let config;
|
|
60
60
|
try {
|
|
61
61
|
config = loadConfig(options.config);
|
|
62
|
+
setCacheDir(path.dirname(path.resolve(options.config ?? 'infrawise.yaml')));
|
|
62
63
|
log.success('Config loaded', options.config ?? 'infrawise.yaml');
|
|
63
64
|
}
|
|
64
65
|
catch (err) {
|
package/dist/core/cache.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
const CACHE_VERSION = '1.0.0';
|
|
4
|
-
|
|
4
|
+
let cacheDir = path.join(process.cwd(), '.infrawise', 'cache');
|
|
5
|
+
export function setCacheDir(dir) {
|
|
6
|
+
cacheDir = path.join(dir, '.infrawise', 'cache');
|
|
7
|
+
}
|
|
5
8
|
function ensureCacheDir() {
|
|
6
|
-
if (!fs.existsSync(
|
|
7
|
-
fs.mkdirSync(
|
|
9
|
+
if (!fs.existsSync(cacheDir)) {
|
|
10
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
8
11
|
}
|
|
9
12
|
}
|
|
10
13
|
export function writeCache(key, data) {
|
|
@@ -14,11 +17,11 @@ export function writeCache(key, data) {
|
|
|
14
17
|
data,
|
|
15
18
|
version: CACHE_VERSION,
|
|
16
19
|
};
|
|
17
|
-
const filePath = path.join(
|
|
20
|
+
const filePath = path.join(cacheDir, `${key}.json`);
|
|
18
21
|
fs.writeFileSync(filePath, JSON.stringify(entry, null, 2), 'utf-8');
|
|
19
22
|
}
|
|
20
23
|
export function readCache(key, maxAgeMs = 3600000) {
|
|
21
|
-
const filePath = path.join(
|
|
24
|
+
const filePath = path.join(cacheDir, `${key}.json`);
|
|
22
25
|
if (!fs.existsSync(filePath))
|
|
23
26
|
return null;
|
|
24
27
|
try {
|
|
@@ -36,15 +39,15 @@ export function readCache(key, maxAgeMs = 3600000) {
|
|
|
36
39
|
}
|
|
37
40
|
export function clearCache(key) {
|
|
38
41
|
if (key) {
|
|
39
|
-
const filePath = path.join(
|
|
42
|
+
const filePath = path.join(cacheDir, `${key}.json`);
|
|
40
43
|
if (fs.existsSync(filePath))
|
|
41
44
|
fs.unlinkSync(filePath);
|
|
42
45
|
}
|
|
43
46
|
else {
|
|
44
|
-
if (fs.existsSync(
|
|
45
|
-
const files = fs.readdirSync(
|
|
47
|
+
if (fs.existsSync(cacheDir)) {
|
|
48
|
+
const files = fs.readdirSync(cacheDir);
|
|
46
49
|
for (const file of files) {
|
|
47
|
-
fs.unlinkSync(path.join(
|
|
50
|
+
fs.unlinkSync(path.join(cacheDir, file));
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
}
|
package/dist/core/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { loadConfig, generateDefaultConfig, InfrawiseConfigSchema, ConfigError as ConfigValidationError, } from './config.js';
|
|
2
2
|
export { logger } from './logger.js';
|
|
3
3
|
export { InfrawiseError, AWSConnectionError, DynamoDBError, PostgresConnectionError, RepositoryScanError, ConfigError, formatError, } from './errors.js';
|
|
4
|
-
export { writeCache, readCache, clearCache } from './cache.js';
|
|
4
|
+
export { writeCache, readCache, clearCache, setCacheDir } from './cache.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infrawise",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"mcpName": "io.github.Sidd27/infrawise",
|
|
5
5
|
"description": "CLI-first infrastructure intelligence platform — analyzes DynamoDB, PostgreSQL, MySQL, MongoDB, SQS, SNS, SSM, Secrets Manager, Lambda, S3, CloudWatch Logs and exposes findings as an MCP server for Claude Code",
|
|
6
6
|
"keywords": [
|