@dailephd/my-dev-kit-lab 0.2.0 → 0.2.2
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 +35 -22
- package/dist/scripts/security/resolveToolRoot.js +16 -0
- package/dist/scripts/security/runCodeql.js +2 -1
- package/dist/scripts/security/runDependencyChecks.js +2 -1
- package/dist/scripts/security/runPackageChecks.js +2 -1
- package/dist/scripts/security/runSemgrep.js +2 -1
- package/dist/scripts/security/validate.js +70 -30
- package/dist/src/core/localProjectTarget.js +4 -0
- package/dist/src/core/runMeasuredCommand.js +10 -6
- package/dist/src/securityValidation/attackScenarios/attackProfile.js +20 -0
- package/dist/src/securityValidation/attackScenarios/attackResult.js +37 -0
- package/dist/src/securityValidation/attackScenarios/attackRunner.js +153 -0
- package/dist/src/securityValidation/attackScenarios/attackScenario.js +21 -0
- package/dist/src/securityValidation/attackScenarios/boundedSourceScan.js +100 -0
- package/dist/src/securityValidation/attackScenarios/exploitEvidence.js +69 -0
- package/dist/src/securityValidation/attackScenarios/payloadCorpus.js +0 -0
- package/dist/src/securityValidation/attackScenarios/profiles/localToolProfile.js +10 -0
- package/dist/src/securityValidation/attackScenarios/profiles/nodeCliPackageProfile.js +20 -0
- package/dist/src/securityValidation/attackScenarios/profiles/npmPackageProfile.js +10 -0
- package/dist/src/securityValidation/attackScenarios/reportSchemaGuard.js +41 -0
- package/dist/src/securityValidation/attackScenarios/scenarios/configInjectionScenario.js +144 -0
- package/dist/src/securityValidation/attackScenarios/scenarios/networkAssumptionScenario.js +129 -0
- package/dist/src/securityValidation/attackScenarios/scenarios/outputBoundaryScenario.js +69 -0
- package/dist/src/securityValidation/attackScenarios/scenarios/packageBoundaryScenario.js +118 -0
- package/dist/src/securityValidation/attackScenarios/scenarios/pathTraversalScenario.js +119 -0
- package/dist/src/securityValidation/attackScenarios/scenarios/reportPoisoningScenario.js +176 -0
- package/dist/src/securityValidation/attackScenarios/scenarios/secretLeakageScenario.js +143 -0
- package/dist/src/securityValidation/attackScenarios/scenarios/subprocessInjectionScenario.js +117 -0
- package/dist/src/securityValidation/attackScenarios/scenarios/targetSandboxScenario.js +118 -0
- package/dist/src/securityValidation/attackScenarios/targetSnapshot.js +70 -0
- package/dist/src/securityValidation/packageChecks/forbiddenPackageContents.js +4 -1
- package/dist/src/securityValidation/report/renderSecurityReport.js +153 -12
- package/dist/src/securityValidation/types.js +7 -0
- package/dist/src/securityValidation/validate/cliOptions.js +182 -0
- package/dist/src/securityValidation/validate/runCliSecuritySuiteCheck.js +124 -0
- package/dist/src/securityValidation/validate/runSecurityValidation.js +135 -67
- package/dist/src/securityValidation/validate/verdict.js +147 -0
- package/package.json +14 -10
|
@@ -6,10 +6,30 @@ import { runCodeqlCheck } from "../staticScans/codeql.js";
|
|
|
6
6
|
import { runSemgrepCheck } from "../staticScans/semgrep.js";
|
|
7
7
|
import { runAllFuzzTargets } from "../fuzz/fuzzHarness.js";
|
|
8
8
|
import { ALL_FUZZ_TARGETS } from "../fuzz/fuzzTargets.js";
|
|
9
|
-
import {
|
|
10
|
-
import { calculateVerdict } from "./verdict.js";
|
|
9
|
+
import { calculateVerdict, summarizeVerdictReasoning } from "./verdict.js";
|
|
11
10
|
import { resolveValidationTarget, targetDescription as describeTarget } from "./resolveTarget.js";
|
|
12
11
|
import { DEFAULT_SECURITY_CONFIG } from "../config.js";
|
|
12
|
+
import { runCliSecuritySuiteCheck } from "./runCliSecuritySuiteCheck.js";
|
|
13
|
+
import { DEFAULT_SECURITY_CHECKS, DEFAULT_SECURITY_PROFILE, IMPLEMENTED_SECURITY_CHECK_IDS, PLANNED_SECURITY_CHECK_IDS, } from "./cliOptions.js";
|
|
14
|
+
import { runAttackScenarios } from "../attackScenarios/attackRunner.js";
|
|
15
|
+
import { toSecurityCheckResult } from "../attackScenarios/attackResult.js";
|
|
16
|
+
import { captureTargetSnapshot } from "../attackScenarios/targetSnapshot.js";
|
|
17
|
+
const NOT_SELECTED_REASON = "Not selected via --checks.";
|
|
18
|
+
function skippedCheck(id, name, category, skippedReason) {
|
|
19
|
+
const now = new Date().toISOString();
|
|
20
|
+
return {
|
|
21
|
+
id,
|
|
22
|
+
name,
|
|
23
|
+
category,
|
|
24
|
+
status: "skipped",
|
|
25
|
+
severity: "skipped",
|
|
26
|
+
startedAt: now,
|
|
27
|
+
finishedAt: now,
|
|
28
|
+
durationMs: 0,
|
|
29
|
+
findings: [],
|
|
30
|
+
skippedReason,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
13
33
|
export async function runSecurityValidation(options) {
|
|
14
34
|
const toolRoot = options.cwd;
|
|
15
35
|
const config = {
|
|
@@ -20,9 +40,15 @@ export async function runSecurityValidation(options) {
|
|
|
20
40
|
};
|
|
21
41
|
// Resolve and validate the target project
|
|
22
42
|
const target = resolveValidationTarget(options.targetPath, toolRoot);
|
|
43
|
+
// v0.2.2 Batch 3: read-only git-status snapshot taken before any checks run,
|
|
44
|
+
// so the target-sandbox attack scenario (which runs last, among the
|
|
45
|
+
// attack-scenario checks) can distinguish pre-existing target dirtiness
|
|
46
|
+
// from changes caused by this validation run.
|
|
47
|
+
const targetSnapshotBefore = captureTargetSnapshot(target.targetRoot, target.hasGit);
|
|
23
48
|
const startedAt = new Date().toISOString();
|
|
24
49
|
const allChecks = [];
|
|
25
50
|
const allFindings = [];
|
|
51
|
+
const selectedChecks = new Set(options.selectedChecks ?? DEFAULT_SECURITY_CHECKS);
|
|
26
52
|
function collect(check) {
|
|
27
53
|
allChecks.push(check);
|
|
28
54
|
allFindings.push(...check.findings);
|
|
@@ -34,88 +60,122 @@ export async function runSecurityValidation(options) {
|
|
|
34
60
|
// --- Dependency checks (runs in target root) ---
|
|
35
61
|
// If target has no package.json/lockfile, runDependencyChecks will surface that
|
|
36
62
|
// as a structured finding rather than crashing.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
63
|
+
if (selectedChecks.has("deps")) {
|
|
64
|
+
try {
|
|
65
|
+
const depOutput = await runDependencyChecks({ cwd: target.targetRoot, config });
|
|
66
|
+
collectMany(depOutput.checks);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
collect(errorCheck("dependency-checks", "Dependency checks", "dependency-audit", err));
|
|
70
|
+
}
|
|
40
71
|
}
|
|
41
|
-
|
|
42
|
-
|
|
72
|
+
else {
|
|
73
|
+
// Preserve the mandatory-check IDs verdict.ts relies on so deselecting this
|
|
74
|
+
// group still shows up as a skipped mandatory check, not a silent gap.
|
|
75
|
+
collect(skippedCheck("npm-audit-full", "npm audit (full)", "dependency-audit", NOT_SELECTED_REASON));
|
|
76
|
+
collect(skippedCheck("npm-audit-runtime", "npm audit (runtime)", "dependency-audit", NOT_SELECTED_REASON));
|
|
43
77
|
}
|
|
44
78
|
// --- Package content checks (runs in target root) ---
|
|
45
79
|
// Not all projects are publishable npm packages; runPackageChecks handles
|
|
46
80
|
// missing package.json gracefully.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
81
|
+
if (selectedChecks.has("package")) {
|
|
82
|
+
try {
|
|
83
|
+
const pkgOutput = await runPackageChecks({ cwd: target.targetRoot, config });
|
|
84
|
+
collectMany(pkgOutput.checks);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
collect(errorCheck("package-content", "Package content checks", "package-content", err));
|
|
88
|
+
}
|
|
50
89
|
}
|
|
51
|
-
|
|
52
|
-
collect(
|
|
90
|
+
else {
|
|
91
|
+
collect(skippedCheck("npm-pack-dry-run", "npm pack --dry-run", "package-content", NOT_SELECTED_REASON));
|
|
53
92
|
}
|
|
54
93
|
// --- Static scans (scan target root; config from tool root) ---
|
|
55
|
-
|
|
56
|
-
|
|
94
|
+
if (selectedChecks.has("static")) {
|
|
95
|
+
try {
|
|
96
|
+
collect(await runCodeqlCheck({ cwd: toolRoot, targetRoot: target.targetRoot, timeoutMs: config.commandTimeoutMs }));
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
collect(errorCheck("codeql-scan", "CodeQL static analysis", "static-scan", err));
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
collect(await runSemgrepCheck({
|
|
103
|
+
targetRoot: target.targetRoot,
|
|
104
|
+
toolRoot,
|
|
105
|
+
timeoutMs: config.commandTimeoutMs,
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
collect(errorCheck("semgrep-scan", "Semgrep static analysis", "static-scan", err));
|
|
110
|
+
}
|
|
57
111
|
}
|
|
58
|
-
|
|
59
|
-
collect(
|
|
112
|
+
else {
|
|
113
|
+
collect(skippedCheck("codeql-scan", "CodeQL static analysis", "static-scan", NOT_SELECTED_REASON));
|
|
114
|
+
collect(skippedCheck("semgrep-scan", "Semgrep static analysis", "static-scan", NOT_SELECTED_REASON));
|
|
60
115
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
116
|
+
// --- Security suite check ---
|
|
117
|
+
// Self-validation runs the lab's own test:security suite.
|
|
118
|
+
// External validation runs the target project's test:security script when present.
|
|
119
|
+
if (selectedChecks.has("cli-adversarial")) {
|
|
120
|
+
collect(await runCliSecuritySuiteCheck({
|
|
64
121
|
toolRoot,
|
|
122
|
+
target,
|
|
65
123
|
timeoutMs: config.commandTimeoutMs,
|
|
66
124
|
}));
|
|
67
125
|
}
|
|
68
|
-
|
|
69
|
-
collect(
|
|
70
|
-
}
|
|
71
|
-
// --- CLI adversarial tests (always tool-internal; tests my-dev-kit-lab harness) ---
|
|
72
|
-
// These run in the tool root because they use fake-adversarial-cli.js fixtures.
|
|
73
|
-
// Labeled clearly so the report shows they are not target-specific checks.
|
|
74
|
-
{
|
|
75
|
-
const startedAt2 = new Date().toISOString();
|
|
76
|
-
const npm = resolveNpmCommand();
|
|
77
|
-
const cmd = await runSecurityCommand({
|
|
78
|
-
command: npm,
|
|
79
|
-
args: ["run", "test:security", "--", "--reporter=json"],
|
|
80
|
-
cwd: toolRoot,
|
|
81
|
-
timeoutMs: Math.min(config.commandTimeoutMs * 3, 180_000),
|
|
82
|
-
});
|
|
83
|
-
const finishedAt2 = new Date().toISOString();
|
|
84
|
-
const passed = cmd.exitCode === 0;
|
|
85
|
-
collect({
|
|
86
|
-
id: "cli-adversarial-suite",
|
|
87
|
-
name: "CLI adversarial test suite (tool self-tests, not target-specific)",
|
|
88
|
-
category: "cli-adversarial",
|
|
89
|
-
status: passed ? "passed" : "failed",
|
|
90
|
-
severity: "major",
|
|
91
|
-
startedAt: startedAt2,
|
|
92
|
-
finishedAt: finishedAt2,
|
|
93
|
-
durationMs: cmd.durationMs,
|
|
94
|
-
findings: passed
|
|
95
|
-
? []
|
|
96
|
-
: [
|
|
97
|
-
{
|
|
98
|
-
id: "cli-adversarial-suite-failed",
|
|
99
|
-
title: "CLI adversarial test suite had failures",
|
|
100
|
-
severity: "major",
|
|
101
|
-
category: "cli-adversarial",
|
|
102
|
-
description: `test:security exited with code ${String(cmd.exitCode)}. Run 'npm run test:security' for details.`,
|
|
103
|
-
recommendation: "Fix failing adversarial tests before release.",
|
|
104
|
-
releaseImpact: "Should fix before release",
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
command: `${npm} run test:security`,
|
|
108
|
-
});
|
|
126
|
+
else {
|
|
127
|
+
collect(skippedCheck("cli-adversarial-suite", "Target security test suite", "cli-adversarial", NOT_SELECTED_REASON));
|
|
109
128
|
}
|
|
110
129
|
// --- Fuzz smoke (always tool-internal; tests library parser/helper code) ---
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
130
|
+
if (selectedChecks.has("fuzz")) {
|
|
131
|
+
try {
|
|
132
|
+
collect(await runAllFuzzTargets(ALL_FUZZ_TARGETS, {
|
|
133
|
+
seed: options.fuzzSeed ?? 0xdeadbeef,
|
|
134
|
+
iterations: options.fuzzIterations ?? 50,
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
collect(errorCheck("fuzz-smoke", "Fuzz smoke", "fuzz-smoke", err));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
collect(skippedCheck("fuzz-smoke", "Fuzz smoke", "fuzz-smoke", NOT_SELECTED_REASON));
|
|
116
143
|
}
|
|
117
|
-
|
|
118
|
-
|
|
144
|
+
// --- Attack-scenario checks (boundary, subprocess, secrets, network) ---
|
|
145
|
+
// v0.2.2 Batch 2: routed through the attack-scenario runner. The registry
|
|
146
|
+
// is empty in Batch 2, so these currently resolve to explicit "no
|
|
147
|
+
// scenarios registered yet" skipped results — never "passed".
|
|
148
|
+
const profile = options.profile ?? DEFAULT_SECURITY_PROFILE;
|
|
149
|
+
const attackScenarioChecks = PLANNED_SECURITY_CHECK_IDS.filter((id) => selectedChecks.has(id));
|
|
150
|
+
let attackResults = [];
|
|
151
|
+
if (attackScenarioChecks.length > 0) {
|
|
152
|
+
try {
|
|
153
|
+
attackResults = await runAttackScenarios({
|
|
154
|
+
selectedChecks: attackScenarioChecks,
|
|
155
|
+
profile,
|
|
156
|
+
toolRoot,
|
|
157
|
+
target,
|
|
158
|
+
config,
|
|
159
|
+
targetSnapshotBefore,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
attackResults = attackScenarioChecks.map((checkId) => ({
|
|
164
|
+
scenarioId: `${checkId}-attack-runner-error`,
|
|
165
|
+
scenarioTitle: `Attack scenario runner error for '${checkId}'`,
|
|
166
|
+
checkId,
|
|
167
|
+
profileId: profile,
|
|
168
|
+
status: "blocked",
|
|
169
|
+
severity: "major",
|
|
170
|
+
confidence: "low",
|
|
171
|
+
evidence: [],
|
|
172
|
+
category: "cli-adversarial",
|
|
173
|
+
errorSummary: err instanceof Error ? err.message : String(err),
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
for (const result of attackResults) {
|
|
177
|
+
collect(toSecurityCheckResult(result));
|
|
178
|
+
}
|
|
119
179
|
}
|
|
120
180
|
const finishedAt = new Date().toISOString();
|
|
121
181
|
// --- Tool root metadata ---
|
|
@@ -133,6 +193,11 @@ export async function runSecurityValidation(options) {
|
|
|
133
193
|
// Use defaults.
|
|
134
194
|
}
|
|
135
195
|
const { verdict, recommendedNextStep } = calculateVerdict(allChecks, allFindings);
|
|
196
|
+
const verdictReasonSummary = summarizeVerdictReasoning(allChecks, allFindings);
|
|
197
|
+
// "Full release gate" = every classic pre-v0.2.2 check group was selected.
|
|
198
|
+
// Attack-scenario checks (boundary/subprocess/secrets/network) are
|
|
199
|
+
// additional coverage, not part of that traditional baseline gate.
|
|
200
|
+
const isFullReleaseGate = IMPLEMENTED_SECURITY_CHECK_IDS.every((id) => selectedChecks.has(id));
|
|
136
201
|
return {
|
|
137
202
|
toolRoot,
|
|
138
203
|
toolPackageName,
|
|
@@ -150,6 +215,9 @@ export async function runSecurityValidation(options) {
|
|
|
150
215
|
findings: allFindings,
|
|
151
216
|
verdict,
|
|
152
217
|
recommendedNextStep,
|
|
218
|
+
attackResults,
|
|
219
|
+
verdictReasonSummary,
|
|
220
|
+
isFullReleaseGate,
|
|
153
221
|
};
|
|
154
222
|
}
|
|
155
223
|
function errorCheck(id, name, category, err) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { VERDICT_IMPACTS } from "../types.js";
|
|
1
2
|
// ---------------------------------------------------------------------------
|
|
2
3
|
// Verdict calculation
|
|
3
4
|
//
|
|
@@ -71,3 +72,149 @@ export function verdictToHumanLabel(verdict) {
|
|
|
71
72
|
return "inconclusive: audit environment incomplete";
|
|
72
73
|
}
|
|
73
74
|
}
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// v0.2.2 Batch 5 — fail-on severity threshold
|
|
77
|
+
//
|
|
78
|
+
// --fail-on uses a separate vocabulary (blocker/high/medium/low) from
|
|
79
|
+
// SecuritySeverity (blocker/major/minor/informational/skipped) — Batch 1
|
|
80
|
+
// deliberately kept these distinct. This maps one onto the other so a
|
|
81
|
+
// --fail-on breach can be detected without renaming any existing severity.
|
|
82
|
+
// Mapping (conservative — never understates a blocker-level finding):
|
|
83
|
+
// blocker fail-on -> only "blocker" severity findings breach it
|
|
84
|
+
// high fail-on -> "blocker" or "major" ("major" is treated as "high")
|
|
85
|
+
// medium fail-on -> "blocker", "major", or "minor" ("minor" -> "medium")
|
|
86
|
+
// low fail-on -> "blocker", "major", "minor", or "informational"
|
|
87
|
+
// "skipped" severity never breaches any threshold — a skipped optional tool
|
|
88
|
+
// is not a finding.
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
const SEVERITY_THRESHOLD_RANK = {
|
|
91
|
+
blocker: 4,
|
|
92
|
+
major: 3,
|
|
93
|
+
minor: 2,
|
|
94
|
+
informational: 1,
|
|
95
|
+
skipped: 0,
|
|
96
|
+
};
|
|
97
|
+
const FAIL_ON_THRESHOLD_RANK = {
|
|
98
|
+
blocker: 4,
|
|
99
|
+
high: 3,
|
|
100
|
+
medium: 2,
|
|
101
|
+
low: 1,
|
|
102
|
+
};
|
|
103
|
+
export function severityMeetsFailOnThreshold(severity, threshold) {
|
|
104
|
+
return SEVERITY_THRESHOLD_RANK[severity] >= FAIL_ON_THRESHOLD_RANK[threshold];
|
|
105
|
+
}
|
|
106
|
+
// True when at least one finding's severity breaches the given --fail-on
|
|
107
|
+
// threshold. Callers should OR this with the existing verdict-based exit
|
|
108
|
+
// decision, never use it to replace/weaken that decision — this only adds
|
|
109
|
+
// stricter exit-1 triggers for --fail-on medium/low (which the default
|
|
110
|
+
// verdict policy doesn't already cover via its blocker/major rules); for
|
|
111
|
+
// --fail-on blocker/high it is always a no-op given the existing policy.
|
|
112
|
+
export function findingsBreachFailOnThreshold(findings, threshold) {
|
|
113
|
+
return findings.some((f) => severityMeetsFailOnThreshold(f.severity, threshold));
|
|
114
|
+
}
|
|
115
|
+
export const VERDICT_REASON_CATEGORIES = [
|
|
116
|
+
"release-blocker",
|
|
117
|
+
"target-project-blocker",
|
|
118
|
+
"tool-framework-blocker",
|
|
119
|
+
"environment-inconclusive",
|
|
120
|
+
"optional-skipped-tool",
|
|
121
|
+
"adversarial-scenario-skipped",
|
|
122
|
+
"scanner-finding",
|
|
123
|
+
"adversarial-scenario-failure",
|
|
124
|
+
"required-evidence-missing",
|
|
125
|
+
"informational-evidence",
|
|
126
|
+
];
|
|
127
|
+
// v0.2.2 Batch 6: scenario blocker-impact classification now comes directly
|
|
128
|
+
// from SecurityCheckResult.verdictImpact (carried through from the
|
|
129
|
+
// originating AttackScenario's static declaration — see attackScenario.ts /
|
|
130
|
+
// attackResult.ts). verdict.ts no longer owns a separate scenario-id map, so
|
|
131
|
+
// there is nothing to keep in sync when a new scenario is registered — as
|
|
132
|
+
// long as the scenario declares verdictImpact, categorization is correct
|
|
133
|
+
// automatically. Checks with no verdictImpact but a scenario-shaped id
|
|
134
|
+
// (placeholder/error results, or a scenario that doesn't declare one) fall
|
|
135
|
+
// back to the generic "adversarial-scenario-failure"/"-skipped" buckets.
|
|
136
|
+
//
|
|
137
|
+
// A check is "scenario-shaped" if it carries a verdictImpact OR its id
|
|
138
|
+
// matches the synthetic placeholder/error patterns produced by
|
|
139
|
+
// attackRunner.ts for unregistered checks or runner crashes (those never
|
|
140
|
+
// carry verdictImpact since there's no real AttackScenario object).
|
|
141
|
+
function isAttackScenarioCheckId(id) {
|
|
142
|
+
return id.endsWith("-no-scenarios-registered") || id.endsWith("-attack-runner-error");
|
|
143
|
+
}
|
|
144
|
+
// Categorizes a single check for verdict-reasoning purposes. Returns null
|
|
145
|
+
// for passed/warning checks — those don't need a blocker-reasoning bucket.
|
|
146
|
+
export function categorizeCheck(check) {
|
|
147
|
+
const scenario = isAttackScenarioCheckId(check.id) || check.verdictImpact !== undefined;
|
|
148
|
+
if (check.status === "skipped") {
|
|
149
|
+
if (scenario)
|
|
150
|
+
return "adversarial-scenario-skipped";
|
|
151
|
+
if (MANDATORY_CHECK_IDS.has(check.id))
|
|
152
|
+
return "required-evidence-missing";
|
|
153
|
+
return "optional-skipped-tool";
|
|
154
|
+
}
|
|
155
|
+
if (check.status === "failed") {
|
|
156
|
+
if (check.verdictImpact && VERDICT_IMPACTS.includes(check.verdictImpact)) {
|
|
157
|
+
return check.verdictImpact;
|
|
158
|
+
}
|
|
159
|
+
if (scenario)
|
|
160
|
+
return "adversarial-scenario-failure";
|
|
161
|
+
if (check.severity === "informational")
|
|
162
|
+
return "informational-evidence";
|
|
163
|
+
return "scanner-finding";
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
// Aggregates categorizeCheck() across all checks, plus a direct informational
|
|
168
|
+
// finding count (informational findings don't get their own check-level
|
|
169
|
+
// bucket since a check can carry multiple findings of mixed severity).
|
|
170
|
+
export function summarizeVerdictReasoning(checks, findings) {
|
|
171
|
+
const summary = {
|
|
172
|
+
releaseBlockerCount: 0,
|
|
173
|
+
targetProjectBlockerCount: 0,
|
|
174
|
+
toolFrameworkBlockerCount: 0,
|
|
175
|
+
environmentInconclusiveCount: 0,
|
|
176
|
+
optionalSkippedToolCount: 0,
|
|
177
|
+
adversarialScenarioSkippedCount: 0,
|
|
178
|
+
scannerFindingCount: 0,
|
|
179
|
+
adversarialScenarioFailureCount: 0,
|
|
180
|
+
requiredEvidenceMissingCount: 0,
|
|
181
|
+
informationalEvidenceCount: 0,
|
|
182
|
+
};
|
|
183
|
+
for (const check of checks) {
|
|
184
|
+
const category = categorizeCheck(check);
|
|
185
|
+
switch (category) {
|
|
186
|
+
case "release-blocker":
|
|
187
|
+
summary.releaseBlockerCount++;
|
|
188
|
+
break;
|
|
189
|
+
case "target-project-blocker":
|
|
190
|
+
summary.targetProjectBlockerCount++;
|
|
191
|
+
break;
|
|
192
|
+
case "tool-framework-blocker":
|
|
193
|
+
summary.toolFrameworkBlockerCount++;
|
|
194
|
+
break;
|
|
195
|
+
case "environment-inconclusive":
|
|
196
|
+
summary.environmentInconclusiveCount++;
|
|
197
|
+
break;
|
|
198
|
+
case "optional-skipped-tool":
|
|
199
|
+
summary.optionalSkippedToolCount++;
|
|
200
|
+
break;
|
|
201
|
+
case "adversarial-scenario-skipped":
|
|
202
|
+
summary.adversarialScenarioSkippedCount++;
|
|
203
|
+
break;
|
|
204
|
+
case "scanner-finding":
|
|
205
|
+
summary.scannerFindingCount++;
|
|
206
|
+
break;
|
|
207
|
+
case "adversarial-scenario-failure":
|
|
208
|
+
summary.adversarialScenarioFailureCount++;
|
|
209
|
+
break;
|
|
210
|
+
case "required-evidence-missing":
|
|
211
|
+
summary.requiredEvidenceMissingCount++;
|
|
212
|
+
break;
|
|
213
|
+
case "informational-evidence":
|
|
214
|
+
case null:
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
summary.informationalEvidenceCount = findings.filter((f) => f.severity === "informational").length;
|
|
219
|
+
return summary;
|
|
220
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "@dailephd/my-dev-kit-lab",
|
|
3
|
-
"version": "0.2.
|
|
2
|
+
"name": "@dailephd/my-dev-kit-lab",
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Evidence, benchmark, and evaluation companion for my-dev-kit.",
|
|
6
6
|
"bin": {
|
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
"url": "git+https://github.com/dailephd/my-dev-kit-lab.git"
|
|
20
20
|
},
|
|
21
21
|
"homepage": "https://github.com/dailephd/my-dev-kit-lab#readme",
|
|
22
|
-
"bugs": {
|
|
23
|
-
"url": "https://github.com/dailephd/my-dev-kit-lab/issues"
|
|
24
|
-
},
|
|
25
|
-
"publishConfig": {
|
|
26
|
-
"access": "public"
|
|
27
|
-
},
|
|
28
|
-
"funding": [
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/dailephd/my-dev-kit-lab/issues"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"funding": [
|
|
29
29
|
"https://github.com/sponsors/dailephd",
|
|
30
30
|
"https://paypal.me/daile88"
|
|
31
31
|
],
|
|
@@ -38,9 +38,10 @@
|
|
|
38
38
|
"claude"
|
|
39
39
|
],
|
|
40
40
|
"author": "Dai Le",
|
|
41
|
-
"license": "
|
|
41
|
+
"license": "MIT",
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "node scripts/clean-dist.mjs && tsc -p tsconfig.json",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
44
45
|
"test": "vitest run",
|
|
45
46
|
"test:benchmarks": "vitest run tests/benchmarks tests/scripts",
|
|
46
47
|
"test:report": "vitest run tests/report tests/commands",
|
|
@@ -85,6 +86,9 @@
|
|
|
85
86
|
"typescript": "^5.8.3",
|
|
86
87
|
"vitest": "^3.2.4"
|
|
87
88
|
},
|
|
89
|
+
"overrides": {
|
|
90
|
+
"vite": "7.3.6"
|
|
91
|
+
},
|
|
88
92
|
"engines": {
|
|
89
93
|
"node": ">=20"
|
|
90
94
|
}
|