@dailephd/my-dev-kit-lab 0.2.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 (250) hide show
  1. package/README.md +272 -0
  2. package/benchmarks/contracts/benchmark-project-profiles.json +1199 -0
  3. package/benchmarks/contracts/todo-behavior.md +70 -0
  4. package/benchmarks/contracts/todo-benchmark-case.json +227 -0
  5. package/benchmarks/projects/README.md +34 -0
  6. package/benchmarks/projects/task-analytics-large-mixed/README.md +1 -0
  7. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/__init__.py +3 -0
  8. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/fixtures.py +6 -0
  9. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/metrics.py +29 -0
  10. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/models.py +21 -0
  11. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/parser.py +16 -0
  12. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/pipeline.py +9 -0
  13. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/quality.py +8 -0
  14. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/reporting.py +11 -0
  15. package/benchmarks/projects/task-analytics-large-mixed/py/tests/test_metrics.py +19 -0
  16. package/benchmarks/projects/task-analytics-large-mixed/py/tests/test_parser.py +15 -0
  17. package/benchmarks/projects/task-analytics-large-mixed/py/tests/test_quality.py +19 -0
  18. package/benchmarks/projects/task-analytics-large-mixed/py/tests/test_reporting.py +15 -0
  19. package/benchmarks/projects/task-analytics-large-mixed/ts/package.json +12 -0
  20. package/benchmarks/projects/task-analytics-large-mixed/ts/src/index.ts +11 -0
  21. package/benchmarks/projects/task-analytics-large-mixed/ts/src/models/analyticsSnapshot.ts +20 -0
  22. package/benchmarks/projects/task-analytics-large-mixed/ts/src/models/project.ts +5 -0
  23. package/benchmarks/projects/task-analytics-large-mixed/ts/src/models/task.ts +10 -0
  24. package/benchmarks/projects/task-analytics-large-mixed/ts/src/reporting/buildProjectLeaderboard.ts +7 -0
  25. package/benchmarks/projects/task-analytics-large-mixed/ts/src/reporting/formatTaskHealthReport.ts +13 -0
  26. package/benchmarks/projects/task-analytics-large-mixed/ts/src/services/buildAnalyticsSnapshot.ts +39 -0
  27. package/benchmarks/projects/task-analytics-large-mixed/ts/src/services/completeTask.ts +10 -0
  28. package/benchmarks/projects/task-analytics-large-mixed/ts/src/services/createTask.ts +21 -0
  29. package/benchmarks/projects/task-analytics-large-mixed/ts/src/services/listTasksByProject.ts +6 -0
  30. package/benchmarks/projects/task-analytics-large-mixed/ts/src/store/projectStore.ts +20 -0
  31. package/benchmarks/projects/task-analytics-large-mixed/ts/src/store/taskStore.ts +44 -0
  32. package/benchmarks/projects/task-analytics-large-mixed/ts/src/validation/projectValidation.ts +12 -0
  33. package/benchmarks/projects/task-analytics-large-mixed/ts/src/validation/taskValidation.ts +18 -0
  34. package/benchmarks/projects/task-analytics-large-mixed/ts/tests/buildAnalyticsSnapshot.test.ts +48 -0
  35. package/benchmarks/projects/task-analytics-large-mixed/ts/tests/completeTask.test.ts +21 -0
  36. package/benchmarks/projects/task-analytics-large-mixed/ts/tests/createTask.test.ts +31 -0
  37. package/benchmarks/projects/task-analytics-large-mixed/ts/tests/listTasksByProject.test.ts +18 -0
  38. package/benchmarks/projects/task-analytics-large-mixed/ts/tests/reporting.test.ts +19 -0
  39. package/benchmarks/projects/task-analytics-large-mixed/ts/tsconfig.json +12 -0
  40. package/benchmarks/projects/task-analytics-large-mixed/ts/vitest.config.ts +5 -0
  41. package/benchmarks/projects/task-workflow-medium-ts/README.md +1 -0
  42. package/benchmarks/projects/task-workflow-medium-ts/package.json +12 -0
  43. package/benchmarks/projects/task-workflow-medium-ts/src/index.ts +9 -0
  44. package/benchmarks/projects/task-workflow-medium-ts/src/models/project.ts +6 -0
  45. package/benchmarks/projects/task-workflow-medium-ts/src/models/task.ts +39 -0
  46. package/benchmarks/projects/task-workflow-medium-ts/src/services/completeTask.ts +15 -0
  47. package/benchmarks/projects/task-workflow-medium-ts/src/services/createTask.ts +26 -0
  48. package/benchmarks/projects/task-workflow-medium-ts/src/services/filterTasks.ts +17 -0
  49. package/benchmarks/projects/task-workflow-medium-ts/src/services/importTasks.ts +33 -0
  50. package/benchmarks/projects/task-workflow-medium-ts/src/services/summarizeTasks.ts +30 -0
  51. package/benchmarks/projects/task-workflow-medium-ts/src/store/taskStore.ts +76 -0
  52. package/benchmarks/projects/task-workflow-medium-ts/src/utils/deterministicId.ts +3 -0
  53. package/benchmarks/projects/task-workflow-medium-ts/src/validation/taskValidation.ts +45 -0
  54. package/benchmarks/projects/task-workflow-medium-ts/tests/completeTask.test.ts +16 -0
  55. package/benchmarks/projects/task-workflow-medium-ts/tests/createTask.test.ts +21 -0
  56. package/benchmarks/projects/task-workflow-medium-ts/tests/filterTasks.test.ts +18 -0
  57. package/benchmarks/projects/task-workflow-medium-ts/tests/importTasks.test.ts +22 -0
  58. package/benchmarks/projects/task-workflow-medium-ts/tests/summarizeTasks.test.ts +29 -0
  59. package/benchmarks/projects/task-workflow-medium-ts/tsconfig.json +12 -0
  60. package/benchmarks/projects/task-workflow-medium-ts/vitest.config.ts +5 -0
  61. package/benchmarks/projects/todo-js/README.md +3 -0
  62. package/benchmarks/projects/todo-js/package.json +11 -0
  63. package/benchmarks/projects/todo-js/src/index.js +2 -0
  64. package/benchmarks/projects/todo-js/src/taskService.js +37 -0
  65. package/benchmarks/projects/todo-js/src/taskStore.js +28 -0
  66. package/benchmarks/projects/todo-js/tests/taskService.test.js +45 -0
  67. package/benchmarks/projects/todo-js/vitest.config.js +5 -0
  68. package/benchmarks/projects/todo-mixed-ts-py/README.md +3 -0
  69. package/benchmarks/projects/todo-mixed-ts-py/package.json +13 -0
  70. package/benchmarks/projects/todo-mixed-ts-py/python/task_service.py +76 -0
  71. package/benchmarks/projects/todo-mixed-ts-py/src/taskCli.ts +38 -0
  72. package/benchmarks/projects/todo-mixed-ts-py/tests/mixedBoundary.test.ts +18 -0
  73. package/benchmarks/projects/todo-mixed-ts-py/tsconfig.json +12 -0
  74. package/benchmarks/projects/todo-mixed-ts-py/vitest.config.ts +5 -0
  75. package/benchmarks/projects/todo-python/README.md +3 -0
  76. package/benchmarks/projects/todo-python/src/__init__.py +4 -0
  77. package/benchmarks/projects/todo-python/src/task_service.py +32 -0
  78. package/benchmarks/projects/todo-python/src/task_store.py +28 -0
  79. package/benchmarks/projects/todo-python/tests/test_task_service.py +52 -0
  80. package/benchmarks/projects/todo-ts/README.md +3 -0
  81. package/benchmarks/projects/todo-ts/package.json +12 -0
  82. package/benchmarks/projects/todo-ts/src/index.ts +2 -0
  83. package/benchmarks/projects/todo-ts/src/taskService.ts +41 -0
  84. package/benchmarks/projects/todo-ts/src/taskStore.ts +34 -0
  85. package/benchmarks/projects/todo-ts/tests/taskService.test.ts +45 -0
  86. package/benchmarks/projects/todo-ts/tsconfig.json +12 -0
  87. package/benchmarks/projects/todo-ts/vitest.config.ts +5 -0
  88. package/dist/scripts/build-gallery.js +3 -0
  89. package/dist/scripts/capture-demo-report.js +3 -0
  90. package/dist/scripts/evaluate-token-savings.js +2 -0
  91. package/dist/scripts/experiments/describeExperiment.js +143 -0
  92. package/dist/scripts/experiments/listExperiments.js +44 -0
  93. package/dist/scripts/experiments/runExperiment.js +199 -0
  94. package/dist/scripts/generate-experiment-plots.js +3 -0
  95. package/dist/scripts/generate-prompt-variants.js +2 -0
  96. package/dist/scripts/render-experiment-report.js +2 -0
  97. package/dist/scripts/run-agent-prompt.js +2 -0
  98. package/dist/scripts/run-controlled-experiment.js +2 -0
  99. package/dist/scripts/run-final-demo.js +3 -0
  100. package/dist/scripts/run-lab-demo.js +5 -0
  101. package/dist/scripts/run-visualization-demos.js +3 -0
  102. package/dist/scripts/security/runCodeql.js +57 -0
  103. package/dist/scripts/security/runDependencyChecks.js +57 -0
  104. package/dist/scripts/security/runFuzzSmoke.js +29 -0
  105. package/dist/scripts/security/runPackageChecks.js +56 -0
  106. package/dist/scripts/security/runSemgrep.js +63 -0
  107. package/dist/scripts/security/validate.js +117 -0
  108. package/dist/scripts/verify-benchmarks.js +202 -0
  109. package/dist/src/agents/adapters/claudeAdapter.js +37 -0
  110. package/dist/src/agents/adapters/codexAdapter.js +110 -0
  111. package/dist/src/agents/adapters/fakeAgentAdapter.js +101 -0
  112. package/dist/src/agents/agentRegistry.js +21 -0
  113. package/dist/src/agents/index.js +7 -0
  114. package/dist/src/agents/parseAgentTokenUsage.js +137 -0
  115. package/dist/src/agents/runAgentPrompt.js +38 -0
  116. package/dist/src/agents/types.js +1 -0
  117. package/dist/src/commands/buildGalleryCommand.js +56 -0
  118. package/dist/src/commands/captureDemoReport.js +116 -0
  119. package/dist/src/commands/evaluateTokenSavings.js +175 -0
  120. package/dist/src/commands/generateExperimentPlotsCommand.js +38 -0
  121. package/dist/src/commands/generatePromptVariants.js +67 -0
  122. package/dist/src/commands/renderExperimentReportCommand.js +131 -0
  123. package/dist/src/commands/runAgentPromptCommand.js +132 -0
  124. package/dist/src/commands/runControlledExperimentCommand.js +174 -0
  125. package/dist/src/commands/runFinalDemoCommand.js +123 -0
  126. package/dist/src/commands/runLabDemo.js +62 -0
  127. package/dist/src/commands/runVisualizationDemosCommand.js +67 -0
  128. package/dist/src/core/commandLine.js +59 -0
  129. package/dist/src/core/countTokens.js +8 -0
  130. package/dist/src/core/fileGlobs.js +100 -0
  131. package/dist/src/core/localProjectTarget.js +75 -0
  132. package/dist/src/core/pathSafety.js +19 -0
  133. package/dist/src/core/pythonCommand.js +30 -0
  134. package/dist/src/core/resolveCommand.js +110 -0
  135. package/dist/src/core/runMeasuredCommand.js +143 -0
  136. package/dist/src/evaluation/benchmarkMetadata.js +207 -0
  137. package/dist/src/evaluation/buildExperimentMatrix.js +75 -0
  138. package/dist/src/evaluation/classifyAgentRunOutcome.js +40 -0
  139. package/dist/src/evaluation/compareExperimentRuns.js +79 -0
  140. package/dist/src/evaluation/compareTokenSavings.js +47 -0
  141. package/dist/src/evaluation/controlledExperimentTypes.js +1 -0
  142. package/dist/src/evaluation/index.js +18 -0
  143. package/dist/src/evaluation/parseAgentAnswer.js +230 -0
  144. package/dist/src/evaluation/projectComplexity.js +126 -0
  145. package/dist/src/evaluation/projectFileTree.js +83 -0
  146. package/dist/src/evaluation/readEvaluationCases.js +59 -0
  147. package/dist/src/evaluation/renderTokenSavingsReportInput.js +55 -0
  148. package/dist/src/evaluation/runControlledExperiment.js +158 -0
  149. package/dist/src/evaluation/runMyDevKitRetrieval.js +197 -0
  150. package/dist/src/evaluation/runRawFullFileBaseline.js +31 -0
  151. package/dist/src/evaluation/scoreCorrectness.js +127 -0
  152. package/dist/src/evaluation/types.js +1 -0
  153. package/dist/src/evaluation/writeExperimentArtifacts.js +104 -0
  154. package/dist/src/evaluation/writeTokenSavingsArtifacts.js +57 -0
  155. package/dist/src/experiments/config.js +24 -0
  156. package/dist/src/experiments/defaultRegistry.js +7 -0
  157. package/dist/src/experiments/errors.js +18 -0
  158. package/dist/src/experiments/index.js +9 -0
  159. package/dist/src/experiments/outputPaths.js +25 -0
  160. package/dist/src/experiments/plugins/contextStrategyComparison/config.js +37 -0
  161. package/dist/src/experiments/plugins/contextStrategyComparison/index.js +3 -0
  162. package/dist/src/experiments/plugins/contextStrategyComparison/plugin.js +83 -0
  163. package/dist/src/experiments/plugins/contextStrategyComparison/resultMapping.js +260 -0
  164. package/dist/src/experiments/plugins/index.js +1 -0
  165. package/dist/src/experiments/registry.js +43 -0
  166. package/dist/src/experiments/results.js +48 -0
  167. package/dist/src/experiments/runner.js +181 -0
  168. package/dist/src/experiments/target.js +8 -0
  169. package/dist/src/experiments/types.js +1 -0
  170. package/dist/src/gallery/index.js +2 -0
  171. package/dist/src/gallery/types.js +1 -0
  172. package/dist/src/gallery/writeGalleryManifest.js +214 -0
  173. package/dist/src/index.js +12 -0
  174. package/dist/src/plots/buildExperimentPlotData.js +137 -0
  175. package/dist/src/plots/index.js +4 -0
  176. package/dist/src/plots/renderSvgChart.js +82 -0
  177. package/dist/src/plots/types.js +1 -0
  178. package/dist/src/plots/writePlotArtifacts.js +46 -0
  179. package/dist/src/prompts/buildPromptContext.js +68 -0
  180. package/dist/src/prompts/generateMyDevKitPrompt.js +106 -0
  181. package/dist/src/prompts/generatePromptVariants.js +36 -0
  182. package/dist/src/prompts/generateRawFullFilePrompt.js +97 -0
  183. package/dist/src/prompts/index.js +7 -0
  184. package/dist/src/prompts/measurePromptComplexity.js +41 -0
  185. package/dist/src/prompts/types.js +1 -0
  186. package/dist/src/prompts/writePromptArtifacts.js +43 -0
  187. package/dist/src/report/buildExperimentReportInput.js +339 -0
  188. package/dist/src/report/experimentReportTypes.js +1 -0
  189. package/dist/src/report/experiments/buildPluginExperimentReport.js +153 -0
  190. package/dist/src/report/experiments/experimentReportModel.js +1 -0
  191. package/dist/src/report/experiments/index.js +4 -0
  192. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +133 -0
  193. package/dist/src/report/experiments/writePluginExperimentReports.js +30 -0
  194. package/dist/src/report/index.js +8 -0
  195. package/dist/src/report/renderExperimentHtmlReport.js +354 -0
  196. package/dist/src/report/renderHtmlReport.js +103 -0
  197. package/dist/src/report/types.js +10 -0
  198. package/dist/src/report/writeExperimentReportArtifacts.js +38 -0
  199. package/dist/src/report/writeReportArtifacts.js +39 -0
  200. package/dist/src/screenshot/captureReportScreenshot.js +75 -0
  201. package/dist/src/screenshot/index.js +2 -0
  202. package/dist/src/screenshot/types.js +1 -0
  203. package/dist/src/securityValidation/artifacts.js +15 -0
  204. package/dist/src/securityValidation/cliAdversarial/adversarialCliConfig.js +38 -0
  205. package/dist/src/securityValidation/cliAdversarial/dataVolumeChecks.js +194 -0
  206. package/dist/src/securityValidation/cliAdversarial/jsonStdoutChecks.js +359 -0
  207. package/dist/src/securityValidation/cliAdversarial/malformedArtifactChecks.js +284 -0
  208. package/dist/src/securityValidation/cliAdversarial/malformedArtifactFixtures.js +79 -0
  209. package/dist/src/securityValidation/cliAdversarial/pathBoundaryChecks.js +431 -0
  210. package/dist/src/securityValidation/cliAdversarial/pathCases.js +144 -0
  211. package/dist/src/securityValidation/cliAdversarial/readOnlyBoundaryChecks.js +294 -0
  212. package/dist/src/securityValidation/cliAdversarial/runAdversarialCheck.js +149 -0
  213. package/dist/src/securityValidation/cliAdversarial/subprocessSafetyChecks.js +214 -0
  214. package/dist/src/securityValidation/cliAdversarial/tempWorkspace.js +160 -0
  215. package/dist/src/securityValidation/commandRunner.js +136 -0
  216. package/dist/src/securityValidation/config.js +39 -0
  217. package/dist/src/securityValidation/dependencies/parseNpmAudit.js +115 -0
  218. package/dist/src/securityValidation/dependencies/parseNpmLs.js +71 -0
  219. package/dist/src/securityValidation/dependencies/parseNpmOutdated.js +41 -0
  220. package/dist/src/securityValidation/dependencies/runDependencyChecks.js +239 -0
  221. package/dist/src/securityValidation/dependencies/runOsvScanner.js +43 -0
  222. package/dist/src/securityValidation/fuzz/fuzzHarness.js +61 -0
  223. package/dist/src/securityValidation/fuzz/fuzzTargets.js +204 -0
  224. package/dist/src/securityValidation/fuzz/randomInput.js +0 -0
  225. package/dist/src/securityValidation/index.js +34 -0
  226. package/dist/src/securityValidation/packageChecks/forbiddenPackageContents.js +67 -0
  227. package/dist/src/securityValidation/packageChecks/parseNpmPackDryRun.js +56 -0
  228. package/dist/src/securityValidation/packageChecks/runPackageChecks.js +88 -0
  229. package/dist/src/securityValidation/report/renderSecurityReport.js +248 -0
  230. package/dist/src/securityValidation/report/securityReportTypes.js +1 -0
  231. package/dist/src/securityValidation/staticScans/codeql.js +66 -0
  232. package/dist/src/securityValidation/staticScans/semgrep.js +180 -0
  233. package/dist/src/securityValidation/testMatrix.js +535 -0
  234. package/dist/src/securityValidation/types.js +34 -0
  235. package/dist/src/securityValidation/validate/resolveTarget.js +32 -0
  236. package/dist/src/securityValidation/validate/runSecurityValidation.js +169 -0
  237. package/dist/src/securityValidation/validate/verdict.js +73 -0
  238. package/dist/src/visualizationDemos/buildMyDevKitVisualizationCommands.js +59 -0
  239. package/dist/src/visualizationDemos/index.js +4 -0
  240. package/dist/src/visualizationDemos/runVisualizationDemos.js +82 -0
  241. package/dist/src/visualizationDemos/types.js +1 -0
  242. package/dist/src/visualizationDemos/writeVisualizationDemoArtifacts.js +25 -0
  243. package/docs/METRICS.md +286 -0
  244. package/examples/demo-report-input.json +78 -0
  245. package/examples/lab-demo-cases.json +35 -0
  246. package/examples/real-agent-campaign-cases.json +118 -0
  247. package/examples/token-savings-cases.json +122 -0
  248. package/package.json +91 -0
  249. package/tests/fixtures/fake-adversarial-cli.js +152 -0
  250. package/tests/fixtures/fake-my-dev-kit-cli.js +83 -0
