@deniscuciuc/compose-analyzer 1.0.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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +164 -0
- package/analyzerrc.example.json +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/package.json +81 -0
- package/dist/src/analyzers/image-analyzer.d.ts +8 -0
- package/dist/src/analyzers/image-analyzer.d.ts.map +1 -0
- package/dist/src/analyzers/image-analyzer.js +61 -0
- package/dist/src/analyzers/network-analyzer.d.ts +8 -0
- package/dist/src/analyzers/network-analyzer.d.ts.map +1 -0
- package/dist/src/analyzers/network-analyzer.js +48 -0
- package/dist/src/analyzers/reliability-analyzer.d.ts +8 -0
- package/dist/src/analyzers/reliability-analyzer.d.ts.map +1 -0
- package/dist/src/analyzers/reliability-analyzer.js +84 -0
- package/dist/src/analyzers/resource-analyzer.d.ts +8 -0
- package/dist/src/analyzers/resource-analyzer.d.ts.map +1 -0
- package/dist/src/analyzers/resource-analyzer.js +34 -0
- package/dist/src/analyzers/security-analyzer.d.ts +9 -0
- package/dist/src/analyzers/security-analyzer.d.ts.map +1 -0
- package/dist/src/analyzers/security-analyzer.js +82 -0
- package/dist/src/cli/options.d.ts +16 -0
- package/dist/src/cli/options.d.ts.map +1 -0
- package/dist/src/cli/options.js +129 -0
- package/dist/src/cli/runner.d.ts +6 -0
- package/dist/src/cli/runner.d.ts.map +1 -0
- package/dist/src/cli/runner.js +234 -0
- package/dist/src/collectors/compose-collector.d.ts +19 -0
- package/dist/src/collectors/compose-collector.d.ts.map +1 -0
- package/dist/src/collectors/compose-collector.js +140 -0
- package/dist/src/collectors/docker-collector.d.ts +8 -0
- package/dist/src/collectors/docker-collector.d.ts.map +1 -0
- package/dist/src/collectors/docker-collector.js +96 -0
- package/dist/src/config/loader.d.ts +3 -0
- package/dist/src/config/loader.d.ts.map +1 -0
- package/dist/src/config/loader.js +41 -0
- package/dist/src/constants.d.ts +23 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +70 -0
- package/dist/src/health-score.d.ts +3 -0
- package/dist/src/health-score.d.ts.map +1 -0
- package/dist/src/health-score.js +14 -0
- package/dist/src/interactive/display.d.ts +11 -0
- package/dist/src/interactive/display.d.ts.map +1 -0
- package/dist/src/interactive/display.js +117 -0
- package/dist/src/interactive/index.d.ts +15 -0
- package/dist/src/interactive/index.d.ts.map +1 -0
- package/dist/src/interactive/index.js +218 -0
- package/dist/src/interactive/menus.d.ts +68 -0
- package/dist/src/interactive/menus.d.ts.map +1 -0
- package/dist/src/interactive/menus.js +32 -0
- package/dist/src/reporters/diff-reporter.d.ts +21 -0
- package/dist/src/reporters/diff-reporter.d.ts.map +1 -0
- package/dist/src/reporters/diff-reporter.js +87 -0
- package/dist/src/reporters/html-reporter.d.ts +12 -0
- package/dist/src/reporters/html-reporter.d.ts.map +1 -0
- package/dist/src/reporters/html-reporter.js +226 -0
- package/dist/src/reporters/report-generator.d.ts +23 -0
- package/dist/src/reporters/report-generator.d.ts.map +1 -0
- package/dist/src/reporters/report-generator.js +326 -0
- package/dist/src/types.d.ts +198 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/utils/format.d.ts +6 -0
- package/dist/src/utils/format.d.ts.map +1 -0
- package/dist/src/utils/format.js +32 -0
- package/dist/src/utils/print.d.ts +8 -0
- package/dist/src/utils/print.d.ts.map +1 -0
- package/dist/src/utils/print.js +42 -0
- package/package.json +80 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.0.0] - 2026-06-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Docker Compose analyzer CLI with full, health, image, security, reliability, resource, and network commands
|
|
15
|
+
- YAML parsing for Compose v2, v3, and versionless files
|
|
16
|
+
- Image analysis for unpinned images and `:latest` tags
|
|
17
|
+
- Security analysis for secrets in environment variables, privileged containers, and exposed database ports
|
|
18
|
+
- Reliability analysis for healthchecks, `depends_on` conditions, and restart policies
|
|
19
|
+
- Resource analysis for missing CPU and memory limits
|
|
20
|
+
- Network analysis for default-network usage and random host port publishing
|
|
21
|
+
- Health score (0–100), Markdown/JSON/HTML reports, and report diffing
|
|
22
|
+
- Optional Docker daemon runtime enrichment with `--with-docker`
|
|
23
|
+
- Interactive CLI flows for analysis, reports, and settings
|
|
24
|
+
- Node built-in tests, GitHub Actions CI workflow, publish workflow, and OSS project metadata
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Denis Cuciuc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Docker Compose Analyzer
|
|
2
|
+
|
|
3
|
+
[](https://nodejs.org/)
|
|
4
|
+
[](https://www.npmjs.com/package/@deniscuciuc/compose-analyzer)
|
|
5
|
+
[](https://www.npmjs.com/package/@deniscuciuc/compose-analyzer)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://github.com/deniscuciuc/compose-analyzer/actions/workflows/ci.yml)
|
|
9
|
+
|
|
10
|
+
A CLI that analyzes Docker Compose files for image hygiene, security risk, reliability gaps, resource limits, and network exposure. It emits JSON to stdout with `-j`, or writes Markdown/JSON/HTML reports to `./reports`.
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
No installation required:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @deniscuciuc/compose-analyzer -f docker-compose.yml -c health
|
|
18
|
+
npx @deniscuciuc/compose-analyzer -f docker-compose.yml -c full --html
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or install globally:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g @deniscuciuc/compose-analyzer
|
|
25
|
+
compose-analyzer -f docker-compose.yml -c security
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- Unpinned image and `:latest` detection
|
|
33
|
+
- Plain-text secret detection in Compose environment variables
|
|
34
|
+
- Privileged container and exposed database port checks
|
|
35
|
+
- Missing healthcheck, weak `depends_on`, and restart policy analysis
|
|
36
|
+
- Missing CPU / memory limit detection for both legacy and deploy-based formats
|
|
37
|
+
- Default-network and random host-port publishing checks
|
|
38
|
+
- Markdown, JSON, HTML, and diffable report output
|
|
39
|
+
- Optional Docker runtime enrichment via `--with-docker`
|
|
40
|
+
- Interactive CLI mode for browsing analysis and report flows
|
|
41
|
+
|
|
42
|
+
## Requirements
|
|
43
|
+
|
|
44
|
+
- Node.js >= 20
|
|
45
|
+
- pnpm >= 10
|
|
46
|
+
- Any Docker Compose YAML file (`docker-compose.yml`, `compose.yml`, etc.)
|
|
47
|
+
- Optional: Docker socket access for `--with-docker`
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
### Interactive mode
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm start -- --file docker-compose.yml
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### npm scripts
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pnpm analyze -- --file docker-compose.yml
|
|
61
|
+
pnpm analyze:health -- --file docker-compose.yml
|
|
62
|
+
pnpm analyze:security -- --file docker-compose.yml
|
|
63
|
+
pnpm analyze:html -- --file docker-compose.yml
|
|
64
|
+
pnpm test
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Direct CLI
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
node -r ts-node/register index.ts -f docker-compose.yml -j -c full
|
|
71
|
+
node -r ts-node/register index.ts -f docker-compose.yml -c images
|
|
72
|
+
node -r ts-node/register index.ts -f docker-compose.yml --with-docker -c full --html
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Commands
|
|
76
|
+
|
|
77
|
+
| Command | Description |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `full` | Complete analysis and report generation (default) |
|
|
80
|
+
| `health` | Health score and issue summary |
|
|
81
|
+
| `images` | Image pinning and tag analysis |
|
|
82
|
+
| `security` | Secrets in env, privileged containers, and exposed DB ports |
|
|
83
|
+
| `reliability` | Healthchecks, `depends_on`, and restart policy analysis |
|
|
84
|
+
| `resources` | CPU and memory limit analysis |
|
|
85
|
+
| `networks` | Default network usage and risky port publishing |
|
|
86
|
+
|
|
87
|
+
## CLI options
|
|
88
|
+
|
|
89
|
+
| Option | Short | Description | Default |
|
|
90
|
+
| --- | --- | --- | --- |
|
|
91
|
+
| `--file` | `-f` | Path to Compose file | `docker-compose.yml` |
|
|
92
|
+
| `--with-docker` | | Enrich the report with Docker daemon runtime status | `false` |
|
|
93
|
+
| `--compare` | | Previous JSON report to diff against | — |
|
|
94
|
+
| `--html` | | Also generate HTML for `full` | `false` |
|
|
95
|
+
| `--command` | `-c` | Command to run | `full` |
|
|
96
|
+
| `--json` | `-j` | Print JSON to stdout | `false` |
|
|
97
|
+
| `--output` | `-o` | Reports directory | `./reports` |
|
|
98
|
+
| `--interactive` | `-i` | Interactive menu | `false` |
|
|
99
|
+
| `--config` | | Path to a config file (non-connection settings only) | auto-search |
|
|
100
|
+
|
|
101
|
+
> This tool intentionally has **no** watch mode and **no** connection/profile handling. It analyzes static files only.
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
Copy `analyzerrc.example.json` to `.analyzerrc.json` if you want a default reports directory:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
cp analyzerrc.example.json .analyzerrc.json
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"output": "./reports"
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Output formats
|
|
120
|
+
|
|
121
|
+
- **JSON** to stdout with `-j`
|
|
122
|
+
- **Markdown** report on `full`
|
|
123
|
+
- **HTML** report on `full --html`
|
|
124
|
+
- **Diff** summary with `--compare previous-report.json`
|
|
125
|
+
|
|
126
|
+
## Health score
|
|
127
|
+
|
|
128
|
+
Health starts at `100` and deductions are applied per issue severity:
|
|
129
|
+
|
|
130
|
+
- `critical`: -15
|
|
131
|
+
- `high`: -8
|
|
132
|
+
- `medium`: -4
|
|
133
|
+
- `low`: -1
|
|
134
|
+
|
|
135
|
+
Interpretation:
|
|
136
|
+
|
|
137
|
+
| Score | Status |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `90–100` | Excellent |
|
|
140
|
+
| `70–89` | Good |
|
|
141
|
+
| `50–69` | Warning |
|
|
142
|
+
| `0–49` | Critical |
|
|
143
|
+
|
|
144
|
+
## Architecture
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
index.ts
|
|
148
|
+
src/cli/{options,runner}.ts
|
|
149
|
+
src/config/loader.ts
|
|
150
|
+
src/collectors/{compose-collector,docker-collector}.ts
|
|
151
|
+
src/analyzers/*.ts
|
|
152
|
+
src/reporters/{report-generator,html-reporter,diff-reporter}.ts
|
|
153
|
+
src/interactive/{index,menus,display}.ts
|
|
154
|
+
src/utils/{format,print}.ts
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Development
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
pnpm install
|
|
161
|
+
pnpm lint
|
|
162
|
+
pnpm build
|
|
163
|
+
pnpm test
|
|
164
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const package_json_1 = __importDefault(require("./package.json"));
|
|
8
|
+
const options_1 = require("./src/cli/options");
|
|
9
|
+
const runner_1 = require("./src/cli/runner");
|
|
10
|
+
const loader_1 = require("./src/config/loader");
|
|
11
|
+
const constants_1 = require("./src/constants");
|
|
12
|
+
const interactive_1 = require("./src/interactive");
|
|
13
|
+
async function main() {
|
|
14
|
+
const options = (0, options_1.parseOptions)();
|
|
15
|
+
if (options.version) {
|
|
16
|
+
console.log(package_json_1.default.version);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const config = (0, loader_1.loadConfig)(options.config);
|
|
20
|
+
const runtimeOptions = {
|
|
21
|
+
...options,
|
|
22
|
+
outputDir: options.outputDir ?? config.output ?? constants_1.DEFAULTS.output,
|
|
23
|
+
};
|
|
24
|
+
if (runtimeOptions.interactive) {
|
|
25
|
+
const cli = new interactive_1.InteractiveCLI(runtimeOptions);
|
|
26
|
+
await cli.start();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await (0, runner_1.executeCommand)(runtimeOptions);
|
|
30
|
+
}
|
|
31
|
+
main().catch((error) => {
|
|
32
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
+
console.error("Error during analysis:", message);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deniscuciuc/compose-analyzer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool that analyzes Docker Compose files for image hygiene, security, reliability, resource limits, and network exposure.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"docker",
|
|
7
|
+
"docker-compose",
|
|
8
|
+
"compose",
|
|
9
|
+
"analyzer",
|
|
10
|
+
"security",
|
|
11
|
+
"devops",
|
|
12
|
+
"cli",
|
|
13
|
+
"containers"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/deniscuciuc/compose-analyzer.git"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/deniscuciuc/compose-analyzer/issues"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Denis Cuciuc <denis@deniscuciuc.dev>",
|
|
24
|
+
"homepage": "https://github.com/deniscuciuc/compose-analyzer#readme",
|
|
25
|
+
"private": false,
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"bin": {
|
|
30
|
+
"compose-analyzer": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"main": "./dist/index.js",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"files": [
|
|
35
|
+
"dist/",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE",
|
|
38
|
+
"CHANGELOG.md",
|
|
39
|
+
"analyzerrc.example.json"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"start": "node -r ts-node/register index.ts start",
|
|
43
|
+
"analyze": "node -r ts-node/register index.ts",
|
|
44
|
+
"analyze:help": "node -r ts-node/register index.ts --help",
|
|
45
|
+
"analyze:health": "node -r ts-node/register index.ts -j -c health",
|
|
46
|
+
"analyze:images": "node -r ts-node/register index.ts -j -c images",
|
|
47
|
+
"analyze:security": "node -r ts-node/register index.ts -j -c security",
|
|
48
|
+
"analyze:reliability": "node -r ts-node/register index.ts -j -c reliability",
|
|
49
|
+
"analyze:resources": "node -r ts-node/register index.ts -j -c resources",
|
|
50
|
+
"analyze:networks": "node -r ts-node/register index.ts -j -c networks",
|
|
51
|
+
"analyze:html": "node -r ts-node/register index.ts --html -c full",
|
|
52
|
+
"build": "tsc",
|
|
53
|
+
"postbuild": "node scripts/add-shebang.js",
|
|
54
|
+
"lint": "biome check .",
|
|
55
|
+
"lint:fix": "biome check --write .",
|
|
56
|
+
"test": "node -r ts-node/register --test test/*.test.ts",
|
|
57
|
+
"prepublishOnly": "pnpm lint && pnpm build && pnpm test",
|
|
58
|
+
"release:patch": "npm version patch && git push && git push --tags",
|
|
59
|
+
"release:minor": "npm version minor && git push && git push --tags",
|
|
60
|
+
"release:major": "npm version major && git push && git push --tags"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@inquirer/prompts": "^8.1.0",
|
|
64
|
+
"js-yaml": "^4.1.0"
|
|
65
|
+
},
|
|
66
|
+
"optionalDependencies": {
|
|
67
|
+
"dockerode": "^4.0.9"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@biomejs/biome": "2.3.11",
|
|
71
|
+
"@types/dockerode": "^3.3.38",
|
|
72
|
+
"@types/js-yaml": "^4.0.9",
|
|
73
|
+
"@types/node": "^22.15.0",
|
|
74
|
+
"ts-node": "^10.9.2",
|
|
75
|
+
"typescript": "^5.9.3"
|
|
76
|
+
},
|
|
77
|
+
"engines": {
|
|
78
|
+
"node": ">=20",
|
|
79
|
+
"pnpm": ">=10"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/image-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE5E,qBAAa,aAAa;IACzB,OAAO,CACN,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC,GACxD,aAAa;CA6DhB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImageAnalyzer = void 0;
|
|
4
|
+
const compose_collector_1 = require("../collectors/compose-collector");
|
|
5
|
+
class ImageAnalyzer {
|
|
6
|
+
analyze(services) {
|
|
7
|
+
const unpinnedImages = [];
|
|
8
|
+
const latestTagImages = [];
|
|
9
|
+
const issues = [];
|
|
10
|
+
for (const { name, service } of services) {
|
|
11
|
+
if (!service.image) {
|
|
12
|
+
if (service.build) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
issues.push({
|
|
16
|
+
service: name,
|
|
17
|
+
severity: "high",
|
|
18
|
+
category: "image",
|
|
19
|
+
title: "No image or build context",
|
|
20
|
+
detail: `Service "${name}" has neither an image nor a build context.`,
|
|
21
|
+
fix: "Add an image reference or a build.context definition.",
|
|
22
|
+
});
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (compose_collector_1.ComposeCollector.hasDigest(service.image)) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const tag = compose_collector_1.ComposeCollector.imageTag(service.image);
|
|
29
|
+
const base = compose_collector_1.ComposeCollector.baseImageName(service.image);
|
|
30
|
+
if (!tag) {
|
|
31
|
+
unpinnedImages.push({
|
|
32
|
+
service: name,
|
|
33
|
+
image: service.image,
|
|
34
|
+
recommendation: `Pin ${service.image} to a stable version such as ${base}:<version>.`,
|
|
35
|
+
});
|
|
36
|
+
issues.push({
|
|
37
|
+
service: name,
|
|
38
|
+
severity: "medium",
|
|
39
|
+
category: "image",
|
|
40
|
+
title: "Image not version-pinned",
|
|
41
|
+
detail: `${service.image} has no explicit tag or digest, which hurts reproducibility.`,
|
|
42
|
+
fix: `Change to image: ${service.image}:<version> or pin by digest.`,
|
|
43
|
+
});
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (tag === "latest") {
|
|
47
|
+
latestTagImages.push({ service: name, image: service.image });
|
|
48
|
+
issues.push({
|
|
49
|
+
service: name,
|
|
50
|
+
severity: "medium",
|
|
51
|
+
category: "image",
|
|
52
|
+
title: "Image uses :latest tag",
|
|
53
|
+
detail: `${service.image} can change between deploys without notice.`,
|
|
54
|
+
fix: `Pin to a specific version tag such as ${base}:<version>.`,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { unpinnedImages, latestTagImages, issues };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.ImageAnalyzer = ImageAnalyzer;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ComposeService, NetworkAnalysis } from "../types";
|
|
2
|
+
export declare class NetworkAnalyzer {
|
|
3
|
+
analyze(services: Array<{
|
|
4
|
+
name: string;
|
|
5
|
+
service: ComposeService;
|
|
6
|
+
}>, fileNetworks: Record<string, unknown> | undefined): NetworkAnalysis;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=network-analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/network-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE9E,qBAAa,eAAe;IAC3B,OAAO,CACN,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC,EAC1D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC/C,eAAe;CAkDlB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NetworkAnalyzer = void 0;
|
|
4
|
+
const compose_collector_1 = require("../collectors/compose-collector");
|
|
5
|
+
class NetworkAnalyzer {
|
|
6
|
+
analyze(services, fileNetworks) {
|
|
7
|
+
const servicesOnDefaultNetwork = [];
|
|
8
|
+
const exposedInternalPorts = [];
|
|
9
|
+
const issues = [];
|
|
10
|
+
const hasTopLevelNetworks = Boolean(fileNetworks && Object.keys(fileNetworks).length > 0);
|
|
11
|
+
for (const { name, service } of services) {
|
|
12
|
+
const hasExplicitNetwork = Array.isArray(service.networks)
|
|
13
|
+
? service.networks.length > 0
|
|
14
|
+
: Boolean(service.networks && Object.keys(service.networks).length > 0);
|
|
15
|
+
if (!hasExplicitNetwork && hasTopLevelNetworks) {
|
|
16
|
+
servicesOnDefaultNetwork.push({ service: name });
|
|
17
|
+
issues.push({
|
|
18
|
+
service: name,
|
|
19
|
+
severity: "low",
|
|
20
|
+
category: "network",
|
|
21
|
+
title: "Service uses default network",
|
|
22
|
+
detail: `Service "${name}" is not attached to an explicit top-level network.`,
|
|
23
|
+
fix: "Assign the service to a named network so network intent is explicit.",
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
for (const port of compose_collector_1.ComposeCollector.normalizePorts(service)) {
|
|
27
|
+
if (!port.randomHostBinding) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
exposedInternalPorts.push({
|
|
31
|
+
service: name,
|
|
32
|
+
port: port.raw,
|
|
33
|
+
note: "Published without an explicit host port or host IP binding.",
|
|
34
|
+
});
|
|
35
|
+
issues.push({
|
|
36
|
+
service: name,
|
|
37
|
+
severity: "low",
|
|
38
|
+
category: "network",
|
|
39
|
+
title: "Published port uses random host binding",
|
|
40
|
+
detail: `Service "${name}" publishes ${port.raw}, which lets Docker choose a host port on all interfaces.`,
|
|
41
|
+
fix: "Prefer explicit mappings such as 127.0.0.1:8080:80, or remove the published port for internal-only services.",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { servicesOnDefaultNetwork, exposedInternalPorts, issues };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.NetworkAnalyzer = NetworkAnalyzer;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ComposeService, ReliabilityAnalysis } from "../types";
|
|
2
|
+
export declare class ReliabilityAnalyzer {
|
|
3
|
+
analyze(services: Array<{
|
|
4
|
+
name: string;
|
|
5
|
+
service: ComposeService;
|
|
6
|
+
}>): ReliabilityAnalysis;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=reliability-analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reliability-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/reliability-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEX,cAAc,EACd,mBAAmB,EACnB,MAAM,UAAU,CAAC;AAElB,qBAAa,mBAAmB;IAC/B,OAAO,CACN,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC,GACxD,mBAAmB;CAyFtB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReliabilityAnalyzer = void 0;
|
|
4
|
+
const constants_1 = require("../constants");
|
|
5
|
+
class ReliabilityAnalyzer {
|
|
6
|
+
analyze(services) {
|
|
7
|
+
const missingHealthcheck = [];
|
|
8
|
+
const unsafeDepends = [];
|
|
9
|
+
const missingRestart = [];
|
|
10
|
+
const insecureRestartPolicy = [];
|
|
11
|
+
const issues = [];
|
|
12
|
+
const servicesWithHealthcheck = new Set(services
|
|
13
|
+
.filter(({ service }) => service.healthcheck && !service.healthcheck.disable)
|
|
14
|
+
.map(({ name }) => name));
|
|
15
|
+
for (const { name, service } of services) {
|
|
16
|
+
if (!service.healthcheck || service.healthcheck.disable) {
|
|
17
|
+
missingHealthcheck.push({ service: name });
|
|
18
|
+
issues.push({
|
|
19
|
+
service: name,
|
|
20
|
+
severity: "medium",
|
|
21
|
+
category: "reliability",
|
|
22
|
+
title: "No healthcheck defined",
|
|
23
|
+
detail: `Service "${name}" does not define a usable healthcheck.`,
|
|
24
|
+
fix: "Add healthcheck.test, interval, timeout, and retries so readiness can be verified.",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (service.depends_on) {
|
|
28
|
+
const dependencies = Array.isArray(service.depends_on)
|
|
29
|
+
? service.depends_on.map((dependency) => ({
|
|
30
|
+
dependency,
|
|
31
|
+
condition: "service_started",
|
|
32
|
+
}))
|
|
33
|
+
: Object.entries(service.depends_on).map(([dependency, details]) => ({
|
|
34
|
+
dependency,
|
|
35
|
+
condition: details.condition ?? "service_started",
|
|
36
|
+
}));
|
|
37
|
+
for (const { dependency, condition } of dependencies) {
|
|
38
|
+
if (condition !== "service_healthy" &&
|
|
39
|
+
servicesWithHealthcheck.has(dependency)) {
|
|
40
|
+
unsafeDepends.push({ service: name, dependency, condition });
|
|
41
|
+
issues.push({
|
|
42
|
+
service: name,
|
|
43
|
+
severity: "high",
|
|
44
|
+
category: "reliability",
|
|
45
|
+
title: `depends_on "${dependency}" uses ${condition}`,
|
|
46
|
+
detail: `Service "${name}" may start before "${dependency}" is actually ready.`,
|
|
47
|
+
fix: `Use depends_on.${dependency}.condition: service_healthy.`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!service.restart) {
|
|
53
|
+
missingRestart.push({ service: name });
|
|
54
|
+
issues.push({
|
|
55
|
+
service: name,
|
|
56
|
+
severity: "low",
|
|
57
|
+
category: "reliability",
|
|
58
|
+
title: "No restart policy",
|
|
59
|
+
detail: `Service "${name}" has no restart policy and will not recover automatically after a crash.`,
|
|
60
|
+
fix: "Set restart: unless-stopped or restart: on-failure.",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else if (constants_1.WEAK_RESTART_POLICIES.has(service.restart)) {
|
|
64
|
+
insecureRestartPolicy.push({ service: name, policy: service.restart });
|
|
65
|
+
issues.push({
|
|
66
|
+
service: name,
|
|
67
|
+
severity: "low",
|
|
68
|
+
category: "reliability",
|
|
69
|
+
title: `Weak restart policy: ${service.restart}`,
|
|
70
|
+
detail: `Service "${name}" uses restart: ${service.restart}, which provides poor crash recovery.`,
|
|
71
|
+
fix: "Use restart: unless-stopped or restart: on-failure.",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
missingHealthcheck,
|
|
77
|
+
unsafeDepends,
|
|
78
|
+
missingRestart,
|
|
79
|
+
insecureRestartPolicy,
|
|
80
|
+
issues,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.ReliabilityAnalyzer = ReliabilityAnalyzer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/resource-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,cAAc,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE/E,qBAAa,gBAAgB;IAC5B,OAAO,CACN,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC,GACxD,gBAAgB;CAmCnB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResourceAnalyzer = void 0;
|
|
4
|
+
class ResourceAnalyzer {
|
|
5
|
+
analyze(services) {
|
|
6
|
+
const missingLimits = [];
|
|
7
|
+
const issues = [];
|
|
8
|
+
for (const { name, service } of services) {
|
|
9
|
+
const cpuLimit = service.deploy?.resources?.limits?.cpus ?? service.cpus;
|
|
10
|
+
const memoryLimit = service.deploy?.resources?.limits?.memory ?? service.mem_limit;
|
|
11
|
+
const missing = [];
|
|
12
|
+
if (cpuLimit === undefined || cpuLimit === "") {
|
|
13
|
+
missing.push("cpu");
|
|
14
|
+
}
|
|
15
|
+
if (!memoryLimit) {
|
|
16
|
+
missing.push("memory");
|
|
17
|
+
}
|
|
18
|
+
if (missing.length === 0) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
missingLimits.push({ service: name, missing });
|
|
22
|
+
issues.push({
|
|
23
|
+
service: name,
|
|
24
|
+
severity: "medium",
|
|
25
|
+
category: "resources",
|
|
26
|
+
title: "Missing resource limits",
|
|
27
|
+
detail: `Service "${name}" is missing ${missing.join(" and ")} limits. Unbounded containers can starve neighboring workloads.`,
|
|
28
|
+
fix: "Add deploy.resources.limits for both memory and cpus, or set mem_limit and cpus in legacy Compose syntax.",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return { missingLimits, issues };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.ResourceAnalyzer = ResourceAnalyzer;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ComposeService, SecurityAnalysis } from "../types";
|
|
2
|
+
export declare class SecurityAnalyzer {
|
|
3
|
+
analyze(services: Array<{
|
|
4
|
+
name: string;
|
|
5
|
+
service: ComposeService;
|
|
6
|
+
}>): SecurityAnalysis;
|
|
7
|
+
private isInterpolatedValue;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=security-analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/security-analyzer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAgB,cAAc,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE/E,qBAAa,gBAAgB;IAC5B,OAAO,CACN,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC,GACxD,gBAAgB;IAoFnB,OAAO,CAAC,mBAAmB;CAG3B"}
|