infrawise 0.7.1 → 0.8.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 CHANGED
@@ -4,6 +4,7 @@
4
4
  [![Publish to npm](https://github.com/Sidd27/infrawise/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/Sidd27/infrawise/actions/workflows/npm-publish.yml)
5
5
  [![CI](https://github.com/Sidd27/infrawise/actions/workflows/ci.yml/badge.svg)](https://github.com/Sidd27/infrawise/actions/workflows/ci.yml)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+ [![infrawise MCP server](https://glama.ai/mcp/servers/Sidd27/infrawise/badges/score.svg)](https://glama.ai/mcp/servers/Sidd27/infrawise)
7
8
 
8
9
  **Understand your infrastructure, not just your code.**
9
10
 
@@ -22,12 +23,14 @@ AI coding assistants can read your source files but have no deterministic knowle
22
23
  Infrawise replaces guessing with infrastructure-aware context.
23
24
 
24
25
  **Without Infrawise**, an AI assistant might:
26
+
25
27
  - Suggest a `.scan()` on your Orders table that has 50M rows
26
28
  - Recommend adding a GSI on `status` that you already have
27
29
  - Write a `SELECT *` when you need to keep query cost low
28
30
  - Not notice that 5 functions are already hammering the same partition key
29
31
 
30
32
  **With Infrawise**, it knows:
33
+
31
34
  - Your exact table schemas, partition keys, sort keys, and GSIs
32
35
  - Which functions query which tables and how
33
36
  - Which patterns are already flagged as high severity
@@ -157,34 +160,57 @@ To let Claude Code manage the server lifecycle automatically:
157
160
 
158
161
  ### MCP tools
159
162
 
160
- | Tool | What it provides |
161
- |---|---|
162
- | `get_infra_overview` | Complete snapshot — all services, counts, and high-severity findings |
163
- | `get_graph_summary` | Full infrastructure graph — all nodes, edges, and findings |
164
- | `analyze_function` | Issues in a specific function — scans, missing indexes, N+1, trigger event shapes |
165
- | `suggest_gsi` | Exact GSI config for a DynamoDB table + attribute |
166
- | `postgres_index_suggestions` | Exact `CREATE INDEX` SQL for your actual table |
167
- | `suggest_mongo_index` | Exact `createIndex` command for a MongoDB collection + field |
168
- | `mysql_index_suggestions` | Exact `ALTER TABLE ADD INDEX` SQL for your MySQL table |
169
- | `get_queue_details` | SQS queues — DLQ status, encryption, message counts |
170
- | `get_topic_details` | SNS topics — subscription counts and protocols |
171
- | `get_secrets_overview` | Secrets Manager — names and rotation status (values never included) |
172
- | `get_parameter_overview` | SSM Parameter Store — names, types, tiers (values never included) |
173
- | `get_lambda_overview` | Lambda functions — runtime, memory, timeout, triggers (SQS/DynamoDB/Kinesis/EventBridge), env var key names |
174
- | `get_eventbridge_details` | EventBridge rules — name, state, schedule/event pattern, target functions |
175
- | `get_log_errors` | CloudWatch error patterns and counts (no raw log messages) |
163
+ | Tool | What it provides |
164
+ | ---------------------------- | ----------------------------------------------------------------------------------------------------------- |
165
+ | `get_infra_overview` | Complete snapshot — all services, counts, and high-severity findings |
166
+ | `get_graph_summary` | Full infrastructure graph — all nodes, edges, and findings |
167
+ | `analyze_function` | Issues in a specific function — scans, missing indexes, N+1, trigger event shapes |
168
+ | `suggest_gsi` | Exact GSI config for a DynamoDB table + attribute |
169
+ | `postgres_index_suggestions` | Exact `CREATE INDEX` SQL for your actual table |
170
+ | `suggest_mongo_index` | Exact `createIndex` command for a MongoDB collection + field |
171
+ | `mysql_index_suggestions` | Exact `ALTER TABLE ADD INDEX` SQL for your MySQL table |
172
+ | `get_queue_details` | SQS queues — DLQ status, encryption, message counts |
173
+ | `get_topic_details` | SNS topics — subscription counts and protocols |
174
+ | `get_secrets_overview` | Secrets Manager — names and rotation status (values never included) |
175
+ | `get_parameter_overview` | SSM Parameter Store — names, types, tiers (values never included) |
176
+ | `get_lambda_overview` | Lambda functions — runtime, memory, timeout, triggers (SQS/DynamoDB/Kinesis/EventBridge/S3), env var key names |
177
+ | `get_eventbridge_details` | EventBridge rules — name, state, schedule/event pattern, target functions |
178
+ | `get_s3_overview` | S3 buckets versioning, encryption, public access, event notifications |
179
+ | `get_log_errors` | CloudWatch error patterns and counts (no raw log messages) |
176
180
 
177
181
  ---
178
182
 
179
183
  ## CLI reference
180
184
 
181
- | Command | What it does |
182
- |---|---|
183
- | `infrawise init` | Detect AWS + repo, generate `infrawise.yaml` |
184
- | `infrawise auth` | Select or switch AWS profile |
185
- | `infrawise analyze` | Scan repo + AWS, build graph, print findings |
186
- | `infrawise dev` | Start MCP server — auto-analyzes if no cache, watches files for live refresh |
187
- | `infrawise doctor` | Validate AWS access, DB connectivity, and config |
185
+ | Command | What it does |
186
+ | ------------------- | ---------------------------------------------------------------------------- |
187
+ | `infrawise init` | Detect AWS + repo, generate `infrawise.yaml` |
188
+ | `infrawise auth` | Select or switch AWS profile |
189
+ | `infrawise analyze` | Scan repo + AWS, build graph, print findings |
190
+ | `infrawise dev` | Start MCP server — auto-analyzes if no cache, watches files for live refresh |
191
+ | `infrawise stdio` | Start MCP server on stdio transport (for Claude Desktop) |
192
+ | `infrawise doctor` | Validate AWS access, DB connectivity, and config |
193
+
194
+ ### `infrawise analyze` options
195
+
196
+ | Flag | Description |
197
+ | --------------------- | ---------------------------------------------------------------------- |
198
+ | `-c, --config <path>` | Path to `infrawise.yaml` (default: `infrawise.yaml`) |
199
+ | `-r, --repo <path>` | Repository to scan (default: current directory) |
200
+ | `--no-cache` | Skip reading/writing the cache |
201
+ | `-o, --output <path>` | Save findings as a markdown report, e.g. `report.md` |
202
+ | `--severity <level>` | Only show findings at or above this level: `high` \| `medium` \| `low` \| `verify` |
203
+
204
+ ```bash
205
+ # Export a shareable findings report
206
+ infrawise analyze --output report.md
207
+
208
+ # Only show high-severity issues
209
+ infrawise analyze --severity high
210
+
211
+ # High-severity issues only, saved to a file
212
+ infrawise analyze --severity high --output report.md
213
+ ```
188
214
 
189
215
  ---
190
216
 
@@ -206,12 +232,12 @@ Full example:
206
232
  project: payments-service
207
233
 
208
234
  aws:
209
- profile: default # AWS profile from ~/.aws/credentials
235
+ profile: default # AWS profile from ~/.aws/credentials
210
236
  region: ap-south-1
211
237
 
212
238
  dynamodb:
213
239
  enabled: true
214
- includeTables: # omit to include all tables
240
+ includeTables: # omit to include all tables
215
241
  - Orders
216
242
  - Users
217
243
 
@@ -221,11 +247,11 @@ postgres:
221
247
 
222
248
  mysql:
223
249
  enabled: false
224
- connectionString: ""
250
+ connectionString: ''
225
251
 
226
252
  mongodb:
227
253
  enabled: false
228
- connectionString: ""
254
+ connectionString: ''
229
255
 
230
256
  sqs:
231
257
  enabled: true
@@ -235,14 +261,14 @@ sns:
235
261
 
236
262
  ssm:
237
263
  enabled: true
238
- paths: [] # filter by prefix e.g. ["/myapp/prod"]
264
+ paths: [] # filter by prefix e.g. ["/myapp/prod"]
239
265
 
240
266
  secretsManager:
241
267
  enabled: true
242
268
 
243
269
  lambda:
244
270
  enabled: true
245
- includeFunctions: # omit to include all functions
271
+ includeFunctions: # omit to include all functions
246
272
  - myFunction
247
273
  - anotherFunction
248
274
 
@@ -252,6 +278,9 @@ eventbridge:
252
278
  rds:
253
279
  enabled: false
254
280
 
281
+ s3:
282
+ enabled: false
283
+
255
284
  kafka:
256
285
  enabled: false
257
286
 
@@ -274,10 +303,7 @@ Infrawise is **read-only**. Minimum IAM policy required:
274
303
  "Statement": [
275
304
  {
276
305
  "Effect": "Allow",
277
- "Action": [
278
- "dynamodb:ListTables",
279
- "dynamodb:DescribeTable"
280
- ],
306
+ "Action": ["dynamodb:ListTables", "dynamodb:DescribeTable"],
281
307
  "Resource": "*"
282
308
  }
283
309
  ]
@@ -313,36 +339,37 @@ Infrawise has two analysis layers:
313
339
 
314
340
  Works from AWS APIs, database schema introspection, and IaC files — no dependency on application code:
315
341
 
316
- | Service | What it checks |
317
- |---|---|
318
- | DynamoDB schema | Tables, GSIs, partition keys |
319
- | PostgreSQL / MySQL schema | Tables, indexes, column types |
320
- | MongoDB schema | Collections, indexes |
321
- | SQS | Missing DLQs, unencrypted queues, large backlogs |
322
- | Kafka (kafkajs) | Producer/consumer topic mapping from code |
323
- | Secrets Manager | Missing secret rotation |
324
- | Lambda | Default memory (128 MB), high timeouts, triggers (SQS/DynamoDB/Kinesis/EventBridge), missing DLQ on trigger source |
325
- | EventBridge | Rules, schedules, event patterns, target Lambda functions |
326
- | RDS | Publicly accessible, no backups, unencrypted, no deletion protection, single-AZ |
327
- | CloudWatch Logs | Log groups with no retention policy |
328
- | Terraform / CloudFormation / CDK | IaC drift vs deployed state |
342
+ | Service | What it checks |
343
+ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
344
+ | DynamoDB schema | Tables, GSIs, partition keys |
345
+ | PostgreSQL / MySQL schema | Tables, indexes, column types |
346
+ | MongoDB schema | Collections, indexes |
347
+ | SQS | Missing DLQs, unencrypted queues, large backlogs |
348
+ | Kafka (kafkajs) | Producer/consumer topic mapping from code |
349
+ | Secrets Manager | Missing secret rotation |
350
+ | Lambda | Default memory (128 MB), high timeouts, triggers (SQS/DynamoDB/Kinesis/EventBridge/S3), missing DLQ on trigger source |
351
+ | S3 | Public access blocking (verify), missing versioning, missing encryption |
352
+ | EventBridge | Rules, schedules, event patterns, target Lambda functions |
353
+ | RDS | Publicly accessible, no backups, unencrypted, no deletion protection, single-AZ |
354
+ | CloudWatch Logs | Log groups with no retention policy |
355
+ | Terraform / CloudFormation / CDK | IaC drift vs deployed state |
329
356
 
330
357
  ### Code correlation analysis (TypeScript / JavaScript)
331
358
 
332
359
  Uses [ts-morph](https://ts-morph.com/) AST analysis to detect which functions call which tables and how:
333
360
 
334
- | Analyzer | Severity | What it detects |
335
- |---|---|---|
336
- | Full Table Scan (DynamoDB) | High | `.scan()` calls without filters |
337
- | Missing GSI | Medium | Queries on attributes without a matching GSI |
338
- | Hot Partition | Medium | 5+ distinct code paths hitting the same table |
339
- | Missing Index (PostgreSQL) | Medium | Tables queried without indexes |
340
- | N+1 Query | Medium | Repeated query patterns from ORM loops |
341
- | Large SELECT | Low | `SELECT *` usage |
342
- | Missing MySQL Index | Medium | MySQL tables queried without indexes |
343
- | MySQL Full Table Scan | High | Full table scan patterns in MySQL queries |
344
- | Missing Mongo Index | Medium | Collections queried without secondary indexes |
345
- | Collection Scan | High | `find()` calls without filter predicates |
361
+ | Analyzer | Severity | What it detects |
362
+ | -------------------------- | -------- | --------------------------------------------- |
363
+ | Full Table Scan (DynamoDB) | High | `.scan()` calls without filters |
364
+ | Missing GSI | Medium | Queries on attributes without a matching GSI |
365
+ | Hot Partition | Medium | 5+ distinct code paths hitting the same table |
366
+ | Missing Index (PostgreSQL) | Medium | Tables queried without indexes |
367
+ | N+1 Query | Medium | Repeated query patterns from ORM loops |
368
+ | Large SELECT | Low | `SELECT *` usage |
369
+ | Missing MySQL Index | Medium | MySQL tables queried without indexes |
370
+ | MySQL Full Table Scan | High | Full table scan patterns in MySQL queries |
371
+ | Missing Mongo Index | Medium | Collections queried without secondary indexes |
372
+ | Collection Scan | High | `find()` calls without filter predicates |
346
373
 
347
374
  Non-TypeScript/JavaScript projects still get full value from infrastructure-level analyzers — code correlation (function-to-table mapping, N+1 patterns) is skipped.
348
375
 
@@ -377,29 +404,52 @@ Infrawise does not use an LLM to analyze your infrastructure. All extraction and
377
404
 
378
405
  ## Architecture overview
379
406
 
380
- ```
381
- Your repo (any language) Your repo (TS/JS only)
382
- │ │
383
- │ Repository Scanner (ts-morph AST)
384
- │ which functions → which tables
385
- │ │
386
- ┌───────┴──────────────────────────────────┴────────────┐
387
- │ infrawise analyze │
388
- │ │
389
- │ AWS APIs / DB schema / IaC files + Code ops (opt) │
390
- │ (works for any project) (TS/JS only) │
391
- │ │ │
392
- │ Graph Engine │
393
- │ (nodes + edges) │
394
- │ │ │
395
- │ Analyzer Engine │
396
- │ (rule-based, deterministic) │
397
- └─────────────────────────┬─────────────────────────────┘
398
-
399
- ┌──────────────────┐
400
- │ MCP Server │ ◄── Claude Code
401
- │ localhost:3000 │ ◄── Cursor
402
- └──────────────────┘ ◄── Windsurf
407
+ ```mermaid
408
+ flowchart LR
409
+ subgraph IN["Your Infrastructure & Code"]
410
+ direction TB
411
+ D["DynamoDB"]
412
+ L["Lambda · SQS · SNS\nEventBridge · RDS"]
413
+ S["Secrets Manager · SSM\nCloudWatch"]
414
+ P["PostgreSQL · MySQL"]
415
+ M["MongoDB"]
416
+ T["Terraform · CDK\nCloudFormation"]
417
+ C["TypeScript / JS"]
418
+ end
419
+
420
+ A["Adapters"]
421
+ G["Graph Engine"]
422
+ AN["23 Analyzers"]
423
+ CA["Cache"]
424
+
425
+ subgraph SV["infrawise dev"]
426
+ MCP["MCP Server\nlocalhost:3000/mcp"]
427
+ end
428
+
429
+ subgraph AI["AI Coding Assistants"]
430
+ direction TB
431
+ CC["Claude Code"]
432
+ CU["Cursor"]
433
+ WS["Windsurf"]
434
+ end
435
+
436
+ D & L & S & P & M & T & C --> A
437
+ A --> G --> AN --> CA --> MCP
438
+ MCP --> CC & CU & WS
439
+
440
+ classDef aws fill:#FF9900,stroke:#232F3E,color:#000
441
+ classDef db fill:#336791,stroke:#1a3a5c,color:#fff
442
+ classDef iac fill:#7B42BC,stroke:#4a2080,color:#fff
443
+ classDef code fill:#3178C6,stroke:#1a4a80,color:#fff
444
+ classDef iw fill:#1a1a2e,stroke:#e94560,color:#fff
445
+ classDef ai fill:#10a37f,stroke:#0a6b54,color:#fff
446
+
447
+ class D,L,S aws
448
+ class P,M db
449
+ class T iac
450
+ class C code
451
+ class A,G,AN,CA,MCP iw
452
+ class CC,CU,WS ai
403
453
  ```
404
454
 
405
455
  ### Source layout
@@ -409,8 +459,10 @@ src/
409
459
  types.ts Shared type definitions
410
460
  core/ Config (Zod + YAML), logger (Pino), local cache
411
461
  graph/ Graph engine — nodes, edges, builder
412
- adapters/ Flat extractors: dynamodb.ts, postgres.ts, mysql.ts,
413
- mongodb.ts, aws.ts, logs.ts, terraform.ts
462
+ adapters/
463
+ aws/ DynamoDB, Lambda, SQS/SNS/SSM/Secrets/EventBridge/RDS, CloudWatch
464
+ db/ PostgreSQL, MySQL, MongoDB
465
+ iac/ Terraform, CDK, CloudFormation (local file parsing)
414
466
  analyzers/ 23 rule-based analyzers
415
467
  context/ Repository scanner (ts-morph AST)
416
468
  server/ Fastify MCP server (@modelcontextprotocol/sdk, Streamable HTTP)
@@ -430,20 +482,7 @@ src/
430
482
 
431
483
  ## Roadmap
432
484
 
433
- Feature roadmap is tracked in the [GitHub Project](https://github.com/users/Sidd27/projects/1). Priorities, complexity, and virality scores are visible there. Feature requests and upvotes welcome.
434
-
435
- ### Planned
436
- - Runtime tracing integration
437
- - Incremental analysis for large monorepos
438
- - Kubernetes workload graphing
439
- - VS Code extension
440
- - Infrastructure drift detection
441
- - MSK (Amazon Managed Streaming for Apache Kafka) — cluster metadata + topic listing via MSK API and Kafka admin client
442
-
443
- ### Under consideration
444
- - OpenTelemetry integration
445
- - CI/CD reporting mode
446
- - Multi-repository graph correlation
485
+ Feature roadmap is tracked in the [GitHub Project](https://github.com/users/Sidd27/projects/1). Feature requests and upvotes welcome.
447
486
 
448
487
  ---
449
488
 
@@ -455,47 +494,7 @@ The `demo/localstack/` directory runs infrawise against real AWS APIs emulated l
455
494
 
456
495
  ## Contributing
457
496
 
458
- ### Prerequisites
459
-
460
- Node.js 24+, pnpm 9+, AWS CLI (for integration testing).
461
-
462
- ### Setup
463
-
464
- ```bash
465
- git clone https://github.com/Sidd27/infrawise
466
- cd infrawise
467
- pnpm install
468
- pnpm build
469
- ```
470
-
471
- ### Development workflow
472
-
473
- ```bash
474
- pnpm build # compile
475
- pnpm test # run all tests
476
- pnpm typecheck # TypeScript strict check
477
- pnpm lint # ESLint
478
- ```
479
-
480
- ### Adding a new analyzer
481
-
482
- 1. Create your analyzer in `src/analyzers/`
483
- 2. Implement the `Analyzer` interface:
484
- ```ts
485
- export class MyAnalyzer implements Analyzer {
486
- name = 'MyAnalyzer';
487
- async analyze(graph: SystemGraph): Promise<Finding[]> { ... }
488
- }
489
- ```
490
- 3. Export it from `src/analyzers/index.ts`
491
- 4. Add tests in `src/analyzers/__tests__/`
492
-
493
- ### Adding a new database adapter
494
-
495
- 1. Create your extractor as `src/adapters/yourdb.ts`
496
- 2. Export a function returning `Promise<YourTableMetadata[]>`
497
- 3. Add the metadata type to `src/types.ts` if needed
498
- 4. Wire it into `src/cli/commands/analyze.ts`
497
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for a full walkthrough — including how to add a new service adapter, a new analyzer, and the PR checklist.
499
498
 
500
499
  ### Releasing
501
500
 
@@ -508,14 +507,6 @@ pnpm release 1.5.0 # explicit version
508
507
 
509
508
  Bumps `package.json`, commits, tags, pushes, and creates a draft GitHub release with notes from commit messages. Then publish the draft on GitHub to trigger npm publish.
510
509
 
511
- ### PR checklist
512
-
513
- - `pnpm lint` passes
514
- - `pnpm typecheck` passes
515
- - `pnpm test` passes
516
- - New analyzers have unit tests with mock graph data
517
- - No hardcoded AWS regions or credentials
518
-
519
510
  ---
520
511
 
521
512
  ## License
@@ -1,6 +1,6 @@
1
1
  import { DynamoDBClient, ListTablesCommand, DescribeTableCommand, } from '@aws-sdk/client-dynamodb';
2
2
  import { fromIni } from '@aws-sdk/credential-providers';
3
- import { DynamoDBError, logger } from '../core/index.js';
3
+ import { DynamoDBError, logger } from '../../core/index.js';
4
4
  function createDynamoClient(config) {
5
5
  const region = config.aws?.region ?? 'us-east-1';
6
6
  const profile = config.aws?.profile;
@@ -0,0 +1,4 @@
1
+ export * from './services.js';
2
+ export * from './dynamodb.js';
3
+ export * from './logs.js';
4
+ export * from './s3.js';
@@ -1,6 +1,6 @@
1
1
  import { CloudWatchLogsClient, DescribeLogGroupsCommand, FilterLogEventsCommand, } from '@aws-sdk/client-cloudwatch-logs';
2
2
  import { fromIni } from '@aws-sdk/credential-providers';
3
- import { logger } from '../core/index.js';
3
+ import { logger } from '../../core/index.js';
4
4
  // Hard caps to prevent context bloat
5
5
  const MAX_LOG_GROUPS = 50;
6
6
  const MAX_EVENTS_PER_GROUP = 50;
@@ -0,0 +1,72 @@
1
+ import { S3Client, ListBucketsCommand, GetBucketNotificationConfigurationCommand, GetBucketVersioningCommand, GetBucketEncryptionCommand, GetPublicAccessBlockCommand, } from '@aws-sdk/client-s3';
2
+ import { fromIni } from '@aws-sdk/credential-providers';
3
+ import { logger } from '../../core/index.js';
4
+ function clientConfig(cfg) {
5
+ const region = cfg.region ?? 'us-east-1';
6
+ const base = { region };
7
+ if (cfg.endpoint)
8
+ base.endpoint = cfg.endpoint;
9
+ if (cfg.profile)
10
+ base.credentials = fromIni({ profile: cfg.profile });
11
+ return base;
12
+ }
13
+ export async function extractS3Metadata(cfg = {}) {
14
+ const client = new S3Client(clientConfig(cfg));
15
+ const buckets = [];
16
+ try {
17
+ const listRes = await client.send(new ListBucketsCommand({}));
18
+ const rawBuckets = (listRes.Buckets ?? []).slice(0, 200);
19
+ for (const bucket of rawBuckets) {
20
+ const name = bucket.Name ?? '';
21
+ if (!name)
22
+ continue;
23
+ const arn = `arn:aws:s3:::${name}`;
24
+ const createdAt = bucket.CreationDate?.toISOString();
25
+ const [notifResult, versionResult, encryptResult, pabResult] = await Promise.allSettled([
26
+ client.send(new GetBucketNotificationConfigurationCommand({ Bucket: name })),
27
+ client.send(new GetBucketVersioningCommand({ Bucket: name })),
28
+ client.send(new GetBucketEncryptionCommand({ Bucket: name })),
29
+ client.send(new GetPublicAccessBlockCommand({ Bucket: name })),
30
+ ]);
31
+ const notifications = [];
32
+ if (notifResult.status === 'fulfilled') {
33
+ for (const config of notifResult.value.LambdaFunctionConfigurations ?? []) {
34
+ const lambdaArn = config.LambdaFunctionArn ?? '';
35
+ const lambdaName = lambdaArn.split(':').pop() ?? lambdaArn;
36
+ const rules = config.Filter?.Key?.FilterRules ?? [];
37
+ const prefix = rules.find((r) => r.Name?.toLowerCase() === 'prefix')?.Value;
38
+ const suffix = rules.find((r) => r.Name?.toLowerCase() === 'suffix')?.Value;
39
+ const notification = {
40
+ events: config.Events ?? [],
41
+ lambdaArn,
42
+ lambdaName,
43
+ };
44
+ if (prefix !== undefined)
45
+ notification.prefix = prefix;
46
+ if (suffix !== undefined)
47
+ notification.suffix = suffix;
48
+ notifications.push(notification);
49
+ }
50
+ }
51
+ const versioned = versionResult.status === 'fulfilled'
52
+ ? versionResult.value.Status === 'Enabled'
53
+ : false;
54
+ const encrypted = encryptResult.status === 'fulfilled'
55
+ ? (encryptResult.value.ServerSideEncryptionConfiguration?.Rules?.length ?? 0) > 0
56
+ : false;
57
+ let publicAccessBlocked = false;
58
+ if (pabResult.status === 'fulfilled') {
59
+ const pab = pabResult.value.PublicAccessBlockConfiguration ?? {};
60
+ publicAccessBlocked = !!(pab.BlockPublicAcls && pab.IgnorePublicAcls && pab.BlockPublicPolicy && pab.RestrictPublicBuckets);
61
+ }
62
+ buckets.push({ name, arn, createdAt, versioned, encrypted, publicAccessBlocked, notifications });
63
+ }
64
+ }
65
+ catch (err) {
66
+ logger.warn(`S3 list failed: ${err instanceof Error ? err.message : String(err)}`);
67
+ }
68
+ return buckets;
69
+ }
70
+ export async function validateS3Access(cfg = {}) {
71
+ await new S3Client(clientConfig(cfg)).send(new ListBucketsCommand({}));
72
+ }
@@ -6,7 +6,7 @@ import { LambdaClient, ListFunctionsCommand, ListEventSourceMappingsCommand, } f
6
6
  import { EventBridgeClient, ListRulesCommand, ListTargetsByRuleCommand, } from '@aws-sdk/client-eventbridge';
7
7
  import { RDSClient, DescribeDBInstancesCommand, } from '@aws-sdk/client-rds';
8
8
  import { fromIni } from '@aws-sdk/credential-providers';
9
- import { logger } from '../core/index.js';
9
+ import { logger } from '../../core/index.js';
10
10
  function clientConfig(cfg) {
11
11
  const region = cfg.region ?? 'us-east-1';
12
12
  const base = { region };
@@ -0,0 +1,3 @@
1
+ export * from './postgres.js';
2
+ export * from './mysql.js';
3
+ export * from './mongodb.js';
@@ -1,5 +1,5 @@
1
1
  import { MongoClient } from 'mongodb';
2
- import { InfrawiseError, logger } from '../core/index.js';
2
+ import { InfrawiseError, logger } from '../../core/index.js';
3
3
  const SYSTEM_DATABASES = new Set(['admin', 'local', 'config']);
4
4
  export class MongoConnectionError extends InfrawiseError {
5
5
  constructor(details) {
@@ -1,5 +1,5 @@
1
1
  import mysql from 'mysql2/promise';
2
- import { InfrawiseError, logger } from '../core/index.js';
2
+ import { InfrawiseError, logger } from '../../core/index.js';
3
3
  const SYSTEM_SCHEMAS = new Set(['information_schema', 'performance_schema', 'mysql', 'sys']);
4
4
  export class MySQLConnectionError extends InfrawiseError {
5
5
  constructor(details) {
@@ -1,5 +1,5 @@
1
1
  import { Pool } from 'pg';
2
- import { PostgresConnectionError, logger } from '../core/index.js';
2
+ import { PostgresConnectionError, logger } from '../../core/index.js';
3
3
  export async function extractPostgresMetadata(connectionString) {
4
4
  const pool = new Pool({
5
5
  connectionString,
@@ -0,0 +1 @@
1
+ export * from './terraform.js';
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import yaml from 'js-yaml';
4
- import { logger } from '../core/index.js';
4
+ import { logger } from '../../core/index.js';
5
5
  function emptySchema() {
6
6
  return {
7
7
  dynamoTables: [],
@@ -183,3 +183,64 @@ export class LambdaHighTimeoutAnalyzer {
183
183
  return findings;
184
184
  }
185
185
  }
186
+ // ─── S3 ──────────────────────────────────────────────────────────────────────
187
+ export class S3PublicAccessAnalyzer {
188
+ name = 'S3PublicAccessAnalyzer';
189
+ async analyze(graph) {
190
+ const findings = [];
191
+ for (const node of graph.nodes) {
192
+ if (node.type !== 'bucket')
193
+ continue;
194
+ if (node.publicAccessBlocked === false) {
195
+ findings.push({
196
+ severity: 'verify',
197
+ issue: `S3 bucket "${node.name}" has public access blocking disabled`,
198
+ description: `Public access blocking is disabled on "${node.name}". This is expected for static website hosting and public asset buckets. Confirm this is intentional before treating it as a security issue.`,
199
+ recommendation: `If "${node.name}" is not intentionally public, enable all four S3 Block Public Access settings: BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, RestrictPublicBuckets.`,
200
+ metadata: { bucketName: node.name, provider: node.provider },
201
+ });
202
+ }
203
+ }
204
+ return findings;
205
+ }
206
+ }
207
+ export class S3MissingVersioningAnalyzer {
208
+ name = 'S3MissingVersioningAnalyzer';
209
+ async analyze(graph) {
210
+ const findings = [];
211
+ for (const node of graph.nodes) {
212
+ if (node.type !== 'bucket')
213
+ continue;
214
+ if (node.versioned === false) {
215
+ findings.push({
216
+ severity: 'medium',
217
+ issue: `S3 bucket "${node.name}" does not have versioning enabled`,
218
+ description: `"${node.name}" has versioning disabled. Without versioning, accidental deletes or overwrites are unrecoverable. Versioning is required for cross-region replication and Object Lock.`,
219
+ recommendation: `Enable versioning on "${node.name}" via the S3 console or IaC. Consider adding a lifecycle rule to expire old versions and manage storage costs.`,
220
+ metadata: { bucketName: node.name, provider: node.provider },
221
+ });
222
+ }
223
+ }
224
+ return findings;
225
+ }
226
+ }
227
+ export class S3UnencryptedAnalyzer {
228
+ name = 'S3UnencryptedAnalyzer';
229
+ async analyze(graph) {
230
+ const findings = [];
231
+ for (const node of graph.nodes) {
232
+ if (node.type !== 'bucket')
233
+ continue;
234
+ if (node.encrypted === false) {
235
+ findings.push({
236
+ severity: 'medium',
237
+ issue: `S3 bucket "${node.name}" does not have server-side encryption configured`,
238
+ description: `"${node.name}" has no SSE (Server-Side Encryption) configuration. Data at rest is unencrypted. AWS S3 has enabled SSE-S3 by default since January 2023 for new buckets, but older buckets or those without explicit config should be verified.`,
239
+ recommendation: `Enable SSE on "${node.name}" using SSE-S3 (AES-256) or SSE-KMS. Specify the encryption configuration in your IaC to make it explicit.`,
240
+ metadata: { bucketName: node.name, provider: node.provider },
241
+ });
242
+ }
243
+ }
244
+ return findings;
245
+ }
246
+ }
@@ -4,7 +4,7 @@ export { MissingIndexAnalyzer, NplusOneAnalyzer, LargeSelectAnalyzer } from './p
4
4
  export { MissingMySQLIndexAnalyzer, MySQLFullTableScanAnalyzer } from './mysql.js';
5
5
  export { MissingMongoIndexAnalyzer, MongoCollectionScanAnalyzer } from './mongodb.js';
6
6
  export { IaCDriftAnalyzer } from './terraform.js';
7
- export { MissingDLQAnalyzer, UnencryptedQueueAnalyzer, LargeQueueBacklogAnalyzer, MissingSecretRotationAnalyzer, MissingLogRetentionAnalyzer, LambdaDefaultMemoryAnalyzer, LambdaHighTimeoutAnalyzer, LambdaMissingTriggerDLQAnalyzer, } from './aws-services.js';
7
+ export { MissingDLQAnalyzer, UnencryptedQueueAnalyzer, LargeQueueBacklogAnalyzer, MissingSecretRotationAnalyzer, MissingLogRetentionAnalyzer, LambdaDefaultMemoryAnalyzer, LambdaHighTimeoutAnalyzer, LambdaMissingTriggerDLQAnalyzer, S3PublicAccessAnalyzer, S3MissingVersioningAnalyzer, S3UnencryptedAnalyzer, } from './aws-services.js';
8
8
  export { RDSPubliclyAccessibleAnalyzer, RDSNoBackupAnalyzer, RDSUnencryptedAnalyzer, RDSNoDeletionProtectionAnalyzer, RDSNoMultiAZAnalyzer, } from './rds.js';
9
9
  export async function runAllAnalyzers(graph, analyzers) {
10
10
  const allFindings = [];
@@ -19,12 +19,12 @@ export async function runAllAnalyzers(graph, analyzers) {
19
19
  logger.warn(`Analyzer "${analyzer.name}" failed: ${err instanceof Error ? err.message : String(err)}`);
20
20
  }
21
21
  }
22
- const severityOrder = { high: 0, medium: 1, low: 2 };
22
+ const severityOrder = { high: 0, medium: 1, low: 2, verify: 3 };
23
23
  allFindings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
24
24
  return allFindings;
25
25
  }
26
26
  export function summarizeFindings(findings) {
27
- const counts = { total: findings.length, high: 0, medium: 0, low: 0 };
27
+ const counts = { total: findings.length, high: 0, medium: 0, low: 0, verify: 0 };
28
28
  for (const f of findings)
29
29
  counts[f.severity]++;
30
30
  return counts;