@dailephd/my-dev-kit-lab 0.2.1 → 0.3.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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -24
  3. package/dist/scripts/audits/runAudit.js +101 -0
  4. package/dist/scripts/security/validate.js +68 -29
  5. package/dist/src/audits/codeRot/codeRotDetectors.js +17 -0
  6. package/dist/src/audits/codeRot/detectors/architectureDriftDetector.js +271 -0
  7. package/dist/src/audits/codeRot/detectors/crossPlatformRotDetector.js +214 -0
  8. package/dist/src/audits/codeRot/detectors/deadCodeCandidateDetector.js +344 -0
  9. package/dist/src/audits/codeRot/detectors/dependencyEnvironmentRotDetector.js +244 -0
  10. package/dist/src/audits/codeRot/detectors/docsCodeMismatchDetector.js +219 -0
  11. package/dist/src/audits/codeRot/detectors/duplicateImplementationDetector.js +273 -0
  12. package/dist/src/audits/codeRot/detectors/packageReleaseRotDetector.js +325 -0
  13. package/dist/src/audits/codeRot/detectors/securityValidationAssumptionRotDetector.js +385 -0
  14. package/dist/src/audits/codeRot/detectors/staleCommandReferenceDetector.js +135 -0
  15. package/dist/src/audits/codeRot/detectors/testRotDetector.js +348 -0
  16. package/dist/src/audits/codeRot/utils/boundedRead.js +23 -0
  17. package/dist/src/audits/codeRot/utils/docClaimPatterns.js +62 -0
  18. package/dist/src/audits/codeRot/utils/docCommandReferences.js +64 -0
  19. package/dist/src/audits/codeRot/utils/filePatternUtils.js +47 -0
  20. package/dist/src/audits/codeRot/utils/issueFactories.js +38 -0
  21. package/dist/src/audits/codeRot/utils/textLines.js +12 -0
  22. package/dist/src/audits/core/auditConfig.js +125 -0
  23. package/dist/src/audits/core/auditExitCode.js +75 -0
  24. package/dist/src/audits/core/auditIssue.js +20 -0
  25. package/dist/src/audits/core/auditRegistry.js +40 -0
  26. package/dist/src/audits/core/auditRunner.js +71 -0
  27. package/dist/src/audits/core/auditTarget.js +19 -0
  28. package/dist/src/audits/core/auditTypes.js +30 -0
  29. package/dist/src/audits/core/projectInventory.js +308 -0
  30. package/dist/src/audits/core/sourceOfTruth.js +339 -0
  31. package/dist/src/audits/core/textLines.js +24 -0
  32. package/dist/src/audits/report/auditReportModel.js +241 -0
  33. package/dist/src/audits/report/auditReportPaths.js +13 -0
  34. package/dist/src/audits/report/renderAuditJsonReport.js +29 -0
  35. package/dist/src/audits/report/renderAuditTextReport.js +165 -0
  36. package/dist/src/audits/report/sanitizeAuditText.js +39 -0
  37. package/dist/src/audits/report/writeAuditReports.js +27 -0
  38. package/dist/src/core/resolveCommand.js +53 -2
  39. package/dist/src/securityValidation/attackScenarios/attackProfile.js +20 -0
  40. package/dist/src/securityValidation/attackScenarios/attackResult.js +37 -0
  41. package/dist/src/securityValidation/attackScenarios/attackRunner.js +153 -0
  42. package/dist/src/securityValidation/attackScenarios/attackScenario.js +21 -0
  43. package/dist/src/securityValidation/attackScenarios/boundedSourceScan.js +100 -0
  44. package/dist/src/securityValidation/attackScenarios/exploitEvidence.js +69 -0
  45. package/dist/src/securityValidation/attackScenarios/payloadCorpus.js +0 -0
  46. package/dist/src/securityValidation/attackScenarios/profiles/localToolProfile.js +10 -0
  47. package/dist/src/securityValidation/attackScenarios/profiles/nodeCliPackageProfile.js +20 -0
  48. package/dist/src/securityValidation/attackScenarios/profiles/npmPackageProfile.js +10 -0
  49. package/dist/src/securityValidation/attackScenarios/reportSchemaGuard.js +41 -0
  50. package/dist/src/securityValidation/attackScenarios/scenarios/configInjectionScenario.js +144 -0
  51. package/dist/src/securityValidation/attackScenarios/scenarios/networkAssumptionScenario.js +129 -0
  52. package/dist/src/securityValidation/attackScenarios/scenarios/outputBoundaryScenario.js +69 -0
  53. package/dist/src/securityValidation/attackScenarios/scenarios/packageBoundaryScenario.js +118 -0
  54. package/dist/src/securityValidation/attackScenarios/scenarios/pathTraversalScenario.js +119 -0
  55. package/dist/src/securityValidation/attackScenarios/scenarios/reportPoisoningScenario.js +176 -0
  56. package/dist/src/securityValidation/attackScenarios/scenarios/secretLeakageScenario.js +143 -0
  57. package/dist/src/securityValidation/attackScenarios/scenarios/subprocessInjectionScenario.js +117 -0
  58. package/dist/src/securityValidation/attackScenarios/scenarios/targetSandboxScenario.js +118 -0
  59. package/dist/src/securityValidation/attackScenarios/targetSnapshot.js +70 -0
  60. package/dist/src/securityValidation/packageChecks/forbiddenPackageContents.js +4 -1
  61. package/dist/src/securityValidation/report/renderSecurityReport.js +135 -13
  62. package/dist/src/securityValidation/types.js +7 -0
  63. package/dist/src/securityValidation/validate/cliOptions.js +182 -0
  64. package/dist/src/securityValidation/validate/runSecurityValidation.js +134 -35
  65. package/dist/src/securityValidation/validate/verdict.js +147 -0
  66. package/package.json +18 -17
