infrawise 0.8.3 → 0.9.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 +2 -1
- package/dist/adapters/aws/s3.js +23 -2
- package/dist/adapters/aws/services.js +39 -3
- 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/context/index.js +9 -8
- package/dist/core/cache.js +12 -9
- package/dist/core/index.js +1 -1
- package/dist/graph/index.js +1 -0
- package/dist/server/index.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -170,7 +170,7 @@ To let Claude Code manage the server lifecycle automatically:
|
|
|
170
170
|
| `suggest_mongo_index` | Exact `createIndex` command for a MongoDB collection + field |
|
|
171
171
|
| `mysql_index_suggestions` | Exact `ALTER TABLE ADD INDEX` SQL for your MySQL table |
|
|
172
172
|
| `get_queue_details` | SQS queues — DLQ status, encryption, message counts |
|
|
173
|
-
| `get_topic_details` | SNS topics — subscription counts and
|
|
173
|
+
| `get_topic_details` | SNS topics — subscription counts, protocols, and filter policies (required message attributes per subscription) |
|
|
174
174
|
| `get_secrets_overview` | Secrets Manager — names and rotation status (values never included) |
|
|
175
175
|
| `get_parameter_overview` | SSM Parameter Store — names, types, tiers (values never included) |
|
|
176
176
|
| `get_lambda_overview` | Lambda functions — runtime, memory, timeout, triggers (SQS/DynamoDB/Kinesis/EventBridge/S3), env var key names |
|
|
@@ -345,6 +345,7 @@ Works from AWS APIs, database schema introspection, and IaC files — no depende
|
|
|
345
345
|
| PostgreSQL / MySQL schema | Tables, indexes, column types |
|
|
346
346
|
| MongoDB schema | Collections, indexes |
|
|
347
347
|
| SQS | Missing DLQs, unencrypted queues, large backlogs |
|
|
348
|
+
| SNS | Subscription filter policies — required message attributes per subscription |
|
|
348
349
|
| Kafka (kafkajs) | Producer/consumer topic mapping from code |
|
|
349
350
|
| Secrets Manager | Missing secret rotation |
|
|
350
351
|
| Lambda | Default memory (128 MB), high timeouts, triggers (SQS/DynamoDB/Kinesis/EventBridge/S3), missing DLQ on trigger source |
|
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,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SQSClient, ListQueuesCommand, GetQueueAttributesCommand } from '@aws-sdk/client-sqs';
|
|
2
|
-
import { SNSClient, ListTopicsCommand, GetTopicAttributesCommand, ListSubscriptionsByTopicCommand, } from '@aws-sdk/client-sns';
|
|
2
|
+
import { SNSClient, ListTopicsCommand, GetTopicAttributesCommand, ListSubscriptionsByTopicCommand, GetSubscriptionAttributesCommand, } from '@aws-sdk/client-sns';
|
|
3
3
|
import { SSMClient, DescribeParametersCommand } from '@aws-sdk/client-ssm';
|
|
4
4
|
import { SecretsManagerClient, ListSecretsCommand } from '@aws-sdk/client-secrets-manager';
|
|
5
5
|
import { LambdaClient, ListFunctionsCommand, ListEventSourceMappingsCommand, } from '@aws-sdk/client-lambda';
|
|
@@ -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;
|
|
@@ -98,12 +112,34 @@ export async function extractSNSMetadata(cfg = {}) {
|
|
|
98
112
|
]);
|
|
99
113
|
const attrs = attrsRes.Attributes ?? {};
|
|
100
114
|
const subs = subsRes.Subscriptions ?? [];
|
|
115
|
+
const filterPolicies = [];
|
|
116
|
+
for (const sub of subs) {
|
|
117
|
+
if (!sub.SubscriptionArn || sub.SubscriptionArn === 'PendingConfirmation')
|
|
118
|
+
continue;
|
|
119
|
+
try {
|
|
120
|
+
const subAttrs = await client.send(new GetSubscriptionAttributesCommand({ SubscriptionArn: sub.SubscriptionArn }));
|
|
121
|
+
const fp = subAttrs.Attributes?.['FilterPolicy'];
|
|
122
|
+
if (fp) {
|
|
123
|
+
const parsed = JSON.parse(fp);
|
|
124
|
+
filterPolicies.push({
|
|
125
|
+
subscriptionArn: sub.SubscriptionArn,
|
|
126
|
+
protocol: sub.Protocol ?? 'unknown',
|
|
127
|
+
requiredAttributes: Object.keys(parsed),
|
|
128
|
+
scope: subAttrs.Attributes?.['FilterPolicyScope'] ?? 'MessageAttributes',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// skip subscription if attributes fetch fails
|
|
134
|
+
}
|
|
135
|
+
}
|
|
101
136
|
topics.push({
|
|
102
137
|
name: arn.split(':').pop() ?? arn,
|
|
103
138
|
arn,
|
|
104
139
|
encrypted: !!attrs['KmsMasterKeyId'],
|
|
105
140
|
subscriptionCount: parseInt(attrs['SubscriptionsConfirmed'] ?? '0', 10),
|
|
106
141
|
subscriptionProtocols: [...new Set(subs.map((s) => s.Protocol ?? 'unknown'))],
|
|
142
|
+
filterPolicies,
|
|
107
143
|
});
|
|
108
144
|
}
|
|
109
145
|
catch (err) {
|
|
@@ -298,7 +334,7 @@ export async function extractLambdaMetadata(cfg = {}, includeFunctions) {
|
|
|
298
334
|
});
|
|
299
335
|
}
|
|
300
336
|
marker = res.NextMarker;
|
|
301
|
-
} while (marker
|
|
337
|
+
} while (marker);
|
|
302
338
|
// Fetch all event source mappings in one paginated call and attach to functions
|
|
303
339
|
const triggerMap = await fetchAllEventSourceMappings(cfg);
|
|
304
340
|
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/context/index.js
CHANGED
|
@@ -193,14 +193,15 @@ function resolveStringValue(node, sourceFile) {
|
|
|
193
193
|
return result;
|
|
194
194
|
}
|
|
195
195
|
if (Node.isIdentifier(node)) {
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
196
|
+
const symbol = node.getSymbol();
|
|
197
|
+
if (symbol) {
|
|
198
|
+
for (const decl of symbol.getDeclarations()) {
|
|
199
|
+
if (Node.isVariableDeclaration(decl)) {
|
|
200
|
+
const init = decl.getInitializer();
|
|
201
|
+
if (init)
|
|
202
|
+
return resolveStringValue(init, sourceFile);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
204
205
|
}
|
|
205
206
|
}
|
|
206
207
|
return null;
|
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/dist/graph/index.js
CHANGED
|
@@ -85,6 +85,7 @@ export function buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta = [],
|
|
|
85
85
|
provider: 'aws',
|
|
86
86
|
subscriptionCount: t.subscriptionCount,
|
|
87
87
|
encrypted: t.encrypted,
|
|
88
|
+
filterPolicies: t.filterPolicies?.length > 0 ? t.filterPolicies : undefined,
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
91
|
for (const s of servicesMeta.secrets ?? []) {
|
package/dist/server/index.js
CHANGED
|
@@ -265,7 +265,7 @@ export function createMcpServer() {
|
|
|
265
265
|
});
|
|
266
266
|
}));
|
|
267
267
|
mcp.registerTool('get_topic_details', {
|
|
268
|
-
description: 'Returns all SNS topics with subscription count and
|
|
268
|
+
description: 'Returns all SNS topics with subscription count, encryption status, and filter policies. Filter policies list the message attributes each subscription requires — publishers must include these attributes or messages are silently dropped. Call this before writing any SNS publish code or when reviewing event fan-out patterns.',
|
|
269
269
|
inputSchema: z.object({}),
|
|
270
270
|
}, logged('get_topic_details', async () => {
|
|
271
271
|
const topics = getTopicNodes(currentGraph);
|
|
@@ -276,6 +276,7 @@ export function createMcpServer() {
|
|
|
276
276
|
provider: t.provider,
|
|
277
277
|
subscriptionCount: t.subscriptionCount,
|
|
278
278
|
encrypted: t.encrypted,
|
|
279
|
+
filterPolicies: t.filterPolicies ?? [],
|
|
279
280
|
})),
|
|
280
281
|
});
|
|
281
282
|
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infrawise",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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": [
|