@@ -0,0 +1,239 @@
1
+ import path from "node:path";
2
+ import { runSecurityCommand, resolveNpmCommand } from "../commandRunner.js";
3
+ import { writeCheckResult } from "../artifacts.js";
4
+ import { parseNpmAudit } from "./parseNpmAudit.js";
5
+ import { parseNpmLs } from "./parseNpmLs.js";
6
+ import { parseNpmOutdated } from "./parseNpmOutdated.js";
7
+ import { runOsvScanner } from "./runOsvScanner.js";
8
+ // Orchestrate all dependency checks and write structured results to reportDir.
9
+ // npm audit, npm ls, and npm outdated require network/lockfile access;
10
+ // OSV-Scanner is optional and marked skipped if not installed.
11
+ export async function runDependencyChecks(options) {
12
+ const { cwd, config } = options;
13
+ const { reportDir, rawOutputDir, commandTimeoutMs, requireOsvScanner } = config;
14
+ const allFindings = [];
15
+ const checks = [];
16
+ const npm = resolveNpmCommand();
17
+ // npm audit --json (full audit including devDependencies)
18
+ {
19
+ const startedAt = new Date().toISOString();
20
+ const cmd = await runSecurityCommand({
21
+ command: npm,
22
+ args: ["audit", "--json"],
23
+ cwd,
24
+ timeoutMs: commandTimeoutMs,
25
+ });
26
+ const finishedAt = new Date().toISOString();
27
+ const parsed = parseNpmAudit(cmd.stdout, "npm-audit-full");
28
+ const findings = parsed.findings;
29
+ allFindings.push(...findings);
30
+ const check = {
31
+ id: "npm-audit-full",
32
+ name: "npm audit (including devDependencies)",
33
+ category: "dependency-audit",
34
+ status: cmd.timedOut
35
+ ? "failed"
36
+ : parsed.parseError
37
+ ? "warning"
38
+ : parsed.ok
39
+ ? "passed"
40
+ : parsed.severityCounts.critical > 0 || parsed.severityCounts.high > 0
41
+ ? "failed"
42
+ : "warning",
43
+ severity: parsed.ok
44
+ ? "informational"
45
+ : parsed.severityCounts.critical > 0 || parsed.severityCounts.high > 0
46
+ ? "blocker"
47
+ : parsed.severityCounts.moderate > 0
48
+ ? "major"
49
+ : "minor",
50
+ startedAt,
51
+ finishedAt,
52
+ durationMs: cmd.durationMs,
53
+ findings,
54
+ skippedReason: undefined,
55
+ command: "npm audit --json",
56
+ };
57
+ await writeCheckResult({
58
+ result: check,
59
+ outputPath: path.join(reportDir, "npm-audit-full.json"),
60
+ rawDir: rawOutputDir,
61
+ rawStdout: cmd.stdout,
62
+ rawStderr: cmd.stderr,
63
+ });
64
+ checks.push(check);
65
+ }
66
+ // npm audit --omit=dev --json (runtime dependencies only)
67
+ {
68
+ const startedAt = new Date().toISOString();
69
+ const cmd = await runSecurityCommand({
70
+ command: npm,
71
+ args: ["audit", "--omit=dev", "--json"],
72
+ cwd,
73
+ timeoutMs: commandTimeoutMs,
74
+ });
75
+ const finishedAt = new Date().toISOString();
76
+ const parsed = parseNpmAudit(cmd.stdout, "npm-audit-runtime");
77
+ const findings = parsed.findings;
78
+ allFindings.push(...findings);
79
+ const check = {
80
+ id: "npm-audit-runtime",
81
+ name: "npm audit --omit=dev (runtime dependencies only)",
82
+ category: "dependency-audit",
83
+ status: cmd.timedOut
84
+ ? "failed"
85
+ : parsed.parseError
86
+ ? "warning"
87
+ : parsed.ok
88
+ ? "passed"
89
+ : parsed.severityCounts.critical > 0 || parsed.severityCounts.high > 0
90
+ ? "failed"
91
+ : "warning",
92
+ severity: parsed.ok
93
+ ? "informational"
94
+ : parsed.severityCounts.critical > 0 || parsed.severityCounts.high > 0
95
+ ? "blocker"
96
+ : parsed.severityCounts.moderate > 0
97
+ ? "major"
98
+ : "minor",
99
+ startedAt,
100
+ finishedAt,
101
+ durationMs: cmd.durationMs,
102
+ findings,
103
+ skippedReason: undefined,
104
+ command: "npm audit --omit=dev --json",
105
+ };
106
+ await writeCheckResult({
107
+ result: check,
108
+ outputPath: path.join(reportDir, "npm-audit-runtime.json"),
109
+ rawDir: rawOutputDir,
110
+ rawStdout: cmd.stdout,
111
+ rawStderr: cmd.stderr,
112
+ });
113
+ checks.push(check);
114
+ }
115
+ // npm outdated --json
116
+ {
117
+ const startedAt = new Date().toISOString();
118
+ const cmd = await runSecurityCommand({
119
+ command: npm,
120
+ args: ["outdated", "--json"],
121
+ cwd,
122
+ timeoutMs: commandTimeoutMs,
123
+ });
124
+ const finishedAt = new Date().toISOString();
125
+ const parsed = parseNpmOutdated(cmd.stdout, "npm-outdated");
126
+ const findings = parsed.findings;
127
+ allFindings.push(...findings);
128
+ const check = {
129
+ id: "npm-outdated",
130
+ name: "npm outdated",
131
+ category: "dependency-audit",
132
+ status: cmd.timedOut ? "failed" : parsed.outdatedCount > 0 ? "warning" : "passed",
133
+ severity: "informational",
134
+ startedAt,
135
+ finishedAt,
136
+ durationMs: cmd.durationMs,
137
+ findings,
138
+ skippedReason: undefined,
139
+ command: "npm outdated --json",
140
+ };
141
+ await writeCheckResult({
142
+ result: check,
143
+ outputPath: path.join(reportDir, "npm-outdated.json"),
144
+ rawDir: rawOutputDir,
145
+ rawStdout: cmd.stdout,
146
+ rawStderr: cmd.stderr,
147
+ });
148
+ checks.push(check);
149
+ }
150
+ // npm ls --all --json
151
+ {
152
+ const startedAt = new Date().toISOString();
153
+ const cmd = await runSecurityCommand({
154
+ command: npm,
155
+ args: ["ls", "--all", "--json"],
156
+ cwd,
157
+ timeoutMs: commandTimeoutMs,
158
+ });
159
+ const finishedAt = new Date().toISOString();
160
+ const parsed = parseNpmLs(cmd.stdout, "npm-ls");
161
+ const findings = parsed.findings;
162
+ allFindings.push(...findings);
163
+ const check = {
164
+ id: "npm-ls",
165
+ name: "npm ls --all (dependency tree resolution)",
166
+ category: "dependency-audit",
167
+ status: cmd.timedOut
168
+ ? "failed"
169
+ : parsed.parseError
170
+ ? "warning"
171
+ : parsed.ok
172
+ ? "passed"
173
+ : "warning",
174
+ severity: parsed.missingCount > 0 ? "major" : "informational",
175
+ startedAt,
176
+ finishedAt,
177
+ durationMs: cmd.durationMs,
178
+ findings,
179
+ skippedReason: undefined,
180
+ command: "npm ls --all --json",
181
+ };
182
+ await writeCheckResult({
183
+ result: check,
184
+ outputPath: path.join(reportDir, "npm-ls.json"),
185
+ rawDir: rawOutputDir,
186
+ rawStdout: cmd.stdout,
187
+ rawStderr: cmd.stderr,
188
+ });
189
+ checks.push(check);
190
+ }
191
+ // OSV-Scanner (optional)
192
+ {
193
+ const startedAt = new Date().toISOString();
194
+ const cmd = await runOsvScanner({ cwd, timeoutMs: commandTimeoutMs, requireOsvScanner });
195
+ const finishedAt = new Date().toISOString();
196
+ const check = {
197
+ id: "osv-scanner",
198
+ name: "OSV-Scanner",
199
+ category: "dependency-audit",
200
+ status: cmd.skipped ? "skipped" : cmd.timedOut || cmd.exitCode === null ? "failed" : cmd.exitCode === 0 ? "passed" : "warning",
201
+ severity: cmd.skipped ? "skipped" : "informational",
202
+ startedAt,
203
+ finishedAt,
204
+ durationMs: cmd.durationMs,
205
+ findings: [],
206
+ skippedReason: cmd.skippedReason,
207
+ command: "osv-scanner --lockfile package-lock.json --format json",
208
+ };
209
+ await writeCheckResult({
210
+ result: check,
211
+ outputPath: path.join(reportDir, "osv-scanner.json"),
212
+ rawDir: rawOutputDir,
213
+ rawStdout: cmd.stdout,
214
+ rawStderr: cmd.stderr,
215
+ });
216
+ checks.push(check);
217
+ }
218
+ // Write combined dependency checks summary
219
+ const combined = {
220
+ id: "dependency-checks",
221
+ name: "Dependency checks summary",
222
+ category: "dependency-audit",
223
+ status: checks.some((c) => c.status === "failed") ? "failed" : checks.some((c) => c.status === "warning") ? "warning" : "passed",
224
+ severity: allFindings.reduce((worst, f) => {
225
+ const order = { blocker: 4, major: 3, minor: 2, informational: 1, skipped: 0 };
226
+ return (order[f.severity] ?? 0) > (order[worst] ?? 0) ? f.severity : worst;
227
+ }, "informational"),
228
+ startedAt: checks[0]?.startedAt ?? new Date().toISOString(),
229
+ finishedAt: checks[checks.length - 1]?.finishedAt ?? new Date().toISOString(),
230
+ durationMs: checks.reduce((sum, c) => sum + c.durationMs, 0),
231
+ findings: allFindings,
232
+ };
233
+ await writeCheckResult({
234
+ result: combined,
235
+ outputPath: path.join(reportDir, "dependency-checks.json"),
236
+ rawDir: rawOutputDir,
237
+ });
238
+ return { checks, findings: allFindings };
239
+ }
@@ -0,0 +1,43 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { runSecurityCommand, skippedResult } from "../commandRunner.js";
3
+ // Detect whether osv-scanner is accessible by probing for its version flag.
4
+ function isOsvScannerAvailable() {
5
+ try {
6
+ const result = spawnSync("osv-scanner", ["--version"], { shell: false, encoding: "utf8" });
7
+ return result.error === undefined && result.status !== null;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ // Run OSV-Scanner on the lockfile if the tool is available.
14
+ // When not available and requireOsvScanner is false, returns a skipped result.
15
+ export async function runOsvScanner(options) {
16
+ if (!isOsvScannerAvailable()) {
17
+ if (options.requireOsvScanner) {
18
+ return {
19
+ command: "osv-scanner",
20
+ args: ["--lockfile", "package-lock.json", "--format", "json"],
21
+ cwd: options.cwd,
22
+ exitCode: null,
23
+ durationMs: 0,
24
+ stdout: "",
25
+ stderr: "osv-scanner not found on PATH",
26
+ timedOut: false,
27
+ skipped: false,
28
+ };
29
+ }
30
+ return skippedResult({
31
+ command: "osv-scanner",
32
+ args: ["--lockfile", "package-lock.json", "--format", "json"],
33
+ cwd: options.cwd,
34
+ reason: "osv-scanner is not installed or not on PATH; check skipped",
35
+ });
36
+ }
37
+ return runSecurityCommand({
38
+ command: "osv-scanner",
39
+ args: ["--lockfile", "package-lock.json", "--format", "json"],
40
+ cwd: options.cwd,
41
+ timeoutMs: options.timeoutMs,
42
+ });
43
+ }
@@ -0,0 +1,61 @@
1
+ import { createPrng } from "./randomInput.js";
2
+ export async function runFuzzTarget(target, options = {}) {
3
+ const seed = options.seed ?? 0xdeadbeef;
4
+ const iterations = options.iterations ?? 50;
5
+ const maxCrashes = options.maxCrashesPerTarget ?? 10;
6
+ const prng = createPrng(seed);
7
+ const crashes = [];
8
+ const started = Date.now();
9
+ for (let i = 0; i < iterations && crashes.length < maxCrashes; i++) {
10
+ const input = target.generateInput(prng, i);
11
+ try {
12
+ target.run(input);
13
+ }
14
+ catch (err) {
15
+ const errorMessage = err instanceof Error ? err.message : String(err);
16
+ const stack = err instanceof Error ? err.stack : undefined;
17
+ crashes.push({ iteration: i, input: input.slice(0, 200), errorMessage, stack });
18
+ }
19
+ }
20
+ return {
21
+ targetId: target.id,
22
+ targetName: target.name,
23
+ iterations,
24
+ crashes,
25
+ durationMs: Date.now() - started,
26
+ };
27
+ }
28
+ export async function runAllFuzzTargets(targets, options = {}) {
29
+ const startedAt = new Date().toISOString();
30
+ const started = Date.now();
31
+ const allResults = [];
32
+ for (const target of targets) {
33
+ const result = await runFuzzTarget(target, options);
34
+ allResults.push(result);
35
+ }
36
+ const finishedAt = new Date().toISOString();
37
+ const durationMs = Date.now() - started;
38
+ const allCrashes = allResults.flatMap((r) => r.crashes.map((c) => ({ targetId: r.targetId, targetName: r.targetName, ...c })));
39
+ const findings = allCrashes.map((c) => ({
40
+ id: `fuzz-crash-${c.targetId}-iter-${c.iteration}`,
41
+ title: `Fuzz crash in ${c.targetName} (iteration ${c.iteration})`,
42
+ severity: "major",
43
+ category: "fuzz-smoke",
44
+ description: c.errorMessage,
45
+ evidence: `Input (first 200 chars): ${c.input}${c.stack ? `\nStack: ${c.stack.slice(0, 300)}` : ""}`,
46
+ recommendation: "Investigate and add defensive input validation in the parser.",
47
+ releaseImpact: "Should fix before release",
48
+ }));
49
+ const totalIterations = allResults.reduce((sum, r) => sum + r.iterations, 0);
50
+ return {
51
+ id: "fuzz-smoke",
52
+ name: `Fuzz smoke (${targets.length} targets, ${totalIterations} total iterations)`,
53
+ category: "fuzz-smoke",
54
+ status: findings.length === 0 ? "passed" : "failed",
55
+ severity: findings.length === 0 ? "informational" : "major",
56
+ startedAt,
57
+ finishedAt,
58
+ durationMs,
59
+ findings,
60
+ };
61
+ }
@@ -0,0 +1,204 @@
1
+ import path from "node:path";
2
+ import { ALL_MUTATION_STRATEGIES, PATH_TRAVERSAL_INPUTS, mutateJson, randomChoice, randomInt, randomJsonString, randomString, validCodeGraphJson, validManifestJson, } from "./randomInput.js";
3
+ import { parseNpmAudit } from "../dependencies/parseNpmAudit.js";
4
+ import { parseNpmLs } from "../dependencies/parseNpmLs.js";
5
+ import { parseNpmOutdated } from "../dependencies/parseNpmOutdated.js";
6
+ import { parseNpmPackDryRun } from "../packageChecks/parseNpmPackDryRun.js";
7
+ import { escapeDotLabel } from "../cliAdversarial/subprocessSafetyChecks.js";
8
+ // ---------------------------------------------------------------------------
9
+ // Fuzz targets — parsers, helpers, and path normalization
10
+ //
11
+ // Each target must not crash on any input. Expected validation errors are OK.
12
+ // Targets are designed to be pure functions — no filesystem, no network.
13
+ // ---------------------------------------------------------------------------
14
+ // Manifest reader — expects JSON with schemaVersion, generatedAt, rootDir, files
15
+ export const manifestReaderTarget = {
16
+ id: "manifest-reader",
17
+ name: "Manifest JSON parser",
18
+ generateInput(prng, iteration) {
19
+ if (iteration % 3 === 0)
20
+ return validManifestJson();
21
+ const strategy = randomChoice(prng, ALL_MUTATION_STRATEGIES);
22
+ return mutateJson(prng, validManifestJson(), strategy);
23
+ },
24
+ run(input) {
25
+ // The manifest reader is the JSON.parse step that produces structured data.
26
+ // We replicate minimal parsing logic (same shape as what runAdversarialCheck does).
27
+ try {
28
+ JSON.parse(input);
29
+ }
30
+ catch {
31
+ return "expected: invalid JSON";
32
+ }
33
+ return undefined;
34
+ },
35
+ };
36
+ // Code-graph reader — expects JSON with nodes/edges arrays
37
+ export const codeGraphReaderTarget = {
38
+ id: "code-graph-reader",
39
+ name: "Code-graph JSON parser",
40
+ generateInput(prng, iteration) {
41
+ if (iteration % 3 === 0)
42
+ return validCodeGraphJson();
43
+ const strategy = randomChoice(prng, ALL_MUTATION_STRATEGIES);
44
+ return mutateJson(prng, validCodeGraphJson(), strategy);
45
+ },
46
+ run(input) {
47
+ try {
48
+ const parsed = JSON.parse(input);
49
+ if (!Array.isArray(parsed["nodes"]))
50
+ return "expected: missing nodes array";
51
+ if (!Array.isArray(parsed["edges"]))
52
+ return "expected: missing edges array";
53
+ }
54
+ catch {
55
+ return "expected: invalid JSON";
56
+ }
57
+ return undefined;
58
+ },
59
+ };
60
+ // npm audit JSON parser
61
+ export const npmAuditParserTarget = {
62
+ id: "npm-audit-parser",
63
+ name: "npm audit JSON parser",
64
+ generateInput(prng, iteration) {
65
+ if (iteration % 5 === 0) {
66
+ return JSON.stringify({
67
+ auditReportVersion: 2,
68
+ vulnerabilities: {},
69
+ metadata: { vulnerabilities: { total: 0 } },
70
+ });
71
+ }
72
+ const strategy = randomChoice(prng, ALL_MUTATION_STRATEGIES);
73
+ return mutateJson(prng, randomJsonString(prng, 3), strategy);
74
+ },
75
+ run(input) {
76
+ parseNpmAudit(input, "fuzz-test");
77
+ return undefined;
78
+ },
79
+ };
80
+ // npm ls JSON parser
81
+ export const npmLsParserTarget = {
82
+ id: "npm-ls-parser",
83
+ name: "npm ls JSON parser",
84
+ generateInput(prng, iteration) {
85
+ if (iteration % 5 === 0) {
86
+ return JSON.stringify({
87
+ name: "my-dev-kit-lab",
88
+ version: "0.1.2",
89
+ dependencies: {},
90
+ });
91
+ }
92
+ const strategy = randomChoice(prng, ALL_MUTATION_STRATEGIES);
93
+ return mutateJson(prng, randomJsonString(prng, 2), strategy);
94
+ },
95
+ run(input) {
96
+ parseNpmLs(input, "fuzz-test");
97
+ return undefined;
98
+ },
99
+ };
100
+ // npm outdated JSON parser
101
+ export const npmOutdatedParserTarget = {
102
+ id: "npm-outdated-parser",
103
+ name: "npm outdated JSON parser",
104
+ generateInput(prng, iteration) {
105
+ if (iteration % 5 === 0) {
106
+ return JSON.stringify({});
107
+ }
108
+ const strategy = randomChoice(prng, ALL_MUTATION_STRATEGIES);
109
+ return mutateJson(prng, randomJsonString(prng, 2), strategy);
110
+ },
111
+ run(input) {
112
+ parseNpmOutdated(input, "fuzz-test");
113
+ return undefined;
114
+ },
115
+ };
116
+ // npm pack --dry-run output parser
117
+ export const npmPackDryRunParserTarget = {
118
+ id: "npm-pack-dry-run-parser",
119
+ name: "npm pack --dry-run output parser",
120
+ generateInput(prng, iteration) {
121
+ if (iteration % 3 === 0) {
122
+ return JSON.stringify([{
123
+ id: "my-dev-kit-lab@0.1.2",
124
+ name: "my-dev-kit-lab",
125
+ version: "0.1.2",
126
+ filename: "my-dev-kit-lab-0.1.2.tgz",
127
+ files: [],
128
+ entryCount: 0,
129
+ bundled: [],
130
+ }]);
131
+ }
132
+ return randomChoice(prng, ALL_MUTATION_STRATEGIES) === "replace-with-empty"
133
+ ? ""
134
+ : randomJsonString(prng, 2);
135
+ },
136
+ run(input) {
137
+ parseNpmPackDryRun(input);
138
+ return undefined;
139
+ },
140
+ };
141
+ // DOT label escaping — must never crash on any input
142
+ export const dotLabelEscapingTarget = {
143
+ id: "dot-label-escaping",
144
+ name: "DOT label escaping",
145
+ generateInput(prng, _iteration) {
146
+ return randomString(prng, 256);
147
+ },
148
+ run(input) {
149
+ escapeDotLabel(input);
150
+ return undefined;
151
+ },
152
+ };
153
+ // Path normalization — path inputs must not produce unexpected crashes
154
+ export const pathNormalizationTarget = {
155
+ id: "path-normalization",
156
+ name: "Path normalization (traversal inputs)",
157
+ generateInput(prng, iteration) {
158
+ if (iteration < PATH_TRAVERSAL_INPUTS.length) {
159
+ return PATH_TRAVERSAL_INPUTS[iteration];
160
+ }
161
+ return randomChoice(prng, PATH_TRAVERSAL_INPUTS);
162
+ },
163
+ run(input) {
164
+ path.normalize(input);
165
+ path.resolve("/base", input);
166
+ return undefined;
167
+ },
168
+ };
169
+ // Source windowing — negative, zero, huge, out-of-range window sizes
170
+ export const sourceWindowingTarget = {
171
+ id: "source-windowing",
172
+ name: "Source retrieval windowing (window size edge cases)",
173
+ generateInput(prng, _iteration) {
174
+ const windowSizes = [-1, 0, 1, 5, 100, 10_000, Number.MAX_SAFE_INTEGER, NaN, Infinity];
175
+ const size = randomChoice(prng, windowSizes);
176
+ return JSON.stringify({ startLine: randomInt(prng, -10, 1000), windowSize: size });
177
+ },
178
+ run(input) {
179
+ try {
180
+ const parsed = JSON.parse(input);
181
+ const startLine = Number(parsed["startLine"]);
182
+ const windowSize = Number(parsed["windowSize"]);
183
+ // Simulate windowing math — must not throw
184
+ const safeStart = Math.max(0, isFinite(startLine) ? startLine : 0);
185
+ const safeWindow = isFinite(windowSize) && windowSize > 0 ? Math.min(windowSize, 10_000) : 50;
186
+ void (safeStart + safeWindow);
187
+ }
188
+ catch {
189
+ return "expected: invalid input";
190
+ }
191
+ return undefined;
192
+ },
193
+ };
194
+ export const ALL_FUZZ_TARGETS = [
195
+ manifestReaderTarget,
196
+ codeGraphReaderTarget,
197
+ npmAuditParserTarget,
198
+ npmLsParserTarget,
199
+ npmOutdatedParserTarget,
200
+ npmPackDryRunParserTarget,
201
+ dotLabelEscapingTarget,
202
+ pathNormalizationTarget,
203
+ sourceWindowingTarget,
204
+ ];
@@ -0,0 +1,34 @@
1
+ export { SECURITY_SEVERITIES, RELEASE_VERDICTS, SECURITY_CHECK_STATUSES, SECURITY_CHECK_CATEGORIES, } from "./types.js";
2
+ export { DEFAULT_SECURITY_CONFIG } from "./config.js";
3
+ export { SECURITY_TEST_MATRIX } from "./testMatrix.js";
4
+ export { runSecurityCommand, skippedResult, resolveNpmCommand } from "./commandRunner.js";
5
+ export { writeCheckResult } from "./artifacts.js";
6
+ export { parseNpmAudit } from "./dependencies/parseNpmAudit.js";
7
+ export { parseNpmLs } from "./dependencies/parseNpmLs.js";
8
+ export { parseNpmOutdated } from "./dependencies/parseNpmOutdated.js";
9
+ export { runOsvScanner } from "./dependencies/runOsvScanner.js";
10
+ export { runDependencyChecks } from "./dependencies/runDependencyChecks.js";
11
+ export { parseNpmPackDryRun } from "./packageChecks/parseNpmPackDryRun.js";
12
+ export { detectForbiddenContents } from "./packageChecks/forbiddenPackageContents.js";
13
+ export { runPackageChecks } from "./packageChecks/runPackageChecks.js";
14
+ export { createTempWorkspace, snapshotDir, diffSnapshots, findWritesOutside, findNewFiles } from "./cliAdversarial/tempWorkspace.js";
15
+ export { getAdversarialCliTarget, buildCliCommand } from "./cliAdversarial/adversarialCliConfig.js";
16
+ export { ALL_PATH_TEST_INPUTS, PATH_TRAVERSAL_CASES, ABSOLUTE_PATH_CASES, SPACES_PATH_CASES, METACHAR_PATH_CASES, UNICODE_PATH_CASES, LONG_NAME_CASES, MISSING_PATH_CASES } from "./cliAdversarial/pathCases.js";
17
+ export { runAdversarialCheck, skippedCheck, makeFinding } from "./cliAdversarial/runAdversarialCheck.js";
18
+ export { checkRootPathTraversal, checkOutPathTraversal, checkIndexPathTraversal, checkPathWithSpaces, checkUnicodePath, checkSafeAbsolutePath, checkHarnessEscapeDetection } from "./cliAdversarial/pathBoundaryChecks.js";
19
+ export { checkSourceFilesNotModified, checkWritesLimitedToOutput, checkIndexWriteContainment, checkArtifactCleanupSafe } from "./cliAdversarial/readOnlyBoundaryChecks.js";
20
+ export { MALFORMED_MANIFEST_CASES, MALFORMED_CODE_GRAPH_CASES, UNSUPPORTED_SCHEMA_VERSION_CASES, placeMalformedArtifact, placeMalformedManifest, placeMalformedCodeGraph, placeUnsupportedSchemaManifest } from "./cliAdversarial/malformedArtifactFixtures.js";
21
+ export { checkMalformedManifest, checkAllMalformedManifestCases, checkMalformedCodeGraph, checkUnsupportedSchemaVersion, checkMissingIndexDirectory } from "./cliAdversarial/malformedArtifactChecks.js";
22
+ export { checkJsonOutputIsParseable, checkStderrNotInStdout, checkFailureProducesJsonError, checkProgressNotInJsonStdout } from "./cliAdversarial/jsonStdoutChecks.js";
23
+ export { DOT_LABEL_TEST_CASES, escapeDotLabel, checkSubprocessNoShellInterpolation, checkDotLabelEscaping } from "./cliAdversarial/subprocessSafetyChecks.js";
24
+ export { checkHugeSourceFile, checkManyFiles, checkDeeplyNestedSource } from "./cliAdversarial/dataVolumeChecks.js";
25
+ export { runCodeqlCheck } from "./staticScans/codeql.js";
26
+ export { runSemgrepCheck, parseSemgrepJson } from "./staticScans/semgrep.js";
27
+ export { createPrng, randomInt, randomChoice, randomString, randomJsonValue, randomJsonString, mutateJson, validManifestJson, validCodeGraphJson } from "./fuzz/randomInput.js";
28
+ export { ALL_MUTATION_STRATEGIES, PATH_TRAVERSAL_INPUTS } from "./fuzz/randomInput.js";
29
+ export { runFuzzTarget, runAllFuzzTargets } from "./fuzz/fuzzHarness.js";
30
+ export { ALL_FUZZ_TARGETS, manifestReaderTarget, codeGraphReaderTarget, dotLabelEscapingTarget, pathNormalizationTarget, sourceWindowingTarget } from "./fuzz/fuzzTargets.js";
31
+ export { calculateVerdict, verdictToHumanLabel } from "./validate/verdict.js";
32
+ export { resolveValidationTarget, reportFilenamePrefix, targetDescription } from "./validate/resolveTarget.js";
33
+ export { runSecurityValidation } from "./validate/runSecurityValidation.js";
34
+ export { renderTextReport, renderJsonReport } from "./report/renderSecurityReport.js";
@@ -0,0 +1,67 @@
1
+ // Normalize a tarball path for pattern matching.
2
+ // npm pack prefixes files with "package/", which we strip.
3
+ function normalizeTarballPath(p) {
4
+ const normalized = p.replace(/\\/g, "/");
5
+ return normalized.startsWith("package/") ? normalized.slice("package/".length) : normalized;
6
+ }
7
+ // Check whether a file path matches a forbidden pattern.
8
+ // Patterns ending with "/" match directory prefixes.
9
+ // Patterns starting with "*" are suffix matches.
10
+ // Exact string matches are also supported.
11
+ function matchesForbiddenPattern(normalizedPath, pattern) {
12
+ const p = pattern.toLowerCase();
13
+ const f = normalizedPath.toLowerCase();
14
+ if (p.endsWith("/")) {
15
+ return f.startsWith(p) || f === p.slice(0, -1);
16
+ }
17
+ if (p.startsWith("*.")) {
18
+ return f.endsWith(p.slice(1));
19
+ }
20
+ if (p.includes("*")) {
21
+ // Simple glob: split on * and check prefix/suffix
22
+ const parts = p.split("*").filter(Boolean);
23
+ if (parts.length === 1)
24
+ return f.includes(parts[0]);
25
+ return f.startsWith(parts[0]) && f.endsWith(parts[parts.length - 1]);
26
+ }
27
+ return f === p || f.startsWith(p + "/");
28
+ }
29
+ // Detect files in the tarball file list that match forbidden patterns.
30
+ export function detectForbiddenContents(options) {
31
+ const matches = [];
32
+ for (const rawFile of options.files) {
33
+ const normalized = normalizeTarballPath(rawFile);
34
+ const isException = options.allowedExceptions.some((ex) => matchesForbiddenPattern(normalized, ex));
35
+ if (isException)
36
+ continue;
37
+ for (const pattern of options.forbiddenPatterns) {
38
+ if (matchesForbiddenPattern(normalized, pattern)) {
39
+ matches.push({ file: normalized, pattern });
40
+ break;
41
+ }
42
+ }
43
+ }
44
+ const findings = matches.map((m, i) => ({
45
+ id: `${options.checkId}-forbidden-${i}`,
46
+ title: `Forbidden file in npm tarball: ${m.file}`,
47
+ severity: isCriticalForbidden(m.file) ? "blocker" : "major",
48
+ category: "package-content",
49
+ description: `The file '${m.file}' matches the forbidden pattern '${m.pattern}' and must not be included in the published package`,
50
+ evidence: `File: ${m.file}, Matched pattern: ${m.pattern}`,
51
+ affectedFiles: [m.file],
52
+ recommendation: `Add '${m.file}' to the .npmignore file or remove it from the 'files' field in package.json`,
53
+ releaseImpact: isCriticalForbidden(m.file)
54
+ ? "Blocker: this file must not be published"
55
+ : "Major: this file should not be in the published package",
56
+ }));
57
+ return { matches, findings };
58
+ }
59
+ function isCriticalForbidden(file) {
60
+ const f = file.toLowerCase();
61
+ return (f.includes(".env") ||
62
+ f.endsWith(".pem") ||
63
+ f.endsWith(".key") ||
64
+ f.endsWith(".p12") ||
65
+ f.startsWith("lab-output/") ||
66
+ f.startsWith(".my-dev-kit/"));
67
+ }