@infograb/docker-slim-advisor 0.1.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/LICENSE +21 -0
- package/README.md +274 -0
- package/build/base-image-db-loader.d.ts +16 -0
- package/build/base-image-db-loader.js +72 -0
- package/build/base-image-db-loader.js.map +1 -0
- package/build/cli.d.ts +47 -0
- package/build/cli.js +142 -0
- package/build/cli.js.map +1 -0
- package/build/data/base-image-db-schema.d.ts +117 -0
- package/build/data/base-image-db-schema.js +108 -0
- package/build/data/base-image-db-schema.js.map +1 -0
- package/build/data/base-image-db.d.ts +21 -0
- package/build/data/base-image-db.js +190 -0
- package/build/data/base-image-db.js.map +1 -0
- package/build/exit-codes.d.ts +25 -0
- package/build/exit-codes.js +31 -0
- package/build/exit-codes.js.map +1 -0
- package/build/formatters/formatter-dispatcher.d.ts +15 -0
- package/build/formatters/formatter-dispatcher.js +27 -0
- package/build/formatters/formatter-dispatcher.js.map +1 -0
- package/build/formatters/json-formatter.d.ts +10 -0
- package/build/formatters/json-formatter.js +52 -0
- package/build/formatters/json-formatter.js.map +1 -0
- package/build/formatters/json-schema.d.ts +57 -0
- package/build/formatters/json-schema.js +13 -0
- package/build/formatters/json-schema.js.map +1 -0
- package/build/formatters/markdown-formatter.d.ts +12 -0
- package/build/formatters/markdown-formatter.js +97 -0
- package/build/formatters/markdown-formatter.js.map +1 -0
- package/build/formatters/terminal-formatter.d.ts +12 -0
- package/build/formatters/terminal-formatter.js +142 -0
- package/build/formatters/terminal-formatter.js.map +1 -0
- package/build/image-size-lookup.d.ts +35 -0
- package/build/image-size-lookup.js +187 -0
- package/build/image-size-lookup.js.map +1 -0
- package/build/multi-stage-detector.d.ts +29 -0
- package/build/multi-stage-detector.js +29 -0
- package/build/multi-stage-detector.js.map +1 -0
- package/build/output.d.ts +46 -0
- package/build/output.js +62 -0
- package/build/output.js.map +1 -0
- package/build/parser.d.ts +7 -0
- package/build/parser.js +123 -0
- package/build/parser.js.map +1 -0
- package/build/rules/alpine-swap.d.ts +10 -0
- package/build/rules/alpine-swap.js +81 -0
- package/build/rules/alpine-swap.js.map +1 -0
- package/build/rules/dockerignore-missing.d.ts +19 -0
- package/build/rules/dockerignore-missing.js +107 -0
- package/build/rules/dockerignore-missing.js.map +1 -0
- package/build/rules/index.d.ts +11 -0
- package/build/rules/index.js +22 -0
- package/build/rules/index.js.map +1 -0
- package/build/rules/package-cache-cleanup.d.ts +15 -0
- package/build/rules/package-cache-cleanup.js +89 -0
- package/build/rules/package-cache-cleanup.js.map +1 -0
- package/build/rules/run-merge.d.ts +12 -0
- package/build/rules/run-merge.js +67 -0
- package/build/rules/run-merge.js.map +1 -0
- package/build/rules/unnecessary-packages.d.ts +23 -0
- package/build/rules/unnecessary-packages.js +184 -0
- package/build/rules/unnecessary-packages.js.map +1 -0
- package/build/severity-filter.d.ts +22 -0
- package/build/severity-filter.js +31 -0
- package/build/severity-filter.js.map +1 -0
- package/build/size-estimator.d.ts +35 -0
- package/build/size-estimator.js +238 -0
- package/build/size-estimator.js.map +1 -0
- package/build/tty-detection.d.ts +20 -0
- package/build/tty-detection.js +33 -0
- package/build/tty-detection.js.map +1 -0
- package/build/types.d.ts +82 -0
- package/build/types.js +6 -0
- package/build/types.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package Cache Cleanup Rule (DSA003)
|
|
3
|
+
*
|
|
4
|
+
* Detects missing cache removal commands in package install layers.
|
|
5
|
+
* Checks for:
|
|
6
|
+
* - apt-get install without `rm -rf /var/lib/apt/lists/*`
|
|
7
|
+
* - apk add without `--no-cache` flag
|
|
8
|
+
*
|
|
9
|
+
* Package manager caches left in a layer bloat the final image by 20-200MB.
|
|
10
|
+
*
|
|
11
|
+
* Severity: HIGH — cache cleanup is a fundamental Docker best practice.
|
|
12
|
+
* Estimated savings: ~40MB for apt, ~10MB for apk.
|
|
13
|
+
*/
|
|
14
|
+
/** Estimated cache size left behind by apt-get (~40MB) */
|
|
15
|
+
const APT_CACHE_BYTES = 40 * 1024 * 1024;
|
|
16
|
+
/** Estimated cache size left behind by apk (~10MB) */
|
|
17
|
+
const APK_CACHE_BYTES = 10 * 1024 * 1024;
|
|
18
|
+
/** Check if command contains apt-get install */
|
|
19
|
+
function hasAptInstall(command) {
|
|
20
|
+
return /apt-get\s+install/i.test(command);
|
|
21
|
+
}
|
|
22
|
+
/** Check if command removes apt cache */
|
|
23
|
+
function hasAptCacheCleanup(command) {
|
|
24
|
+
// rm -rf /var/lib/apt/lists/* OR apt-get clean
|
|
25
|
+
return (/rm\s+-rf?\s+\/var\/lib\/apt\/lists/i.test(command) ||
|
|
26
|
+
/apt-get\s+clean/i.test(command));
|
|
27
|
+
}
|
|
28
|
+
/** Check if command contains apk add */
|
|
29
|
+
function hasApkAdd(command) {
|
|
30
|
+
return /apk\s+(--\S+\s+)*add/i.test(command);
|
|
31
|
+
}
|
|
32
|
+
/** Check if apk add uses --no-cache flag */
|
|
33
|
+
function hasApkNoCache(command) {
|
|
34
|
+
return /apk\s+(--\S+\s+)*add\s+(--\S+\s+)*--no-cache/i.test(command) ||
|
|
35
|
+
/apk\s+--no-cache\s+(--\S+\s+)*add/i.test(command);
|
|
36
|
+
}
|
|
37
|
+
/** Build fix suggestion for apt-get install */
|
|
38
|
+
function buildAptFix(run) {
|
|
39
|
+
const cmd = run.command.trim();
|
|
40
|
+
return `RUN ${cmd} && \\\n rm -rf /var/lib/apt/lists/*`;
|
|
41
|
+
}
|
|
42
|
+
/** Build fix suggestion for apk add (inject --no-cache) */
|
|
43
|
+
function buildApkFix(run) {
|
|
44
|
+
const cmd = run.command.trim();
|
|
45
|
+
// Replace `apk [flags] add` with `apk add --no-cache`, removing intermediate flags
|
|
46
|
+
const fixed = cmd.replace(/apk\s+(--\S+\s+)*add/i, 'apk add --no-cache');
|
|
47
|
+
return `RUN ${fixed}`;
|
|
48
|
+
}
|
|
49
|
+
export const packageCacheCleanupRule = {
|
|
50
|
+
id: 'DSA003',
|
|
51
|
+
name: 'package-cache-cleanup',
|
|
52
|
+
description: 'Detects missing cache removal commands in package install layers',
|
|
53
|
+
evaluate(instructions, _context) {
|
|
54
|
+
const recommendations = [];
|
|
55
|
+
const runInstructions = instructions.filter((i) => i.type === 'RUN');
|
|
56
|
+
for (const run of runInstructions) {
|
|
57
|
+
const cmd = run.command;
|
|
58
|
+
// Check apt-get install without cache cleanup
|
|
59
|
+
if (hasAptInstall(cmd) && !hasAptCacheCleanup(cmd)) {
|
|
60
|
+
recommendations.push({
|
|
61
|
+
ruleId: 'DSA003',
|
|
62
|
+
severity: 'HIGH',
|
|
63
|
+
line: run.line,
|
|
64
|
+
title: 'apt-get install without cache cleanup',
|
|
65
|
+
description: 'apt-get install leaves package lists in /var/lib/apt/lists/ ' +
|
|
66
|
+
'adding ~40MB to the layer. Add `rm -rf /var/lib/apt/lists/*` ' +
|
|
67
|
+
'or `apt-get clean` in the same RUN to remove them.',
|
|
68
|
+
fix: buildAptFix(run),
|
|
69
|
+
estimatedSavingsBytes: APT_CACHE_BYTES,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Check apk add without --no-cache
|
|
73
|
+
if (hasApkAdd(cmd) && !hasApkNoCache(cmd)) {
|
|
74
|
+
recommendations.push({
|
|
75
|
+
ruleId: 'DSA003',
|
|
76
|
+
severity: 'HIGH',
|
|
77
|
+
line: run.line,
|
|
78
|
+
title: 'apk add without --no-cache',
|
|
79
|
+
description: 'apk add without --no-cache leaves an index cache in /var/cache/apk/ ' +
|
|
80
|
+
'adding ~10MB to the layer. Use `apk add --no-cache` to skip caching.',
|
|
81
|
+
fix: buildApkFix(run),
|
|
82
|
+
estimatedSavingsBytes: APK_CACHE_BYTES,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return recommendations;
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=package-cache-cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-cache-cleanup.js","sourceRoot":"","sources":["../../src/rules/package-cache-cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,0DAA0D;AAC1D,MAAM,eAAe,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEzC,sDAAsD;AACtD,MAAM,eAAe,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEzC,gDAAgD;AAChD,SAAS,aAAa,CAAC,OAAe;IACpC,OAAO,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC5C,CAAC;AAED,yCAAyC;AACzC,SAAS,kBAAkB,CAAC,OAAe;IACzC,iDAAiD;IACjD,OAAO,CACL,qCAAqC,CAAC,IAAI,CAAC,OAAO,CAAC;QACnD,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CACjC,CAAC;AACJ,CAAC;AAED,wCAAwC;AACxC,SAAS,SAAS,CAAC,OAAe;IAChC,OAAO,uBAAuB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED,4CAA4C;AAC5C,SAAS,aAAa,CAAC,OAAe;IACpC,OAAO,+CAA+C,CAAC,IAAI,CAAC,OAAO,CAAC;QAClE,oCAAoC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACvD,CAAC;AAED,+CAA+C;AAC/C,SAAS,WAAW,CAAC,GAAmB;IACtC,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC/B,OAAO,OAAO,GAAG,yCAAyC,CAAC;AAC7D,CAAC;AAED,2DAA2D;AAC3D,SAAS,WAAW,CAAC,GAAmB;IACtC,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC/B,mFAAmF;IACnF,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,uBAAuB,EAAE,oBAAoB,CAAC,CAAC;IACzE,OAAO,OAAO,KAAK,EAAE,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAS;IAC3C,EAAE,EAAE,QAAQ;IACZ,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EAAE,kEAAkE;IAE/E,QAAQ,CAAC,YAA2B,EAAE,QAAqB;QACzD,MAAM,eAAe,GAAqB,EAAE,CAAC;QAE7C,MAAM,eAAe,GAAG,YAAY,CAAC,MAAM,CACzC,CAAC,CAAC,EAAuB,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAC7C,CAAC;QAEF,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC;YAExB,8CAA8C;YAC9C,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;gBACnD,eAAe,CAAC,IAAI,CAAC;oBACnB,MAAM,EAAE,QAAQ;oBAChB,QAAQ,EAAE,MAAM;oBAChB,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,KAAK,EAAE,uCAAuC;oBAC9C,WAAW,EACT,8DAA8D;wBAC9D,+DAA+D;wBAC/D,oDAAoD;oBACtD,GAAG,EAAE,WAAW,CAAC,GAAG,CAAC;oBACrB,qBAAqB,EAAE,eAAe;iBACvC,CAAC,CAAC;YACL,CAAC;YAED,mCAAmC;YACnC,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC1C,eAAe,CAAC,IAAI,CAAC;oBACnB,MAAM,EAAE,QAAQ;oBAChB,QAAQ,EAAE,MAAM;oBAChB,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,KAAK,EAAE,4BAA4B;oBACnC,WAAW,EACT,sEAAsE;wBACtE,sEAAsE;oBACxE,GAAG,EAAE,WAAW,CAAC,GAAG,CAAC;oBACrB,qBAAqB,EAAE,eAAe;iBACvC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,eAAe,CAAC;IACzB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RUN Instruction Merge Rule (DSA002)
|
|
3
|
+
*
|
|
4
|
+
* Detects consecutive RUN commands and suggests combining them with && to
|
|
5
|
+
* reduce image layers. Each RUN creates a new layer in the Docker image;
|
|
6
|
+
* merging consecutive RUNs reduces layer count and total image size.
|
|
7
|
+
*
|
|
8
|
+
* Severity: HIGH when 4+ consecutive RUNs, MEDIUM for 2-3 consecutive RUNs.
|
|
9
|
+
* Estimated savings: ~5MB per eliminated layer (filesystem overhead).
|
|
10
|
+
*/
|
|
11
|
+
import type { Rule } from '../types.js';
|
|
12
|
+
export declare const runMergeRule: Rule;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RUN Instruction Merge Rule (DSA002)
|
|
3
|
+
*
|
|
4
|
+
* Detects consecutive RUN commands and suggests combining them with && to
|
|
5
|
+
* reduce image layers. Each RUN creates a new layer in the Docker image;
|
|
6
|
+
* merging consecutive RUNs reduces layer count and total image size.
|
|
7
|
+
*
|
|
8
|
+
* Severity: HIGH when 4+ consecutive RUNs, MEDIUM for 2-3 consecutive RUNs.
|
|
9
|
+
* Estimated savings: ~5MB per eliminated layer (filesystem overhead).
|
|
10
|
+
*/
|
|
11
|
+
/** Estimated filesystem overhead per layer in bytes (~5MB) */
|
|
12
|
+
const LAYER_OVERHEAD_BYTES = 5 * 1024 * 1024;
|
|
13
|
+
/** Group consecutive RUN instructions into clusters */
|
|
14
|
+
function findConsecutiveRunGroups(instructions) {
|
|
15
|
+
const groups = [];
|
|
16
|
+
let currentGroup = [];
|
|
17
|
+
for (const instr of instructions) {
|
|
18
|
+
if (instr.type === 'RUN') {
|
|
19
|
+
currentGroup.push(instr);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
if (currentGroup.length >= 2) {
|
|
23
|
+
groups.push(currentGroup);
|
|
24
|
+
}
|
|
25
|
+
currentGroup = [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Flush trailing group
|
|
29
|
+
if (currentGroup.length >= 2) {
|
|
30
|
+
groups.push(currentGroup);
|
|
31
|
+
}
|
|
32
|
+
return groups;
|
|
33
|
+
}
|
|
34
|
+
/** Build a merged RUN command from multiple RUN instructions */
|
|
35
|
+
function buildMergedCommand(runs) {
|
|
36
|
+
const commands = runs.map(r => r.command.trim());
|
|
37
|
+
const joined = commands.join(' && \\\n ');
|
|
38
|
+
return `RUN ${joined}`;
|
|
39
|
+
}
|
|
40
|
+
export const runMergeRule = {
|
|
41
|
+
id: 'DSA002',
|
|
42
|
+
name: 'run-merge',
|
|
43
|
+
description: 'Detects consecutive RUN commands and suggests combining them to reduce image layers',
|
|
44
|
+
evaluate(instructions, _context) {
|
|
45
|
+
const groups = findConsecutiveRunGroups(instructions);
|
|
46
|
+
const recommendations = [];
|
|
47
|
+
for (const group of groups) {
|
|
48
|
+
const eliminatedLayers = group.length - 1;
|
|
49
|
+
const estimatedSavings = eliminatedLayers * LAYER_OVERHEAD_BYTES;
|
|
50
|
+
const severity = group.length >= 4 ? 'HIGH' : 'MEDIUM';
|
|
51
|
+
const lineRange = `${group[0].line}-${group[group.length - 1].line}`;
|
|
52
|
+
recommendations.push({
|
|
53
|
+
ruleId: 'DSA002',
|
|
54
|
+
severity,
|
|
55
|
+
line: group[0].line,
|
|
56
|
+
title: `Merge ${group.length} consecutive RUN instructions (lines ${lineRange})`,
|
|
57
|
+
description: `${group.length} consecutive RUN commands create ${group.length} separate layers. ` +
|
|
58
|
+
`Combining them into a single RUN with && eliminates ${eliminatedLayers} layer(s), ` +
|
|
59
|
+
`saving ~${Math.round(estimatedSavings / (1024 * 1024))}MB in filesystem overhead.`,
|
|
60
|
+
fix: buildMergedCommand(group),
|
|
61
|
+
estimatedSavingsBytes: estimatedSavings,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return recommendations;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=run-merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-merge.js","sourceRoot":"","sources":["../../src/rules/run-merge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,8DAA8D;AAC9D,MAAM,oBAAoB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAE7C,uDAAuD;AACvD,SAAS,wBAAwB,CAAC,YAA2B;IAC3D,MAAM,MAAM,GAAuB,EAAE,CAAC;IACtC,IAAI,YAAY,GAAqB,EAAE,CAAC;IAExC,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACzB,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,IAAI,YAAY,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;gBAC7B,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC5B,CAAC;YACD,YAAY,GAAG,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;IAED,uBAAuB;IACvB,IAAI,YAAY,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gEAAgE;AAChE,SAAS,kBAAkB,CAAC,IAAsB;IAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7C,OAAO,OAAO,MAAM,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAS;IAChC,EAAE,EAAE,QAAQ;IACZ,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,qFAAqF;IAElG,QAAQ,CAAC,YAA2B,EAAE,QAAqB;QACzD,MAAM,MAAM,GAAG,wBAAwB,CAAC,YAAY,CAAC,CAAC;QACtD,MAAM,eAAe,GAAqB,EAAE,CAAC;QAE7C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,gBAAgB,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;YAC1C,MAAM,gBAAgB,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;YACjE,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;YACvD,MAAM,SAAS,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAErE,eAAe,CAAC,IAAI,CAAC;gBACnB,MAAM,EAAE,QAAQ;gBAChB,QAAQ;gBACR,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI;gBACnB,KAAK,EAAE,SAAS,KAAK,CAAC,MAAM,wCAAwC,SAAS,GAAG;gBAChF,WAAW,EACT,GAAG,KAAK,CAAC,MAAM,oCAAoC,KAAK,CAAC,MAAM,oBAAoB;oBACnF,uDAAuD,gBAAgB,aAAa;oBACpF,WAAW,IAAI,CAAC,KAAK,CAAC,gBAAgB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,4BAA4B;gBACrF,GAAG,EAAE,kBAAkB,CAAC,KAAK,CAAC;gBAC9B,qBAAqB,EAAE,gBAAgB;aACxC,CAAC,CAAC;QACL,CAAC;QAED,OAAO,eAAe,CAAC;IACzB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unnecessary Packages Rule (DSA005)
|
|
3
|
+
*
|
|
4
|
+
* Detects common dev-only or unnecessary packages left in production images.
|
|
5
|
+
* Categories:
|
|
6
|
+
* - Build tools: build-essential, gcc, g++, make, cmake, etc.
|
|
7
|
+
* - Editors: vim, nano, emacs, vi
|
|
8
|
+
* - Debug/network tools: curl, wget, telnet, netcat, strace, gdb
|
|
9
|
+
* - Documentation: man, man-db, manpages
|
|
10
|
+
* - Version control: git, subversion
|
|
11
|
+
*
|
|
12
|
+
* These packages inflate image size and increase attack surface.
|
|
13
|
+
*
|
|
14
|
+
* Severity: MEDIUM — not as critical as cache cleanup but still impactful.
|
|
15
|
+
* Estimated savings: ~5MB per flagged package (conservative heuristic).
|
|
16
|
+
*/
|
|
17
|
+
import type { Rule } from '../types.js';
|
|
18
|
+
/**
|
|
19
|
+
* Extract installed package names from a RUN command.
|
|
20
|
+
* Handles apt-get install, apk add, yum install, dnf install.
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractInstalledPackages(command: string): string[];
|
|
23
|
+
export declare const unnecessaryPackagesRule: Rule;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unnecessary Packages Rule (DSA005)
|
|
3
|
+
*
|
|
4
|
+
* Detects common dev-only or unnecessary packages left in production images.
|
|
5
|
+
* Categories:
|
|
6
|
+
* - Build tools: build-essential, gcc, g++, make, cmake, etc.
|
|
7
|
+
* - Editors: vim, nano, emacs, vi
|
|
8
|
+
* - Debug/network tools: curl, wget, telnet, netcat, strace, gdb
|
|
9
|
+
* - Documentation: man, man-db, manpages
|
|
10
|
+
* - Version control: git, subversion
|
|
11
|
+
*
|
|
12
|
+
* These packages inflate image size and increase attack surface.
|
|
13
|
+
*
|
|
14
|
+
* Severity: MEDIUM — not as critical as cache cleanup but still impactful.
|
|
15
|
+
* Estimated savings: ~5MB per flagged package (conservative heuristic).
|
|
16
|
+
*/
|
|
17
|
+
/** Estimated bytes per unnecessary package (~5MB average) */
|
|
18
|
+
const BYTES_PER_PACKAGE = 5 * 1024 * 1024;
|
|
19
|
+
const PACKAGE_CATEGORIES = [
|
|
20
|
+
{
|
|
21
|
+
label: 'build tools',
|
|
22
|
+
packages: [
|
|
23
|
+
'build-essential', 'gcc', 'g++', 'make', 'cmake', 'autoconf',
|
|
24
|
+
'automake', 'libtool', 'pkg-config', 'cpp', 'dpkg-dev',
|
|
25
|
+
],
|
|
26
|
+
savingsMultiplier: 3, // build tools are large (~15MB each)
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: 'editors',
|
|
30
|
+
packages: ['vim', 'vim-tiny', 'nano', 'emacs', 'emacs-nox', 'ed'],
|
|
31
|
+
savingsMultiplier: 1,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: 'debug/network utilities',
|
|
35
|
+
packages: [
|
|
36
|
+
'curl', 'wget', 'telnet', 'netcat', 'netcat-openbsd', 'net-tools',
|
|
37
|
+
'strace', 'ltrace', 'gdb', 'tcpdump', 'iputils-ping', 'dnsutils',
|
|
38
|
+
'traceroute', 'nmap', 'socat', 'iproute2',
|
|
39
|
+
],
|
|
40
|
+
savingsMultiplier: 1,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
label: 'documentation',
|
|
44
|
+
packages: ['man', 'man-db', 'manpages', 'manpages-dev', 'info'],
|
|
45
|
+
savingsMultiplier: 1,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
label: 'version control',
|
|
49
|
+
packages: ['git', 'subversion', 'mercurial', 'bzr'],
|
|
50
|
+
savingsMultiplier: 2, // git is ~10MB
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
label: 'dev libraries',
|
|
54
|
+
packages: [
|
|
55
|
+
'libssl-dev', 'libffi-dev', 'zlib1g-dev', 'libreadline-dev',
|
|
56
|
+
'libyaml-dev', 'libxml2-dev', 'libxslt1-dev', 'python3-dev',
|
|
57
|
+
'python3-pip', 'ruby-dev', 'default-jdk',
|
|
58
|
+
],
|
|
59
|
+
savingsMultiplier: 2,
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
/** Build a lookup map: package name -> category for fast detection */
|
|
63
|
+
function buildPackageLookup() {
|
|
64
|
+
const lookup = new Map();
|
|
65
|
+
for (const cat of PACKAGE_CATEGORIES) {
|
|
66
|
+
for (const pkg of cat.packages) {
|
|
67
|
+
lookup.set(pkg, cat);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return lookup;
|
|
71
|
+
}
|
|
72
|
+
const PACKAGE_LOOKUP = buildPackageLookup();
|
|
73
|
+
/** Regex to match apt-get install or apk add commands */
|
|
74
|
+
const APT_INSTALL_RE = /apt-get\s+(?:-\S+\s+)*install\s+(?:-\S+\s+)*/i;
|
|
75
|
+
const APK_ADD_RE = /apk\s+(?:--\S+\s+)*add\s+(?:--\S+\s+)*/i;
|
|
76
|
+
const YUM_INSTALL_RE = /yum\s+(?:-\S+\s+)*install\s+(?:-\S+\s+)*/i;
|
|
77
|
+
const DNF_INSTALL_RE = /dnf\s+(?:-\S+\s+)*install\s+(?:-\S+\s+)*/i;
|
|
78
|
+
/** Check if a RUN command removes the package later (e.g., apt-get remove/purge) */
|
|
79
|
+
function hasPackageRemoval(command, pkg) {
|
|
80
|
+
// apt-get remove/purge, apk del, yum/dnf remove/erase
|
|
81
|
+
const removePatterns = [
|
|
82
|
+
new RegExp(`apt-get\\s+(?:remove|purge)\\s+[^&]*\\b${escapeRegex(pkg)}\\b`, 'i'),
|
|
83
|
+
new RegExp(`apk\\s+del\\s+[^&]*\\b${escapeRegex(pkg)}\\b`, 'i'),
|
|
84
|
+
new RegExp(`(?:yum|dnf)\\s+(?:remove|erase)\\s+[^&]*\\b${escapeRegex(pkg)}\\b`, 'i'),
|
|
85
|
+
];
|
|
86
|
+
return removePatterns.some(re => re.test(command));
|
|
87
|
+
}
|
|
88
|
+
/** Escape regex special characters */
|
|
89
|
+
function escapeRegex(str) {
|
|
90
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Extract installed package names from a RUN command.
|
|
94
|
+
* Handles apt-get install, apk add, yum install, dnf install.
|
|
95
|
+
*/
|
|
96
|
+
export function extractInstalledPackages(command) {
|
|
97
|
+
const packages = [];
|
|
98
|
+
const matchers = [APT_INSTALL_RE, APK_ADD_RE, YUM_INSTALL_RE, DNF_INSTALL_RE];
|
|
99
|
+
for (const matcher of matchers) {
|
|
100
|
+
const match = command.match(matcher);
|
|
101
|
+
if (!match)
|
|
102
|
+
continue;
|
|
103
|
+
// Get text after the install command until next && or end
|
|
104
|
+
const afterInstall = command.slice(match.index + match[0].length);
|
|
105
|
+
// Split on && to isolate just the install portion
|
|
106
|
+
const installPortion = afterInstall.split(/&&/)[0];
|
|
107
|
+
// Extract tokens that look like package names (not flags)
|
|
108
|
+
const tokens = installPortion.split(/\s+/).filter(t => t.length > 0 &&
|
|
109
|
+
!t.startsWith('-') &&
|
|
110
|
+
!t.startsWith('/') &&
|
|
111
|
+
!t.startsWith('$') &&
|
|
112
|
+
!t.includes('=') &&
|
|
113
|
+
t !== '\\' &&
|
|
114
|
+
!/^[|;>&]/.test(t));
|
|
115
|
+
packages.push(...tokens);
|
|
116
|
+
}
|
|
117
|
+
return packages;
|
|
118
|
+
}
|
|
119
|
+
export const unnecessaryPackagesRule = {
|
|
120
|
+
id: 'DSA005',
|
|
121
|
+
name: 'unnecessary-packages',
|
|
122
|
+
description: 'Detects dev-only or unnecessary packages left in production images',
|
|
123
|
+
evaluate(instructions, _context) {
|
|
124
|
+
const recommendations = [];
|
|
125
|
+
const runInstructions = instructions.filter((i) => i.type === 'RUN');
|
|
126
|
+
// Collect all commands to check for later removal across RUN layers
|
|
127
|
+
const allCommands = runInstructions.map(r => r.command).join(' && ');
|
|
128
|
+
for (const run of runInstructions) {
|
|
129
|
+
const installed = extractInstalledPackages(run.command);
|
|
130
|
+
/** Group detected packages by category for a single recommendation per category */
|
|
131
|
+
const detectedByCategory = new Map();
|
|
132
|
+
for (const pkg of installed) {
|
|
133
|
+
const category = PACKAGE_LOOKUP.get(pkg);
|
|
134
|
+
if (!category)
|
|
135
|
+
continue;
|
|
136
|
+
// Skip if the package is removed later in the same or another RUN
|
|
137
|
+
if (hasPackageRemoval(allCommands, pkg))
|
|
138
|
+
continue;
|
|
139
|
+
const key = category.label;
|
|
140
|
+
if (!detectedByCategory.has(key)) {
|
|
141
|
+
detectedByCategory.set(key, { category, packages: [] });
|
|
142
|
+
}
|
|
143
|
+
detectedByCategory.get(key).packages.push(pkg);
|
|
144
|
+
}
|
|
145
|
+
// Emit one recommendation per category per RUN instruction
|
|
146
|
+
for (const [, { category, packages: pkgs }] of detectedByCategory) {
|
|
147
|
+
const pkgList = pkgs.join(', ');
|
|
148
|
+
const savings = pkgs.length * BYTES_PER_PACKAGE * category.savingsMultiplier;
|
|
149
|
+
recommendations.push({
|
|
150
|
+
ruleId: 'DSA005',
|
|
151
|
+
severity: 'MEDIUM',
|
|
152
|
+
line: run.line,
|
|
153
|
+
title: `Unnecessary ${category.label} in production image`,
|
|
154
|
+
description: `Found ${category.label} package(s) [${pkgList}] installed but not removed. ` +
|
|
155
|
+
'These add size and attack surface in production. ' +
|
|
156
|
+
'Remove them after build steps or use a multi-stage build.',
|
|
157
|
+
fix: buildFix(run, pkgs),
|
|
158
|
+
estimatedSavingsBytes: savings,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return recommendations;
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
/** Build fix suggestion: append apt-get purge / apk del for the packages */
|
|
166
|
+
function buildFix(run, packages) {
|
|
167
|
+
const cmd = run.command.trim();
|
|
168
|
+
const pkgList = packages.join(' ');
|
|
169
|
+
if (/apt-get/i.test(cmd)) {
|
|
170
|
+
return `RUN ${cmd} && \\\n apt-get purge -y --auto-remove ${pkgList} && \\\n rm -rf /var/lib/apt/lists/*`;
|
|
171
|
+
}
|
|
172
|
+
if (/apk/i.test(cmd)) {
|
|
173
|
+
return `RUN ${cmd} && \\\n apk del ${pkgList}`;
|
|
174
|
+
}
|
|
175
|
+
if (/yum/i.test(cmd)) {
|
|
176
|
+
return `RUN ${cmd} && \\\n yum remove -y ${pkgList} && yum clean all`;
|
|
177
|
+
}
|
|
178
|
+
if (/dnf/i.test(cmd)) {
|
|
179
|
+
return `RUN ${cmd} && \\\n dnf remove -y ${pkgList} && dnf clean all`;
|
|
180
|
+
}
|
|
181
|
+
// Fallback: generic suggestion
|
|
182
|
+
return `RUN ${cmd}\n# TODO: Remove unnecessary packages: ${pkgList}`;
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=unnecessary-packages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unnecessary-packages.js","sourceRoot":"","sources":["../../src/rules/unnecessary-packages.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Severity filtering utilities shared across CLI, exit codes, and formatters.
|
|
3
|
+
*
|
|
4
|
+
* Provides a single source of truth for severity ranking and filtering
|
|
5
|
+
* so that --severity flag, exit code logic, and output formatters all
|
|
6
|
+
* use consistent behavior.
|
|
7
|
+
*/
|
|
8
|
+
import type { Recommendation, Severity } from './types.js';
|
|
9
|
+
/** Valid severity level strings (uppercase) */
|
|
10
|
+
export declare const SEVERITY_LEVELS: readonly Severity[];
|
|
11
|
+
/** Severity ranking for comparison (higher number = more severe) */
|
|
12
|
+
export declare const SEVERITY_RANK: Record<Severity, number>;
|
|
13
|
+
/**
|
|
14
|
+
* Check whether a string is a valid Severity level (case-insensitive).
|
|
15
|
+
*/
|
|
16
|
+
export declare function isValidSeverity(value: string): value is Severity;
|
|
17
|
+
/**
|
|
18
|
+
* Filter recommendations to only those at or above the minimum severity.
|
|
19
|
+
*
|
|
20
|
+
* Example: minSeverity='MEDIUM' keeps MEDIUM and HIGH, drops LOW.
|
|
21
|
+
*/
|
|
22
|
+
export declare function filterBySeverity(recommendations: Recommendation[], minSeverity: Severity): Recommendation[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Severity filtering utilities shared across CLI, exit codes, and formatters.
|
|
3
|
+
*
|
|
4
|
+
* Provides a single source of truth for severity ranking and filtering
|
|
5
|
+
* so that --severity flag, exit code logic, and output formatters all
|
|
6
|
+
* use consistent behavior.
|
|
7
|
+
*/
|
|
8
|
+
/** Valid severity level strings (uppercase) */
|
|
9
|
+
export const SEVERITY_LEVELS = ['LOW', 'MEDIUM', 'HIGH'];
|
|
10
|
+
/** Severity ranking for comparison (higher number = more severe) */
|
|
11
|
+
export const SEVERITY_RANK = {
|
|
12
|
+
LOW: 0,
|
|
13
|
+
MEDIUM: 1,
|
|
14
|
+
HIGH: 2,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Check whether a string is a valid Severity level (case-insensitive).
|
|
18
|
+
*/
|
|
19
|
+
export function isValidSeverity(value) {
|
|
20
|
+
return SEVERITY_LEVELS.includes(value.toUpperCase());
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Filter recommendations to only those at or above the minimum severity.
|
|
24
|
+
*
|
|
25
|
+
* Example: minSeverity='MEDIUM' keeps MEDIUM and HIGH, drops LOW.
|
|
26
|
+
*/
|
|
27
|
+
export function filterBySeverity(recommendations, minSeverity) {
|
|
28
|
+
const minRank = SEVERITY_RANK[minSeverity];
|
|
29
|
+
return recommendations.filter((r) => SEVERITY_RANK[r.severity] >= minRank);
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=severity-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"severity-filter.js","sourceRoot":"","sources":["../src/severity-filter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,+CAA+C;AAC/C,MAAM,CAAC,MAAM,eAAe,GAAwB,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AAEvF,oEAAoE;AACpE,MAAM,CAAC,MAAM,aAAa,GAA6B;IACrD,GAAG,EAAE,CAAC;IACN,MAAM,EAAE,CAAC;IACT,IAAI,EAAE,CAAC;CACR,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,eAAe,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAc,CAAC,CAAC;AACnE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC9B,eAAiC,EACjC,WAAqB;IAErB,MAAM,OAAO,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;IAC3C,OAAO,eAAe,CAAC,MAAM,CAC3B,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,OAAO,CAC5C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Size prediction engine — estimates Before/After image sizes from parsed
|
|
3
|
+
* Dockerfile instructions using base image DB lookups + layer heuristics.
|
|
4
|
+
*
|
|
5
|
+
* Strategy:
|
|
6
|
+
* - Base image: direct DB lookup via getImageSize()
|
|
7
|
+
* - RUN layers: heuristic by command type (apt install, pip, npm, etc.)
|
|
8
|
+
* - COPY/ADD: rough estimates based on source patterns
|
|
9
|
+
* - After: subtract recommendation savings from before estimate
|
|
10
|
+
*/
|
|
11
|
+
import type { Instruction, Recommendation } from './types.js';
|
|
12
|
+
/** Estimated savings from cache cleanup (apt-get clean, etc.) */
|
|
13
|
+
export declare const APT_CACHE_CLEANUP_SAVINGS = 30000000;
|
|
14
|
+
export declare const APK_CACHE_CLEANUP_SAVINGS = 10000000;
|
|
15
|
+
export interface SizeEstimate {
|
|
16
|
+
baseImageBytes: number;
|
|
17
|
+
layerBytes: number;
|
|
18
|
+
totalBytes: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Estimate the "before" (current) image size from parsed instructions.
|
|
22
|
+
*/
|
|
23
|
+
export declare function estimateBeforeSize(instructions: Instruction[], getImageSizeFn: (image: string, tag: string) => number | undefined): SizeEstimate;
|
|
24
|
+
/**
|
|
25
|
+
* Estimate the "after" (optimized) image size by subtracting recommendation savings.
|
|
26
|
+
* If a base-image swap recommendation exists, uses the alternative base size.
|
|
27
|
+
*/
|
|
28
|
+
export declare function estimateAfterSize(beforeSize: SizeEstimate, recommendations: Recommendation[], newBaseImageBytes?: number): SizeEstimate;
|
|
29
|
+
/** Calculate size reduction percentage (0 if beforeBytes is 0) */
|
|
30
|
+
export declare function calcSizeReductionPercent(beforeBytes: number, afterBytes: number): number;
|
|
31
|
+
/**
|
|
32
|
+
* Estimate cache cleanup savings for a RUN command string.
|
|
33
|
+
* Used by optimization rules to populate estimatedSavingsBytes.
|
|
34
|
+
*/
|
|
35
|
+
export declare function estimateCacheCleanupSavings(command: string): number;
|