@vyuhlabs/dxkit 2.4.6 โ 2.4.8
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 +1076 -0
- package/README.md +132 -27
- package/dist/analysis-result.d.ts +112 -0
- package/dist/analysis-result.d.ts.map +1 -0
- package/dist/analysis-result.js +52 -0
- package/dist/analysis-result.js.map +1 -0
- package/dist/analyzers/bom/detailed.d.ts.map +1 -1
- package/dist/analyzers/bom/detailed.js +19 -0
- package/dist/analyzers/bom/detailed.js.map +1 -1
- package/dist/analyzers/bom/gather.d.ts +27 -26
- package/dist/analyzers/bom/gather.d.ts.map +1 -1
- package/dist/analyzers/bom/gather.js +26 -87
- package/dist/analyzers/bom/gather.js.map +1 -1
- package/dist/analyzers/bom/index.d.ts +0 -7
- package/dist/analyzers/bom/index.d.ts.map +1 -1
- package/dist/analyzers/bom/index.js +98 -48
- package/dist/analyzers/bom/index.js.map +1 -1
- package/dist/analyzers/bom/types.d.ts +11 -13
- package/dist/analyzers/bom/types.d.ts.map +1 -1
- package/dist/analyzers/cache.d.ts +95 -0
- package/dist/analyzers/cache.d.ts.map +1 -0
- package/dist/analyzers/cache.js +309 -0
- package/dist/analyzers/cache.js.map +1 -0
- package/dist/analyzers/coverage-runner.d.ts +56 -0
- package/dist/analyzers/coverage-runner.d.ts.map +1 -0
- package/dist/analyzers/coverage-runner.js +72 -0
- package/dist/analyzers/coverage-runner.js.map +1 -0
- package/dist/analyzers/dashboard/index.d.ts +24 -0
- package/dist/analyzers/dashboard/index.d.ts.map +1 -0
- package/dist/analyzers/dashboard/index.js +667 -0
- package/dist/analyzers/dashboard/index.js.map +1 -0
- package/dist/analyzers/developer/gather.d.ts.map +1 -1
- package/dist/analyzers/developer/gather.js +205 -37
- package/dist/analyzers/developer/gather.js.map +1 -1
- package/dist/analyzers/developer/index.d.ts +1 -1
- package/dist/analyzers/developer/index.d.ts.map +1 -1
- package/dist/analyzers/developer/index.js +21 -9
- package/dist/analyzers/developer/index.js.map +1 -1
- package/dist/analyzers/dispatcher.d.ts +52 -0
- package/dist/analyzers/dispatcher.d.ts.map +1 -1
- package/dist/analyzers/dispatcher.js +92 -9
- package/dist/analyzers/dispatcher.js.map +1 -1
- package/dist/analyzers/docs/shallow.d.ts +17 -5
- package/dist/analyzers/docs/shallow.d.ts.map +1 -1
- package/dist/analyzers/docs/shallow.js +65 -2
- package/dist/analyzers/docs/shallow.js.map +1 -1
- package/dist/analyzers/dx/shallow.d.ts +17 -5
- package/dist/analyzers/dx/shallow.d.ts.map +1 -1
- package/dist/analyzers/dx/shallow.js +66 -2
- package/dist/analyzers/dx/shallow.js.map +1 -1
- package/dist/analyzers/health/actions.d.ts +1 -1
- package/dist/analyzers/health/actions.d.ts.map +1 -1
- package/dist/analyzers/health/actions.js +27 -9
- package/dist/analyzers/health/actions.js.map +1 -1
- package/dist/analyzers/health/detailed.d.ts +2 -1
- package/dist/analyzers/health/detailed.d.ts.map +1 -1
- package/dist/analyzers/health/detailed.js +11 -7
- package/dist/analyzers/health/detailed.js.map +1 -1
- package/dist/analyzers/health.d.ts +27 -0
- package/dist/analyzers/health.d.ts.map +1 -1
- package/dist/analyzers/health.js +282 -34
- package/dist/analyzers/health.js.map +1 -1
- package/dist/analyzers/licenses/gather.d.ts +35 -8
- package/dist/analyzers/licenses/gather.d.ts.map +1 -1
- package/dist/analyzers/licenses/gather.js +86 -13
- package/dist/analyzers/licenses/gather.js.map +1 -1
- package/dist/analyzers/licenses/index.d.ts +1 -1
- package/dist/analyzers/licenses/index.d.ts.map +1 -1
- package/dist/analyzers/licenses/index.js +52 -11
- package/dist/analyzers/licenses/index.js.map +1 -1
- package/dist/analyzers/licenses/types.d.ts +15 -0
- package/dist/analyzers/licenses/types.d.ts.map +1 -1
- package/dist/analyzers/maintainability/shallow.d.ts +17 -5
- package/dist/analyzers/maintainability/shallow.d.ts.map +1 -1
- package/dist/analyzers/maintainability/shallow.js +80 -2
- package/dist/analyzers/maintainability/shallow.js.map +1 -1
- package/dist/analyzers/quality/detailed.d.ts.map +1 -1
- package/dist/analyzers/quality/detailed.js +4 -6
- package/dist/analyzers/quality/detailed.js.map +1 -1
- package/dist/analyzers/quality/gather.d.ts +1 -14
- package/dist/analyzers/quality/gather.d.ts.map +1 -1
- package/dist/analyzers/quality/gather.js +48 -137
- package/dist/analyzers/quality/gather.js.map +1 -1
- package/dist/analyzers/quality/index.d.ts +9 -2
- package/dist/analyzers/quality/index.d.ts.map +1 -1
- package/dist/analyzers/quality/index.js +197 -117
- package/dist/analyzers/quality/index.js.map +1 -1
- package/dist/analyzers/quality/shallow.d.ts +50 -5
- package/dist/analyzers/quality/shallow.d.ts.map +1 -1
- package/dist/analyzers/quality/shallow.js +155 -2
- package/dist/analyzers/quality/shallow.js.map +1 -1
- package/dist/analyzers/quality/types.d.ts +14 -0
- package/dist/analyzers/quality/types.d.ts.map +1 -1
- package/dist/analyzers/security/actions.d.ts +11 -4
- package/dist/analyzers/security/actions.d.ts.map +1 -1
- package/dist/analyzers/security/actions.js +87 -37
- package/dist/analyzers/security/actions.js.map +1 -1
- package/dist/analyzers/security/aggregator.d.ts +236 -0
- package/dist/analyzers/security/aggregator.d.ts.map +1 -0
- package/dist/analyzers/security/aggregator.js +349 -0
- package/dist/analyzers/security/aggregator.js.map +1 -0
- package/dist/analyzers/security/detailed.d.ts +2 -2
- package/dist/analyzers/security/detailed.d.ts.map +1 -1
- package/dist/analyzers/security/detailed.js +10 -9
- package/dist/analyzers/security/detailed.js.map +1 -1
- package/dist/analyzers/security/gather.d.ts +104 -1
- package/dist/analyzers/security/gather.d.ts.map +1 -1
- package/dist/analyzers/security/gather.js +299 -9
- package/dist/analyzers/security/gather.js.map +1 -1
- package/dist/analyzers/security/index.d.ts +15 -0
- package/dist/analyzers/security/index.d.ts.map +1 -1
- package/dist/analyzers/security/index.js +463 -50
- package/dist/analyzers/security/index.js.map +1 -1
- package/dist/analyzers/security/shallow.d.ts +50 -6
- package/dist/analyzers/security/shallow.d.ts.map +1 -1
- package/dist/analyzers/security/shallow.js +154 -2
- package/dist/analyzers/security/shallow.js.map +1 -1
- package/dist/analyzers/security/types.d.ts +51 -0
- package/dist/analyzers/security/types.d.ts.map +1 -1
- package/dist/analyzers/tests/detailed.d.ts.map +1 -1
- package/dist/analyzers/tests/detailed.js +2 -3
- package/dist/analyzers/tests/detailed.js.map +1 -1
- package/dist/analyzers/tests/gather.d.ts +2 -1
- package/dist/analyzers/tests/gather.d.ts.map +1 -1
- package/dist/analyzers/tests/gather.js +98 -69
- package/dist/analyzers/tests/gather.js.map +1 -1
- package/dist/analyzers/tests/index.d.ts +11 -2
- package/dist/analyzers/tests/index.d.ts.map +1 -1
- package/dist/analyzers/tests/index.js +83 -18
- package/dist/analyzers/tests/index.js.map +1 -1
- package/dist/analyzers/tests/shallow.d.ts +19 -5
- package/dist/analyzers/tests/shallow.d.ts.map +1 -1
- package/dist/analyzers/tests/shallow.js +89 -2
- package/dist/analyzers/tests/shallow.js.map +1 -1
- package/dist/analyzers/tests/types.d.ts +41 -1
- package/dist/analyzers/tests/types.d.ts.map +1 -1
- package/dist/analyzers/tools/autogen-header.d.ts +8 -0
- package/dist/analyzers/tools/autogen-header.d.ts.map +1 -0
- package/dist/analyzers/tools/autogen-header.js +107 -0
- package/dist/analyzers/tools/autogen-header.js.map +1 -0
- package/dist/analyzers/tools/cloc.d.ts.map +1 -1
- package/dist/analyzers/tools/cloc.js +36 -5
- package/dist/analyzers/tools/cloc.js.map +1 -1
- package/dist/analyzers/tools/deadline.d.ts +67 -0
- package/dist/analyzers/tools/deadline.d.ts.map +1 -0
- package/dist/analyzers/tools/deadline.js +81 -0
- package/dist/analyzers/tools/deadline.js.map +1 -0
- package/dist/analyzers/tools/debug-statements.d.ts +17 -0
- package/dist/analyzers/tools/debug-statements.d.ts.map +1 -0
- package/dist/analyzers/tools/debug-statements.js +58 -0
- package/dist/analyzers/tools/debug-statements.js.map +1 -0
- package/dist/analyzers/tools/default-exclusions.gitignore +28 -0
- package/dist/analyzers/tools/exclusions.d.ts +33 -6
- package/dist/analyzers/tools/exclusions.d.ts.map +1 -1
- package/dist/analyzers/tools/exclusions.js +95 -26
- package/dist/analyzers/tools/exclusions.js.map +1 -1
- package/dist/analyzers/tools/generic.d.ts +17 -2
- package/dist/analyzers/tools/generic.d.ts.map +1 -1
- package/dist/analyzers/tools/generic.js +206 -109
- package/dist/analyzers/tools/generic.js.map +1 -1
- package/dist/analyzers/tools/gitleaks.d.ts.map +1 -1
- package/dist/analyzers/tools/gitleaks.js +48 -1
- package/dist/analyzers/tools/gitleaks.js.map +1 -1
- package/dist/analyzers/tools/graphify.d.ts +30 -2
- package/dist/analyzers/tools/graphify.d.ts.map +1 -1
- package/dist/analyzers/tools/graphify.js +131 -15
- package/dist/analyzers/tools/graphify.js.map +1 -1
- package/dist/analyzers/tools/jscpd.d.ts +12 -2
- package/dist/analyzers/tools/jscpd.d.ts.map +1 -1
- package/dist/analyzers/tools/jscpd.js +129 -6
- package/dist/analyzers/tools/jscpd.js.map +1 -1
- package/dist/analyzers/tools/lint-label.d.ts +29 -0
- package/dist/analyzers/tools/lint-label.d.ts.map +1 -0
- package/dist/analyzers/tools/lint-label.js +23 -0
- package/dist/analyzers/tools/lint-label.js.map +1 -0
- package/dist/analyzers/tools/minified-detection.d.ts +9 -0
- package/dist/analyzers/tools/minified-detection.d.ts.map +1 -0
- package/dist/analyzers/tools/minified-detection.js +147 -0
- package/dist/analyzers/tools/minified-detection.js.map +1 -0
- package/dist/analyzers/tools/nuget-package-reference.d.ts +133 -0
- package/dist/analyzers/tools/nuget-package-reference.d.ts.map +1 -0
- package/dist/analyzers/tools/nuget-package-reference.js +177 -0
- package/dist/analyzers/tools/nuget-package-reference.js.map +1 -0
- package/dist/analyzers/tools/osv-scanner-deps.d.ts +3 -2
- package/dist/analyzers/tools/osv-scanner-deps.d.ts.map +1 -1
- package/dist/analyzers/tools/osv-scanner-deps.js +32 -14
- package/dist/analyzers/tools/osv-scanner-deps.js.map +1 -1
- package/dist/analyzers/tools/osv.d.ts +36 -0
- package/dist/analyzers/tools/osv.d.ts.map +1 -1
- package/dist/analyzers/tools/osv.js +26 -0
- package/dist/analyzers/tools/osv.js.map +1 -1
- package/dist/analyzers/tools/parallel.d.ts +1 -1
- package/dist/analyzers/tools/parallel.d.ts.map +1 -1
- package/dist/analyzers/tools/parallel.js +2 -2
- package/dist/analyzers/tools/parallel.js.map +1 -1
- package/dist/analyzers/tools/report-date.d.ts +17 -0
- package/dist/analyzers/tools/report-date.d.ts.map +1 -0
- package/dist/analyzers/tools/report-date.js +26 -0
- package/dist/analyzers/tools/report-date.js.map +1 -0
- package/dist/analyzers/tools/risk-score.d.ts +7 -0
- package/dist/analyzers/tools/risk-score.d.ts.map +1 -1
- package/dist/analyzers/tools/risk-score.js +9 -2
- package/dist/analyzers/tools/risk-score.js.map +1 -1
- package/dist/analyzers/tools/run-tests-helper.d.ts +43 -0
- package/dist/analyzers/tools/run-tests-helper.d.ts.map +1 -0
- package/dist/analyzers/tools/run-tests-helper.js +156 -0
- package/dist/analyzers/tools/run-tests-helper.js.map +1 -0
- package/dist/analyzers/tools/runner.d.ts.map +1 -1
- package/dist/analyzers/tools/runner.js +75 -12
- package/dist/analyzers/tools/runner.js.map +1 -1
- package/dist/analyzers/tools/semgrep.d.ts +39 -2
- package/dist/analyzers/tools/semgrep.d.ts.map +1 -1
- package/dist/analyzers/tools/semgrep.js +131 -9
- package/dist/analyzers/tools/semgrep.js.map +1 -1
- package/dist/analyzers/tools/timing.d.ts +17 -3
- package/dist/analyzers/tools/timing.d.ts.map +1 -1
- package/dist/analyzers/tools/timing.js +36 -14
- package/dist/analyzers/tools/timing.js.map +1 -1
- package/dist/analyzers/tools/tool-registry.d.ts.map +1 -1
- package/dist/analyzers/tools/tool-registry.js +11 -1
- package/dist/analyzers/tools/tool-registry.js.map +1 -1
- package/dist/analyzers/tools/tools-unavailable-prose.d.ts +18 -0
- package/dist/analyzers/tools/tools-unavailable-prose.d.ts.map +1 -0
- package/dist/analyzers/tools/tools-unavailable-prose.js +69 -0
- package/dist/analyzers/tools/tools-unavailable-prose.js.map +1 -0
- package/dist/analyzers/tools/upgrade-plan-resolver.d.ts.map +1 -1
- package/dist/analyzers/tools/upgrade-plan-resolver.js +7 -0
- package/dist/analyzers/tools/upgrade-plan-resolver.js.map +1 -1
- package/dist/analyzers/tools/vendored-advisor.d.ts +43 -0
- package/dist/analyzers/tools/vendored-advisor.d.ts.map +1 -0
- package/dist/analyzers/tools/vendored-advisor.js +107 -0
- package/dist/analyzers/tools/vendored-advisor.js.map +1 -0
- package/dist/analyzers/tools/walk-paths.d.ts +78 -0
- package/dist/analyzers/tools/walk-paths.d.ts.map +1 -0
- package/dist/analyzers/tools/walk-paths.js +150 -0
- package/dist/analyzers/tools/walk-paths.js.map +1 -0
- package/dist/analyzers/tools/walk-source-files.d.ts +70 -0
- package/dist/analyzers/tools/walk-source-files.d.ts.map +1 -0
- package/dist/analyzers/tools/walk-source-files.js +369 -0
- package/dist/analyzers/tools/walk-source-files.js.map +1 -0
- package/dist/analyzers/types.d.ts +204 -4
- package/dist/analyzers/types.d.ts.map +1 -1
- package/dist/analyzers/xlsx/bom.d.ts.map +1 -1
- package/dist/analyzers/xlsx/bom.js +8 -1
- package/dist/analyzers/xlsx/bom.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +581 -189
- package/dist/cli.js.map +1 -1
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +24 -7
- package/dist/detect.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +103 -53
- package/dist/doctor.js.map +1 -1
- package/dist/languages/capabilities/provider.d.ts +130 -1
- package/dist/languages/capabilities/provider.d.ts.map +1 -1
- package/dist/languages/capabilities/types.d.ts +68 -7
- package/dist/languages/capabilities/types.d.ts.map +1 -1
- package/dist/languages/csharp.d.ts +15 -1
- package/dist/languages/csharp.d.ts.map +1 -1
- package/dist/languages/csharp.js +624 -146
- package/dist/languages/csharp.js.map +1 -1
- package/dist/languages/go.d.ts.map +1 -1
- package/dist/languages/go.js +89 -11
- package/dist/languages/go.js.map +1 -1
- package/dist/languages/index.d.ts +132 -2
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +207 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/java.d.ts.map +1 -1
- package/dist/languages/java.js +113 -26
- package/dist/languages/java.js.map +1 -1
- package/dist/languages/kotlin.d.ts.map +1 -1
- package/dist/languages/kotlin.js +132 -26
- package/dist/languages/kotlin.js.map +1 -1
- package/dist/languages/python.d.ts.map +1 -1
- package/dist/languages/python.js +149 -44
- package/dist/languages/python.js.map +1 -1
- package/dist/languages/ruby.d.ts +39 -1
- package/dist/languages/ruby.d.ts.map +1 -1
- package/dist/languages/ruby.js +178 -44
- package/dist/languages/ruby.js.map +1 -1
- package/dist/languages/rust.d.ts.map +1 -1
- package/dist/languages/rust.js +103 -16
- package/dist/languages/rust.js.map +1 -1
- package/dist/languages/types.d.ts +228 -5
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript.d.ts.map +1 -1
- package/dist/languages/typescript.js +201 -14
- package/dist/languages/typescript.js.map +1 -1
- package/dist/scoring/dimensions/documentation.d.ts +53 -0
- package/dist/scoring/dimensions/documentation.d.ts.map +1 -0
- package/dist/scoring/dimensions/documentation.js +106 -0
- package/dist/scoring/dimensions/documentation.js.map +1 -0
- package/dist/scoring/dimensions/dx.d.ts +53 -0
- package/dist/scoring/dimensions/dx.d.ts.map +1 -0
- package/dist/scoring/dimensions/dx.js +105 -0
- package/dist/scoring/dimensions/dx.js.map +1 -0
- package/dist/scoring/dimensions/maintainability.d.ts +53 -0
- package/dist/scoring/dimensions/maintainability.d.ts.map +1 -0
- package/dist/scoring/dimensions/maintainability.js +101 -0
- package/dist/scoring/dimensions/maintainability.js.map +1 -0
- package/dist/scoring/dimensions/quality.d.ts +108 -0
- package/dist/scoring/dimensions/quality.d.ts.map +1 -0
- package/dist/scoring/dimensions/quality.js +174 -0
- package/dist/scoring/dimensions/quality.js.map +1 -0
- package/dist/scoring/dimensions/security.d.ts +84 -0
- package/dist/scoring/dimensions/security.d.ts.map +1 -0
- package/dist/scoring/dimensions/security.js +135 -0
- package/dist/scoring/dimensions/security.js.map +1 -0
- package/dist/scoring/dimensions/testing.d.ts +56 -0
- package/dist/scoring/dimensions/testing.d.ts.map +1 -0
- package/dist/scoring/dimensions/testing.js +98 -0
- package/dist/scoring/dimensions/testing.js.map +1 -0
- package/dist/scoring/evaluator.d.ts +27 -0
- package/dist/scoring/evaluator.d.ts.map +1 -0
- package/dist/scoring/evaluator.js +124 -0
- package/dist/scoring/evaluator.js.map +1 -0
- package/dist/scoring/format.d.ts +34 -0
- package/dist/scoring/format.d.ts.map +1 -0
- package/dist/scoring/format.js +63 -0
- package/dist/scoring/format.js.map +1 -0
- package/dist/scoring/index.d.ts +37 -0
- package/dist/scoring/index.d.ts.map +1 -0
- package/dist/scoring/index.js +57 -0
- package/dist/scoring/index.js.map +1 -0
- package/dist/scoring/overall.d.ts +54 -0
- package/dist/scoring/overall.d.ts.map +1 -0
- package/dist/scoring/overall.js +76 -0
- package/dist/scoring/overall.js.map +1 -0
- package/dist/scoring/result.d.ts +111 -0
- package/dist/scoring/result.d.ts.map +1 -0
- package/dist/scoring/result.js +14 -0
- package/dist/scoring/result.js.map +1 -0
- package/dist/scoring/spec.d.ts +76 -0
- package/dist/scoring/spec.d.ts.map +1 -0
- package/dist/scoring/spec.js +22 -0
- package/dist/scoring/spec.js.map +1 -0
- package/dist/scoring/thresholds.d.ts +56 -0
- package/dist/scoring/thresholds.d.ts.map +1 -0
- package/dist/scoring/thresholds.js +75 -0
- package/dist/scoring/thresholds.js.map +1 -0
- package/dist/tools-cli.d.ts.map +1 -1
- package/dist/tools-cli.js +21 -2
- package/dist/tools-cli.js.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/.claude/commands/dashboard.md +17 -9
- package/dist/analyzers/scoring.d.ts +0 -49
- package/dist/analyzers/scoring.d.ts.map +0 -1
- package/dist/analyzers/scoring.js +0 -422
- package/dist/analyzers/scoring.js.map +0 -1
- package/dist/analyzers/security/scoring.d.ts +0 -29
- package/dist/analyzers/security/scoring.d.ts.map +0 -1
- package/dist/analyzers/security/scoring.js +0 -40
- package/dist/analyzers/security/scoring.js.map +0 -1
|
@@ -33,45 +33,116 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.formatDepActionTitle = formatDepActionTitle;
|
|
36
37
|
exports.analyzeSecurity = analyzeSecurity;
|
|
37
38
|
exports.formatSecurityReport = formatSecurityReport;
|
|
38
39
|
/**
|
|
39
40
|
* Security analyzer โ public API.
|
|
40
41
|
*/
|
|
41
42
|
const path = __importStar(require("path"));
|
|
42
|
-
const detect_1 = require("../../detect");
|
|
43
|
-
const runner_1 = require("../tools/runner");
|
|
44
43
|
const timing_1 = require("../tools/timing");
|
|
45
44
|
const gather_1 = require("./gather");
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
const scoring_1 = require("../../scoring");
|
|
46
|
+
const cache_1 = require("../cache");
|
|
47
|
+
const health_1 = require("../health");
|
|
48
|
+
const languages_1 = require("../../languages");
|
|
49
|
+
const tools_unavailable_prose_1 = require("../tools/tools-unavailable-prose");
|
|
50
|
+
/**
|
|
51
|
+
* G_v4_4 (2.4.7): build the "Remediation Commands" entry for one
|
|
52
|
+
* dep-vuln finding by dispatching through the producing pack's
|
|
53
|
+
* `LanguageSupport.upgradeCommand`. Replaces the pre-G_v4_4 switch on
|
|
54
|
+
* `tool` (D062 โ switch keyed on `osv-scanner-nuget-direct` but generic
|
|
55
|
+
* osv-scanner findings carried `tool: 'osv-scanner'`, so dotnet-NuGet
|
|
56
|
+
* advisories shipped as bare prose comments). Each pack now owns its
|
|
57
|
+
* own template; non-pack code stays language-agnostic per CLAUDE.md
|
|
58
|
+
* rule 6.
|
|
59
|
+
*
|
|
60
|
+
* Dispatch order:
|
|
61
|
+
* 1. `f.packId` set โ call pack's `upgradeCommand` (cardinal path).
|
|
62
|
+
* 2. No `packId` (legacy / non-pack producers) โ generic prose fallback.
|
|
63
|
+
* 3. `upgradeCommand` returns `null` โ generic prose fallback.
|
|
64
|
+
*
|
|
65
|
+
* **Contract (G_v4_10 / D111 root fix):** caller MUST pre-filter to
|
|
66
|
+
* findings with `fixedVersion` set. The D090 renderer splits findings
|
|
67
|
+
* into Actionable (has fixedVersion โ bash block via this helper) and
|
|
68
|
+
* Mitigation (no fixedVersion โ markdown list, never enters this
|
|
69
|
+
* helper). Pre-D111 this function had a "no patched version
|
|
70
|
+
* available โ review references" prose fallback for the no-fixedVersion
|
|
71
|
+
* case; that branch became dead after D090's split and gave the
|
|
72
|
+
* misleading impression that the helper handled both states. It does
|
|
73
|
+
* not โ returning null here signals a contract violation (caller did
|
|
74
|
+
* not filter), and consumers should treat null as a skip.
|
|
75
|
+
*/
|
|
76
|
+
function buildUpgradeCommand(f) {
|
|
77
|
+
if (!f.fixedVersion)
|
|
78
|
+
return null;
|
|
79
|
+
if (f.packId) {
|
|
80
|
+
const pack = (0, languages_1.getLanguage)(f.packId);
|
|
81
|
+
if (pack && pack.upgradeCommand) {
|
|
82
|
+
const cmd = pack.upgradeCommand(f.package, f.fixedVersion);
|
|
83
|
+
if (cmd)
|
|
84
|
+
return cmd;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return `# Upgrade ${f.package} to ${f.fixedVersion} (source tool: ${f.tool ?? 'unknown'})`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* G_v4_10 / D111 (2.4.7 Phase C3): canonical UI title for a dep-vuln
|
|
91
|
+
* action. Branches on `fixedVersion` because "upgrade" and "mitigate"
|
|
92
|
+
* are linguistically different actions โ squashing them into one
|
|
93
|
+
* template with a `?? '(no patch)'` literal produced the grammatically
|
|
94
|
+
* broken "Upgrade `SharpCompress` to (no patch)" on the .NET WinForms benchmark when
|
|
95
|
+
* D108 sparse-tier fallback floated mitigation-only items into Top 5.
|
|
96
|
+
*
|
|
97
|
+
* This is the ONLY authorized site for phrasing "(no patch)" / "no
|
|
98
|
+
* patch available" in code; `scripts/check-architecture.sh` enforces
|
|
99
|
+
* G_v4_10 by banning the literal `'(no patch)'` outside this helper.
|
|
100
|
+
* Consumers (Top 5, future risk-prioritized lists, etc.) call this
|
|
101
|
+
* instead of templating inline.
|
|
102
|
+
*/
|
|
103
|
+
function formatDepActionTitle(pkg, fixedVersion) {
|
|
104
|
+
return fixedVersion
|
|
105
|
+
? `Upgrade \`${pkg}\` to ${fixedVersion}`
|
|
106
|
+
: `Review advisory for \`${pkg}\` โ no patch available`;
|
|
51
107
|
}
|
|
52
108
|
async function analyzeSecurity(repoPath, options = {}) {
|
|
53
109
|
const verbose = !!options.verbose;
|
|
54
|
-
|
|
110
|
+
// Code-side findings + the canonical SecurityAggregate come from the
|
|
111
|
+
// cross-process cache. Same gather that `health` reads โ two
|
|
112
|
+
// consumers, ONE source of truth for "code findings by severity,"
|
|
113
|
+
// "secrets by severity," "tls-bypass dedup." Closes the cross-process
|
|
114
|
+
// drift class for code-side numbers by construction.
|
|
115
|
+
const result = await (0, cache_1.readOrBuildAnalysisResult)({
|
|
116
|
+
cwd: repoPath,
|
|
117
|
+
build: (cwd) => (0, health_1.gatherAnalysisResultBody)(cwd, { verbose }),
|
|
118
|
+
});
|
|
119
|
+
const { stack, capabilities } = result;
|
|
120
|
+
const aggregate = capabilities.securityAggregate;
|
|
121
|
+
if (!aggregate) {
|
|
122
|
+
throw new Error('analyzeSecurity: cached AnalysisResult missing securityAggregate');
|
|
123
|
+
}
|
|
124
|
+
// Dependency CVEs need the full enrichment pass (EPSS / KEV /
|
|
125
|
+
// reachability / risk) that vuln-scan surfaces and `health` does
|
|
126
|
+
// not. Run it fresh against the same repo state the cache captured.
|
|
127
|
+
// Severity buckets + unique-fingerprint counts still come from the
|
|
128
|
+
// cached aggregate (severity is a raw advisory property; both basic
|
|
129
|
+
// and enriched paths produce the same buckets), so the two
|
|
130
|
+
// subcommands report identical totals.
|
|
131
|
+
const deps = await (0, timing_1.timedAsync)('dep-audit (enriched)', verbose, () => (0, gather_1.gatherDepVulns)(repoPath));
|
|
132
|
+
// toolsUsed/toolsUnavailable are derived from the cached aggregate's
|
|
133
|
+
// provenance (the canonical "what scanners ran" record), the
|
|
134
|
+
// enriched dep gather, and the cached file-walker capabilities
|
|
135
|
+
// (`find`, `git` are always-available builtins).
|
|
55
136
|
const toolsUsed = ['find', 'git'];
|
|
56
137
|
const toolsUnavailable = [];
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (secrets.toolUsed)
|
|
60
|
-
toolsUsed.push(secrets.toolUsed);
|
|
138
|
+
if (aggregate.provenance.secrets.tool)
|
|
139
|
+
toolsUsed.push(aggregate.provenance.secrets.tool);
|
|
61
140
|
else
|
|
62
141
|
toolsUnavailable.push('gitleaks');
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// 3. Code patterns (semgrep) โ dispatcher-driven via CODE_PATTERNS.
|
|
66
|
-
const code = await (0, timing_1.timedAsync)('semgrep', verbose, () => (0, gather_1.gatherCodePatterns)(repoPath));
|
|
67
|
-
if (code.toolUsed)
|
|
68
|
-
toolsUsed.push(code.toolUsed);
|
|
142
|
+
if (aggregate.provenance.codePatterns.tool)
|
|
143
|
+
toolsUsed.push(aggregate.provenance.codePatterns.tool);
|
|
69
144
|
else
|
|
70
145
|
toolsUnavailable.push('semgrep');
|
|
71
|
-
// 4. Dependency CVEs โ capability dispatcher across every active language
|
|
72
|
-
// pack. The envelope's tool field already joins multiple sources
|
|
73
|
-
// ('pip-audit, npm-audit'); split it back out for toolsUsed.
|
|
74
|
-
const deps = await (0, timing_1.timedAsync)('dep-audit', verbose, () => (0, gather_1.gatherDepVulns)(repoPath));
|
|
75
146
|
if (deps.tool) {
|
|
76
147
|
for (const t of deps.tool.split(', '))
|
|
77
148
|
toolsUsed.push(t);
|
|
@@ -79,18 +150,71 @@ async function analyzeSecurity(repoPath, options = {}) {
|
|
|
79
150
|
else {
|
|
80
151
|
toolsUnavailable.push('dep-audit');
|
|
81
152
|
}
|
|
82
|
-
|
|
83
|
-
|
|
153
|
+
if (aggregate.findingsByCategory.code.some((f) => f.tool === 'tls-bypass-registry')) {
|
|
154
|
+
toolsUsed.push('tls-bypass-registry');
|
|
155
|
+
}
|
|
156
|
+
// Combined code-side severity counts for the existing "Code Findings"
|
|
157
|
+
// table (which renders secrets+files+code+config under one heading).
|
|
158
|
+
// Derived from the dedup'd aggregate, NOT from raw envelope arrays โ
|
|
159
|
+
// that's the D086/D091 closure. Sum of unique findings across the
|
|
160
|
+
// code/secret/config categories.
|
|
161
|
+
const codeFindings = [
|
|
162
|
+
...aggregate.findingsByCategory.secret,
|
|
163
|
+
...aggregate.findingsByCategory.code,
|
|
164
|
+
...aggregate.findingsByCategory.config,
|
|
165
|
+
];
|
|
166
|
+
const codeSummary = {
|
|
167
|
+
critical: aggregate.codeBySeverity.critical + aggregate.secretsBySeverity.critical,
|
|
168
|
+
high: aggregate.codeBySeverity.high + aggregate.secretsBySeverity.high,
|
|
169
|
+
medium: aggregate.codeBySeverity.medium + aggregate.secretsBySeverity.medium,
|
|
170
|
+
low: aggregate.codeBySeverity.low + aggregate.secretsBySeverity.low,
|
|
171
|
+
total: codeFindings.length,
|
|
172
|
+
};
|
|
173
|
+
// C2.1 (perception-D086 closure): code-only + secrets-only severity
|
|
174
|
+
// breakdowns surfaced as siblings of `summary.findings`. Renderer
|
|
175
|
+
// splits the executive-summary "Code Findings" table into two
|
|
176
|
+
// labeled sections so a reader scanning health + vuln-scan sees
|
|
177
|
+
// the SAME number under the SAME label.
|
|
178
|
+
const codeOnlySummary = {
|
|
179
|
+
critical: aggregate.codeBySeverity.critical,
|
|
180
|
+
high: aggregate.codeBySeverity.high,
|
|
181
|
+
medium: aggregate.codeBySeverity.medium,
|
|
182
|
+
low: aggregate.codeBySeverity.low,
|
|
183
|
+
total: aggregate.findingsByCategory.code.length,
|
|
184
|
+
};
|
|
185
|
+
const secretsOnlySummary = {
|
|
186
|
+
critical: aggregate.secretsBySeverity.critical,
|
|
187
|
+
high: aggregate.secretsBySeverity.high,
|
|
188
|
+
medium: aggregate.secretsBySeverity.medium,
|
|
189
|
+
low: aggregate.secretsBySeverity.low,
|
|
190
|
+
total: aggregate.findingsByCategory.secret.length + aggregate.findingsByCategory.config.length,
|
|
191
|
+
};
|
|
192
|
+
// D087 closure: dependency totals now read the canonical
|
|
193
|
+
// unique-by-fingerprint count from the aggregate, matching BoM's
|
|
194
|
+
// semantics. critical/high/medium/low are derived from the unique
|
|
195
|
+
// set, so the bucket sum always equals the unique total โ no more
|
|
196
|
+
// 70 vs 81 on the same page.
|
|
197
|
+
const depSummary = {
|
|
198
|
+
...deps,
|
|
199
|
+
critical: aggregate.depBySeverity.critical,
|
|
200
|
+
high: aggregate.depBySeverity.high,
|
|
201
|
+
medium: aggregate.depBySeverity.medium,
|
|
202
|
+
low: aggregate.depBySeverity.low,
|
|
203
|
+
total: aggregate.dependencyAdvisoryUniqueCount,
|
|
204
|
+
findings: [...aggregate.findingsByCategory.dependency],
|
|
205
|
+
};
|
|
84
206
|
return {
|
|
85
|
-
repo: stack.projectName || path.basename(
|
|
86
|
-
analyzedAt:
|
|
87
|
-
commitSha:
|
|
88
|
-
branch:
|
|
207
|
+
repo: stack.projectName || path.basename(result.cwd),
|
|
208
|
+
analyzedAt: result.builtAt,
|
|
209
|
+
commitSha: result.commitSha,
|
|
210
|
+
branch: result.branch,
|
|
89
211
|
summary: {
|
|
90
|
-
findings:
|
|
91
|
-
|
|
212
|
+
findings: codeSummary,
|
|
213
|
+
codeOnly: codeOnlySummary,
|
|
214
|
+
secretsOnly: secretsOnlySummary,
|
|
215
|
+
dependencies: depSummary,
|
|
92
216
|
},
|
|
93
|
-
findings:
|
|
217
|
+
findings: codeFindings,
|
|
94
218
|
toolsUsed,
|
|
95
219
|
toolsUnavailable,
|
|
96
220
|
};
|
|
@@ -105,30 +229,60 @@ function formatSecurityReport(report, elapsed) {
|
|
|
105
229
|
L.push('');
|
|
106
230
|
L.push('---');
|
|
107
231
|
L.push('');
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
232
|
+
// C2.1 (perception D086 closure): three independent axes, each with
|
|
233
|
+
// its own labeled table. Pre-C2.1 the executive summary had a single
|
|
234
|
+
// "Code Findings" table that combined code+secret+config under one
|
|
235
|
+
// label โ health's "code findings" prose meant code-only, so the
|
|
236
|
+
// two surfaces showed different numbers under the same name. Now
|
|
237
|
+
// each surface reads its own named field and the labels match.
|
|
113
238
|
const s = report.summary.findings;
|
|
239
|
+
const c = report.summary.codeOnly;
|
|
240
|
+
const k = report.summary.secretsOnly;
|
|
114
241
|
const d = report.summary.dependencies;
|
|
115
242
|
L.push('## Executive Summary');
|
|
116
243
|
L.push('');
|
|
117
|
-
L.push('Security signals split across
|
|
118
|
-
L.push('- **Code findings** โ vulnerabilities
|
|
244
|
+
L.push('Security signals split across three independent axes:');
|
|
245
|
+
L.push('- **Code findings** โ code-pattern vulnerabilities your team owns (semgrep + TLS-bypass-registry). Fix by patching code.');
|
|
246
|
+
L.push('- **Secret & config findings** โ hardcoded secrets, private-key files, `.env` tracked in git. Fix by rotating + removing from history.');
|
|
119
247
|
L.push('- **Dependency vulnerabilities** โ vulnerabilities in third-party packages. Fix by upgrading the dep.');
|
|
120
248
|
L.push('');
|
|
249
|
+
// Code-only severity table. Reads `summary.codeOnly` directly from
|
|
250
|
+
// the canonical aggregator field `codeBySeverity`. Health's
|
|
251
|
+
// `Xc Yh Zm Wl code findings` prose comes from the same field โ
|
|
252
|
+
// numbers match by construction.
|
|
253
|
+
const codeSources = [
|
|
254
|
+
...new Set(report.findings.filter((f) => f.category === 'code').map((f) => f.tool)),
|
|
255
|
+
].sort();
|
|
121
256
|
L.push('### Code Findings');
|
|
122
257
|
L.push('');
|
|
123
|
-
L.push(`_Sources: ${
|
|
258
|
+
L.push(`_Sources: ${codeSources.join(', ') || '(none)'}_`);
|
|
124
259
|
L.push('');
|
|
125
260
|
L.push('| Severity | Count |');
|
|
126
261
|
L.push('|----------|------:|');
|
|
127
|
-
L.push(`| CRITICAL | ${
|
|
128
|
-
L.push(`| HIGH | ${
|
|
129
|
-
L.push(`| MEDIUM | ${
|
|
130
|
-
L.push(`| LOW | ${
|
|
131
|
-
L.push(`| **Subtotal** | **${
|
|
262
|
+
L.push(`| CRITICAL | ${c.critical} |`);
|
|
263
|
+
L.push(`| HIGH | ${c.high} |`);
|
|
264
|
+
L.push(`| MEDIUM | ${c.medium} |`);
|
|
265
|
+
L.push(`| LOW | ${c.low} |`);
|
|
266
|
+
L.push(`| **Subtotal** | **${c.total}** |`);
|
|
267
|
+
L.push('');
|
|
268
|
+
// Secret + config severity table. Reads `summary.secretsOnly` from
|
|
269
|
+
// the aggregator's `secretsBySeverity` axis.
|
|
270
|
+
const secretSources = [
|
|
271
|
+
...new Set(report.findings
|
|
272
|
+
.filter((f) => f.category === 'secret' || f.category === 'config')
|
|
273
|
+
.map((f) => f.tool)),
|
|
274
|
+
].sort();
|
|
275
|
+
L.push('### Secret & Config Findings');
|
|
276
|
+
L.push('');
|
|
277
|
+
L.push(`_Sources: ${secretSources.join(', ') || '(none)'}_`);
|
|
278
|
+
L.push('');
|
|
279
|
+
L.push('| Severity | Count |');
|
|
280
|
+
L.push('|----------|------:|');
|
|
281
|
+
L.push(`| CRITICAL | ${k.critical} |`);
|
|
282
|
+
L.push(`| HIGH | ${k.high} |`);
|
|
283
|
+
L.push(`| MEDIUM | ${k.medium} |`);
|
|
284
|
+
L.push(`| LOW | ${k.low} |`);
|
|
285
|
+
L.push(`| **Subtotal** | **${k.total}** |`);
|
|
132
286
|
L.push('');
|
|
133
287
|
L.push('### Dependency Vulnerabilities');
|
|
134
288
|
L.push('');
|
|
@@ -143,16 +297,206 @@ function formatSecurityReport(report, elapsed) {
|
|
|
143
297
|
L.push(`| LOW | ${d.low} |`);
|
|
144
298
|
L.push(`| **Subtotal** | **${d.total}** |`);
|
|
145
299
|
L.push('');
|
|
146
|
-
|
|
300
|
+
// D025e: if at least one pack scanned successfully (tool set) but
|
|
301
|
+
// another active pack returned unavailable, the totals are partial.
|
|
302
|
+
// Surface this rather than letting the customer assume the table is
|
|
303
|
+
// exhaustive across their stack.
|
|
304
|
+
if (!d.available) {
|
|
305
|
+
L.push(`> โ **Partial scan**: ${d.unavailableReason}. The table above`);
|
|
306
|
+
L.push(`> reflects only the packs whose dep-vuln tooling succeeded;`);
|
|
307
|
+
L.push(`> findings in the unscanned pack may be present but not listed.`);
|
|
308
|
+
L.push('');
|
|
309
|
+
}
|
|
310
|
+
L.push(`**Total signals:** ${s.total + d.total} (${c.total} code + ${k.total} secret/config + ${d.total} dependency)`);
|
|
311
|
+
}
|
|
312
|
+
else if (!d.available) {
|
|
313
|
+
// D025e: scan was attempted but couldn't run for any active pack.
|
|
314
|
+
// Pre-D025e this case shared the "no language pack with a depVulns
|
|
315
|
+
// provider was active" string with the genuinely-inactive case โ a
|
|
316
|
+
// factually-wrong framing because pack WAS active, just blocked.
|
|
317
|
+
L.push(`> โ **Dependency vulnerability scan unavailable**: ${d.unavailableReason}.`);
|
|
318
|
+
L.push(`>`);
|
|
319
|
+
L.push(`> The dep-audit tool didn't run on this repo, so the count below`);
|
|
320
|
+
L.push(`> is not "0 vulnerabilities found" โ it's "0 vulnerabilities`);
|
|
321
|
+
L.push(`> reported because the scan didn't complete." The Security`);
|
|
322
|
+
L.push(`> dimension score is capped at ${scoring_1.CAP_TIERS.uncertainty}/100 until the underlying tool`);
|
|
323
|
+
L.push(`> becomes available or a fallback path produces real data.`);
|
|
324
|
+
L.push('');
|
|
325
|
+
L.push(`**Total signals:** ${s.total} (code only โ dep-audit incomplete)`);
|
|
147
326
|
}
|
|
148
327
|
else {
|
|
149
|
-
|
|
328
|
+
// D025e: genuinely-inactive case. No active language pack exposes a
|
|
329
|
+
// depVulns provider โ either the repo is non-code (docs/assets only)
|
|
330
|
+
// or every active pack legitimately reported `no-manifest` (a
|
|
331
|
+
// polyglot repo where the pack activates but has nothing to scan).
|
|
332
|
+
L.push('_No dependency audit data โ no active language pack reported a manifest to scan._');
|
|
150
333
|
L.push('');
|
|
151
334
|
L.push(`**Total signals:** ${s.total} (code only)`);
|
|
152
335
|
}
|
|
153
336
|
L.push('');
|
|
154
337
|
L.push('---');
|
|
155
338
|
L.push('');
|
|
339
|
+
const topActions = [];
|
|
340
|
+
const findings = report.findings;
|
|
341
|
+
const envInGit = findings.filter((f) => f.category === 'config' && f.rule === 'env-in-git');
|
|
342
|
+
// 1. KEV-listed deps
|
|
343
|
+
if (topActions.length < 5) {
|
|
344
|
+
const kev = (d.findings ?? [])
|
|
345
|
+
.filter((f) => f.kev)
|
|
346
|
+
.sort((a, b) => (b.riskScore ?? 0) - (a.riskScore ?? 0));
|
|
347
|
+
for (const f of kev) {
|
|
348
|
+
if (topActions.length >= 5)
|
|
349
|
+
break;
|
|
350
|
+
topActions.push({
|
|
351
|
+
title: formatDepActionTitle(f.package, f.fixedVersion),
|
|
352
|
+
location: `${f.package}@${f.installedVersion ?? '?'} ยท ${f.id}`,
|
|
353
|
+
impact: `**KEV (active exploitation)** ยท ${f.severity.toUpperCase()}`,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// 2. Hardcoded secrets (gitleaks)
|
|
358
|
+
if (topActions.length < 5) {
|
|
359
|
+
const secrets = findings.filter((f) => f.category === 'secret' && f.rule !== 'private-key-file');
|
|
360
|
+
for (const f of secrets) {
|
|
361
|
+
if (topActions.length >= 5)
|
|
362
|
+
break;
|
|
363
|
+
topActions.push({
|
|
364
|
+
title: `Rotate exposed credential (\`${f.rule}\`)`,
|
|
365
|
+
location: `${f.file}${f.line ? ':' + f.line : ''}`,
|
|
366
|
+
impact: `${f.severity.toUpperCase()} ยท committed credential โ presumed compromised`,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// 3. .env in git โ one action covering all .env files
|
|
371
|
+
if (topActions.length < 5 && envInGit.length > 0) {
|
|
372
|
+
topActions.push({
|
|
373
|
+
title: `Remove \`.env\` from git tracking + rotate credentials`,
|
|
374
|
+
location: envInGit.map((f) => f.file).join(', '),
|
|
375
|
+
impact: `HIGH ยท ${envInGit.length} file${envInGit.length === 1 ? '' : 's'} โ see callout below for full procedure`,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
// 4. Private-key files on disk
|
|
379
|
+
if (topActions.length < 5) {
|
|
380
|
+
const keys = findings.filter((f) => f.rule === 'private-key-file');
|
|
381
|
+
for (const f of keys) {
|
|
382
|
+
if (topActions.length >= 5)
|
|
383
|
+
break;
|
|
384
|
+
topActions.push({
|
|
385
|
+
title: `Remove private-key file from repo + rotate`,
|
|
386
|
+
location: f.file,
|
|
387
|
+
impact: `CRITICAL ยท key file on disk`,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// 5. Top non-KEV dep vulns. D108 closure: graceful tiering for
|
|
392
|
+
// sparse repos. Pre-D108 the filter `riskScore >= 25` excluded
|
|
393
|
+
// the "watch" tier (10-25); on the .NET WinForms benchmark with MongoDB.Driver
|
|
394
|
+
// risk 19 + SharpCompress risk 15, no deps surfaced in Top 5
|
|
395
|
+
// even though both have unpatched advisories. Fix: iterate
|
|
396
|
+
// risk-score tiers and stop only when Top 5 is full.
|
|
397
|
+
//
|
|
398
|
+
// Tier order matches risk-score.ts:
|
|
399
|
+
// patch-now (โฅ 50) โ plan-and-patch (25-50) โ watch (10-25)
|
|
400
|
+
// โ deprioritized (< 10)
|
|
401
|
+
// Within each tier, sort by risk score desc.
|
|
402
|
+
if (topActions.length < 5) {
|
|
403
|
+
const SCORE_TIERS = [50, 25, 10, 0];
|
|
404
|
+
const usedFingerprints = new Set();
|
|
405
|
+
for (const minScore of SCORE_TIERS) {
|
|
406
|
+
if (topActions.length >= 5)
|
|
407
|
+
break;
|
|
408
|
+
const tier = (d.findings ?? [])
|
|
409
|
+
.filter((f) => !f.kev)
|
|
410
|
+
.filter((f) => !usedFingerprints.has(f.fingerprint ?? ''))
|
|
411
|
+
.filter((f) => {
|
|
412
|
+
const score = f.riskScore ?? 0;
|
|
413
|
+
// For the lowest tier (>= 0), include findings without a
|
|
414
|
+
// scored risk too (some packs don't compute risk yet).
|
|
415
|
+
return minScore === 0 ? true : score >= minScore;
|
|
416
|
+
})
|
|
417
|
+
.sort((a, b) => (b.riskScore ?? 0) - (a.riskScore ?? 0));
|
|
418
|
+
for (const f of tier) {
|
|
419
|
+
if (topActions.length >= 5)
|
|
420
|
+
break;
|
|
421
|
+
topActions.push({
|
|
422
|
+
title: formatDepActionTitle(f.package, f.fixedVersion),
|
|
423
|
+
location: `${f.package}@${f.installedVersion ?? '?'} ยท ${f.id}`,
|
|
424
|
+
impact: typeof f.riskScore === 'number'
|
|
425
|
+
? `${f.severity.toUpperCase()} ยท risk score ${f.riskScore.toFixed(0)}`
|
|
426
|
+
: `${f.severity.toUpperCase()} ยท ${f.id}`,
|
|
427
|
+
});
|
|
428
|
+
usedFingerprints.add(f.fingerprint ?? '');
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// 6. Code-pattern HIGH findings (TLS bypass, eval, SSRF, etc.)
|
|
433
|
+
if (topActions.length < 5) {
|
|
434
|
+
const codeHigh = findings
|
|
435
|
+
.filter((f) => f.category === 'code' && (f.severity === 'critical' || f.severity === 'high'))
|
|
436
|
+
.sort((a, b) => (a.severity === 'critical' ? -1 : 1) - (b.severity === 'critical' ? -1 : 1));
|
|
437
|
+
for (const f of codeHigh) {
|
|
438
|
+
if (topActions.length >= 5)
|
|
439
|
+
break;
|
|
440
|
+
topActions.push({
|
|
441
|
+
title: `Fix ${f.rule}`,
|
|
442
|
+
location: `${f.file}${f.line ? ':' + f.line : ''}`,
|
|
443
|
+
impact: `${f.severity.toUpperCase()} ยท ${f.cwe || 'see source'}`,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (topActions.length > 0) {
|
|
448
|
+
L.push('## ๐ฏ Top 5 Priority Actions');
|
|
449
|
+
L.push('');
|
|
450
|
+
L.push('Ranked by remediation leverage (active exploitation > committed credentials > high-risk dep upgrades > code patterns). Full inventory in the sections below.');
|
|
451
|
+
L.push('');
|
|
452
|
+
L.push('| # | Action | Location | Impact |');
|
|
453
|
+
L.push('|---|--------|----------|--------|');
|
|
454
|
+
topActions.forEach((a, i) => {
|
|
455
|
+
L.push(`| ${i + 1} | ${a.title} | \`${a.location}\` | ${a.impact} |`);
|
|
456
|
+
});
|
|
457
|
+
L.push('');
|
|
458
|
+
L.push('---');
|
|
459
|
+
L.push('');
|
|
460
|
+
}
|
|
461
|
+
// C2.3 / D099: `.env` files tracked in git get a dedicated callout
|
|
462
|
+
// block with the specific remediation command. Pre-C2.3 these
|
|
463
|
+
// findings appeared in the per-category list with no actionable
|
|
464
|
+
// command, drowning in the noise. A leaked `.env` is high-leverage
|
|
465
|
+
// remediation: one `git rm --cached` per file + a history-rewrite
|
|
466
|
+
// caveat covers the surface.
|
|
467
|
+
const envFindings = report.findings.filter((f) => f.category === 'config' && f.rule === 'env-in-git');
|
|
468
|
+
if (envFindings.length > 0) {
|
|
469
|
+
L.push('## ๐จ `.env` files tracked in git');
|
|
470
|
+
L.push('');
|
|
471
|
+
L.push(`**${envFindings.length} \`.env\` file${envFindings.length === 1 ? '' : 's'} committed to source control.** Even if the file has been deleted in HEAD, the secrets remain in git history and are presumed compromised โ rotate ALL credentials in these files immediately.`);
|
|
472
|
+
L.push('');
|
|
473
|
+
L.push('Remove the file(s) from the working tree (history rewrite is separate):');
|
|
474
|
+
L.push('');
|
|
475
|
+
L.push('```bash');
|
|
476
|
+
for (const f of envFindings) {
|
|
477
|
+
L.push(`git rm --cached ${f.file}`);
|
|
478
|
+
}
|
|
479
|
+
L.push('echo ".env" >> .gitignore # if not already gitignored');
|
|
480
|
+
L.push('git commit -m "remove .env files from tracking"');
|
|
481
|
+
L.push('```');
|
|
482
|
+
L.push('');
|
|
483
|
+
L.push('**To purge from history** (rewrites SHAs โ coordinate with team before pushing):');
|
|
484
|
+
L.push('');
|
|
485
|
+
L.push('```bash');
|
|
486
|
+
L.push('# Option 1: git filter-repo (preferred โ fast, safe)');
|
|
487
|
+
for (const f of envFindings) {
|
|
488
|
+
L.push(`git filter-repo --path ${f.file} --invert-paths`);
|
|
489
|
+
}
|
|
490
|
+
L.push('');
|
|
491
|
+
L.push('# Option 2: BFG Repo-Cleaner (alternative)');
|
|
492
|
+
L.push('# bfg --delete-files .env');
|
|
493
|
+
L.push('');
|
|
494
|
+
L.push('# After EITHER option, every collaborator must re-clone.');
|
|
495
|
+
L.push('```');
|
|
496
|
+
L.push('');
|
|
497
|
+
L.push('---');
|
|
498
|
+
L.push('');
|
|
499
|
+
}
|
|
156
500
|
// Findings grouped by category. Section numbers are assigned dynamically โ
|
|
157
501
|
// empty categories are skipped entirely, so the rendered document never
|
|
158
502
|
// jumps from "## 1. ..." to "## 4. ..." when middle sections have no
|
|
@@ -210,7 +554,11 @@ function formatSecurityReport(report, elapsed) {
|
|
|
210
554
|
for (const f of shown) {
|
|
211
555
|
const risk = typeof f.riskScore === 'number' ? `**${f.riskScore.toFixed(0)}**` : 'โ';
|
|
212
556
|
const kev = f.kev ? 'โ ' : '';
|
|
213
|
-
|
|
557
|
+
// D044 (2.4.7): three-state reachability rendering. Pre-D044
|
|
558
|
+
// `reachable === false` rendered as a mid-dot `ยท` which customers
|
|
559
|
+
// misread as "unknown/not-checked." Use โ/โ/โ for clarity and
|
|
560
|
+
// pair with the legend below the table.
|
|
561
|
+
const reach = f.reachable === true ? 'โ' : f.reachable === false ? 'โ' : 'โ';
|
|
214
562
|
const epss = typeof f.epssScore === 'number' ? `${(f.epssScore * 100).toFixed(2)}%` : 'โ';
|
|
215
563
|
L.push(`| ${risk} | ${f.severity.toUpperCase()} | ${kev} | ${reach} | \`${f.package}@${f.installedVersion ?? '?'}\` | \`${f.id}\` | ${f.fixedVersion ?? 'โ'} | ${epss} | ${f.tool} |`);
|
|
216
564
|
}
|
|
@@ -218,15 +566,80 @@ function formatSecurityReport(report, elapsed) {
|
|
|
218
566
|
L.push('');
|
|
219
567
|
L.push(`_Showing ${cap} of ${sorted.length} advisories ranked by risk score. Run with \`--detailed\` for the full inventory + CVSS column._`);
|
|
220
568
|
}
|
|
569
|
+
// D043 + D044 (2.4.7): column legends. Customers shouldn't have to
|
|
570
|
+
// infer what `ยท` / `โ` / `**19**` mean. Brief explanations keep the
|
|
571
|
+
// table interpretable without external docs.
|
|
572
|
+
L.push('');
|
|
573
|
+
L.push('**Column legend**:');
|
|
574
|
+
L.push('');
|
|
575
|
+
L.push(`- **Risk**: composite score combining CVSS base score, KEV-listing, EPSS exploitation probability, and source-code reachability. Higher is worse. Tiers (post-D023 / risk-score.ts): \`< 10\` deprioritized ยท \`10-25\` watch ยท \`25-50\` plan-and-patch ยท \`> 50\` patch-now.`);
|
|
576
|
+
L.push(`- **KEV**: \`โ \` means the CVE appears in CISA's Known Exploited Vulnerabilities catalog (active in-the-wild exploitation). Blank = not-KEV (verified, not omitted).`);
|
|
577
|
+
L.push(`- **Reach**: \`โ\` = an active language-pack's imports capability found this package in source (reachable). \`โ\` = imports walked but this package is declared in manifest only, not imported in code. \`โ\` = imports capability didn't run (no active pack, no source files, etc.) โ unknown reachability.`);
|
|
578
|
+
L.push(`- **Fix**: minimum upgrade version that clears the advisory (extracted from OSV's \`affected.ranges.events.fixed\`). \`โ\` = no patch released yet (consider mitigations) OR the source tool didn't surface fix info.`);
|
|
579
|
+
L.push(`- **EPSS**: probability the CVE is exploited within the next 30 days (FIRST.org's exploit-prediction scoring system). Blank/\`โ\` = no EPSS data (typically GHSA without a CVE alias).`);
|
|
221
580
|
L.push('');
|
|
222
581
|
L.push('---');
|
|
223
582
|
L.push('');
|
|
583
|
+
// C3.2 / D090 (2.4.7): "Remediation Commands" splits into two
|
|
584
|
+
// subsections so customers can distinguish patch-now upgrades from
|
|
585
|
+
// advisories that need manual mitigation. Pre-fix all entries went
|
|
586
|
+
// into one bash block which made `# no patched version available`
|
|
587
|
+
// prose dominate (platform: 84 prose lines next to 1 actual
|
|
588
|
+
// `npm install` command), drowning out the actionable subset.
|
|
589
|
+
//
|
|
590
|
+
// - "Actionable upgrades" โ findings with `fixedVersion` set.
|
|
591
|
+
// Pack's `upgradeCommand` emits a
|
|
592
|
+
// copy-pasteable install command.
|
|
593
|
+
// - "Mitigation required" โ findings with NO `fixedVersion`.
|
|
594
|
+
// Rendered as a markdown list with
|
|
595
|
+
// the advisory link (first reference)
|
|
596
|
+
// so the customer has a one-click
|
|
597
|
+
// path to upstream guidance.
|
|
598
|
+
const actionable = report.summary.dependencies.findings.filter((f) => !!f.fixedVersion);
|
|
599
|
+
const mitigation = report.summary.dependencies.findings.filter((f) => !f.fixedVersion);
|
|
600
|
+
if (actionable.length > 0 || mitigation.length > 0) {
|
|
601
|
+
L.push('## Remediation Commands');
|
|
602
|
+
L.push('');
|
|
603
|
+
if (actionable.length > 0) {
|
|
604
|
+
L.push(`### Actionable upgrades (${actionable.length})`);
|
|
605
|
+
L.push('');
|
|
606
|
+
L.push('Copy-paste to upgrade each vulnerable package (run from the project root):');
|
|
607
|
+
L.push('');
|
|
608
|
+
L.push('```bash');
|
|
609
|
+
for (const f of actionable) {
|
|
610
|
+
// `actionable` is pre-filtered by `!!fixedVersion`; the `if`
|
|
611
|
+
// is a type-narrowing guard, not a runtime branch.
|
|
612
|
+
if (!f.fixedVersion)
|
|
613
|
+
continue;
|
|
614
|
+
const cmd = buildUpgradeCommand(f);
|
|
615
|
+
if (cmd === null)
|
|
616
|
+
continue;
|
|
617
|
+
L.push(`# ${f.package}@${f.installedVersion ?? '?'} โ ${f.fixedVersion} (${f.id})`);
|
|
618
|
+
L.push(cmd);
|
|
619
|
+
L.push('');
|
|
620
|
+
}
|
|
621
|
+
L.push('```');
|
|
622
|
+
L.push('');
|
|
623
|
+
}
|
|
624
|
+
if (mitigation.length > 0) {
|
|
625
|
+
L.push(`### Mitigation required โ no patch available (${mitigation.length})`);
|
|
626
|
+
L.push('');
|
|
627
|
+
L.push('These advisories have no fixed version released. Review each upstream advisory for workarounds, then reduce exposure by upgrading the dependent package, pinning to a non-vulnerable transitive range, or removing the affected code path.');
|
|
628
|
+
L.push('');
|
|
629
|
+
for (const f of mitigation) {
|
|
630
|
+
const advisory = f.references?.[0];
|
|
631
|
+
const idCell = advisory ? `[${f.id}](${advisory})` : f.id;
|
|
632
|
+
L.push(`- \`${f.package}@${f.installedVersion ?? '?'}\` โ ${idCell}`);
|
|
633
|
+
}
|
|
634
|
+
L.push('');
|
|
635
|
+
}
|
|
636
|
+
L.push('---');
|
|
637
|
+
L.push('');
|
|
638
|
+
}
|
|
224
639
|
}
|
|
225
640
|
// Footer
|
|
226
641
|
L.push(`**Tools used:** ${report.toolsUsed.join(', ')}`);
|
|
227
|
-
|
|
228
|
-
L.push(`**Tools unavailable:** ${report.toolsUnavailable.join(', ')}`);
|
|
229
|
-
}
|
|
642
|
+
L.push(...(0, tools_unavailable_prose_1.renderToolsUnavailableLines)(report.toolsUnavailable));
|
|
230
643
|
L.push(`**Analysis time:** ${elapsed}s`);
|
|
231
644
|
L.push('');
|
|
232
645
|
L.push('*Generated by [VyuhLabs DXKit](https://www.npmjs.com/package/@vyuhlabs/dxkit)*');
|