@@ -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
2
  "name": "@dailephd/my-dev-kit-lab",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Evidence, benchmark, and evaluation companion for my-dev-kit.",
6
6
  "bin": {
@@ -38,7 +38,7 @@
38
38
  "claude"
39
39
  ],
40
40
  "author": "Dai Le",
41
- "license": "UNLICENSED",
41
+ "license": "MIT",
42
42
  "scripts": {
43
43
  "build": "node scripts/clean-dist.mjs && tsc -p tsconfig.json",
44
44
  "typecheck": "tsc --noEmit",
@@ -77,19 +77,20 @@
77
77
  "security:semgrep": "tsx scripts/security/runSemgrep.ts",
78
78
  "test:security": "vitest run tests/security/",
79
79
  "test:fuzz:smoke": "tsx scripts/security/runFuzzSmoke.ts",
80
- "security:validate": "tsx scripts/security/validate.ts"
80
+ "security:validate": "tsx scripts/security/validate.ts",
81
+ "audit": "tsx scripts/audits/runAudit.ts"
81
82
  },
82
- "devDependencies": {
83
- "@types/node": "^24.0.0",
84
- "playwright": "^1.54.1",
85
- "tsx": "^4.20.3",
86
- "typescript": "^5.8.3",
87
- "vitest": "^3.2.4"
88
- },
89
- "overrides": {
90
- "vite": "7.3.6"
91
- },
92
- "engines": {
93
- "node": ">=20"
94
- }
95
- }
83
+ "devDependencies": {
84
+ "@types/node": "^24.0.0",
85
+ "playwright": "^1.54.1",
86
+ "tsx": "^4.20.3",
87
+ "typescript": "^5.8.3",
88
+ "vitest": "^3.2.4"
89
+ },
90
+ "overrides": {
91
+ "vite": "7.3.6"
92
+ },
93
+ "engines": {
94
+ "node": ">=20"
95
+ }
96
+ }