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 +137 -146
- package/dist/adapters/{dynamodb.js → aws/dynamodb.js} +1 -1
- package/dist/adapters/aws/index.js +4 -0
- package/dist/adapters/{logs.js → aws/logs.js} +1 -1
- package/dist/adapters/aws/s3.js +72 -0
- package/dist/adapters/{aws.js → aws/services.js} +1 -1
- package/dist/adapters/db/index.js +3 -0
- package/dist/adapters/{mongodb.js → db/mongodb.js} +1 -1
- package/dist/adapters/{mysql.js → db/mysql.js} +1 -1
- package/dist/adapters/{postgres.js → db/postgres.js} +1 -1
- package/dist/adapters/iac/index.js +1 -0
- package/dist/adapters/{terraform.js → iac/terraform.js} +1 -1
- package/dist/analyzers/aws-services.js +61 -0
- package/dist/analyzers/index.js +3 -3
- package/dist/cli/commands/analyze.js +77 -14
- package/dist/cli/commands/auth.js +1 -1
- package/dist/cli/commands/dev.js +1 -1
- package/dist/cli/commands/doctor.js +23 -6
- package/dist/cli/commands/stdio.js +28 -0
- package/dist/cli/index.js +12 -0
- package/dist/cli/utils.js +3 -0
- package/dist/core/config.js +1 -0
- package/dist/graph/index.js +39 -5
- package/dist/server/index.js +41 -17
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://github.com/Sidd27/infrawise/actions/workflows/npm-publish.yml)
|
|
5
5
|
[](https://github.com/Sidd27/infrawise/actions/workflows/ci.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
|
+
[](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
|
|
161
|
-
|
|
162
|
-
| `get_infra_overview`
|
|
163
|
-
| `get_graph_summary`
|
|
164
|
-
| `analyze_function`
|
|
165
|
-
| `suggest_gsi`
|
|
166
|
-
| `postgres_index_suggestions` | Exact `CREATE INDEX` SQL for your actual table
|
|
167
|
-
| `suggest_mongo_index`
|
|
168
|
-
| `mysql_index_suggestions`
|
|
169
|
-
| `get_queue_details`
|
|
170
|
-
| `get_topic_details`
|
|
171
|
-
| `get_secrets_overview`
|
|
172
|
-
| `get_parameter_overview`
|
|
173
|
-
| `get_lambda_overview`
|
|
174
|
-
| `get_eventbridge_details`
|
|
175
|
-
| `
|
|
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
|
|
182
|
-
|
|
183
|
-
| `infrawise init`
|
|
184
|
-
| `infrawise auth`
|
|
185
|
-
| `infrawise analyze` | Scan repo + AWS, build graph, print findings
|
|
186
|
-
| `infrawise dev`
|
|
187
|
-
| `infrawise
|
|
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
|
|
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:
|
|
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: []
|
|
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:
|
|
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
|
|
317
|
-
|
|
318
|
-
| DynamoDB schema
|
|
319
|
-
| PostgreSQL / MySQL schema
|
|
320
|
-
| MongoDB schema
|
|
321
|
-
| SQS
|
|
322
|
-
| Kafka (kafkajs)
|
|
323
|
-
| Secrets Manager
|
|
324
|
-
| Lambda
|
|
325
|
-
|
|
|
326
|
-
|
|
|
327
|
-
|
|
|
328
|
-
|
|
|
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
|
|
335
|
-
|
|
336
|
-
| Full Table Scan (DynamoDB) | High
|
|
337
|
-
| Missing GSI
|
|
338
|
-
| Hot Partition
|
|
339
|
-
| Missing Index (PostgreSQL) | Medium
|
|
340
|
-
| N+1 Query
|
|
341
|
-
| Large SELECT
|
|
342
|
-
| Missing MySQL Index
|
|
343
|
-
| MySQL Full Table Scan
|
|
344
|
-
| Missing Mongo Index
|
|
345
|
-
| Collection Scan
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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/
|
|
413
|
-
|
|
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).
|
|
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
|
-
|
|
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 '
|
|
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;
|
|
@@ -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 '
|
|
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 '
|
|
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 };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MongoClient } from 'mongodb';
|
|
2
|
-
import { InfrawiseError, logger } from '
|
|
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 '
|
|
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 '
|
|
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';
|
|
@@ -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
|
+
}
|
package/dist/analyzers/index.js
CHANGED
|
@@ -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;
|