@oculum/scanner 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/formatters/cli-terminal.d.ts +27 -0
- package/dist/formatters/cli-terminal.d.ts.map +1 -0
- package/dist/formatters/cli-terminal.js +412 -0
- package/dist/formatters/cli-terminal.js.map +1 -0
- package/dist/formatters/github-comment.d.ts +41 -0
- package/dist/formatters/github-comment.d.ts.map +1 -0
- package/dist/formatters/github-comment.js +306 -0
- package/dist/formatters/github-comment.js.map +1 -0
- package/dist/formatters/grouping.d.ts +52 -0
- package/dist/formatters/grouping.d.ts.map +1 -0
- package/dist/formatters/grouping.js +152 -0
- package/dist/formatters/grouping.js.map +1 -0
- package/dist/formatters/index.d.ts +9 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +35 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/vscode-diagnostic.d.ts +103 -0
- package/dist/formatters/vscode-diagnostic.d.ts.map +1 -0
- package/dist/formatters/vscode-diagnostic.js +151 -0
- package/dist/formatters/vscode-diagnostic.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +648 -0
- package/dist/index.js.map +1 -0
- package/dist/layer1/comments.d.ts +8 -0
- package/dist/layer1/comments.d.ts.map +1 -0
- package/dist/layer1/comments.js +203 -0
- package/dist/layer1/comments.js.map +1 -0
- package/dist/layer1/config-audit.d.ts +8 -0
- package/dist/layer1/config-audit.d.ts.map +1 -0
- package/dist/layer1/config-audit.js +252 -0
- package/dist/layer1/config-audit.js.map +1 -0
- package/dist/layer1/entropy.d.ts +8 -0
- package/dist/layer1/entropy.d.ts.map +1 -0
- package/dist/layer1/entropy.js +500 -0
- package/dist/layer1/entropy.js.map +1 -0
- package/dist/layer1/file-flags.d.ts +7 -0
- package/dist/layer1/file-flags.d.ts.map +1 -0
- package/dist/layer1/file-flags.js +112 -0
- package/dist/layer1/file-flags.js.map +1 -0
- package/dist/layer1/index.d.ts +36 -0
- package/dist/layer1/index.d.ts.map +1 -0
- package/dist/layer1/index.js +132 -0
- package/dist/layer1/index.js.map +1 -0
- package/dist/layer1/patterns.d.ts +8 -0
- package/dist/layer1/patterns.d.ts.map +1 -0
- package/dist/layer1/patterns.js +482 -0
- package/dist/layer1/patterns.js.map +1 -0
- package/dist/layer1/urls.d.ts +8 -0
- package/dist/layer1/urls.d.ts.map +1 -0
- package/dist/layer1/urls.js +296 -0
- package/dist/layer1/urls.js.map +1 -0
- package/dist/layer1/weak-crypto.d.ts +7 -0
- package/dist/layer1/weak-crypto.d.ts.map +1 -0
- package/dist/layer1/weak-crypto.js +291 -0
- package/dist/layer1/weak-crypto.js.map +1 -0
- package/dist/layer2/ai-agent-tools.d.ts +19 -0
- package/dist/layer2/ai-agent-tools.d.ts.map +1 -0
- package/dist/layer2/ai-agent-tools.js +528 -0
- package/dist/layer2/ai-agent-tools.js.map +1 -0
- package/dist/layer2/ai-endpoint-protection.d.ts +36 -0
- package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -0
- package/dist/layer2/ai-endpoint-protection.js +332 -0
- package/dist/layer2/ai-endpoint-protection.js.map +1 -0
- package/dist/layer2/ai-execution-sinks.d.ts +18 -0
- package/dist/layer2/ai-execution-sinks.d.ts.map +1 -0
- package/dist/layer2/ai-execution-sinks.js +496 -0
- package/dist/layer2/ai-execution-sinks.js.map +1 -0
- package/dist/layer2/ai-fingerprinting.d.ts +7 -0
- package/dist/layer2/ai-fingerprinting.d.ts.map +1 -0
- package/dist/layer2/ai-fingerprinting.js +654 -0
- package/dist/layer2/ai-fingerprinting.js.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts +19 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.js +356 -0
- package/dist/layer2/ai-prompt-hygiene.js.map +1 -0
- package/dist/layer2/ai-rag-safety.d.ts +21 -0
- package/dist/layer2/ai-rag-safety.d.ts.map +1 -0
- package/dist/layer2/ai-rag-safety.js +459 -0
- package/dist/layer2/ai-rag-safety.js.map +1 -0
- package/dist/layer2/ai-schema-validation.d.ts +25 -0
- package/dist/layer2/ai-schema-validation.d.ts.map +1 -0
- package/dist/layer2/ai-schema-validation.js +375 -0
- package/dist/layer2/ai-schema-validation.js.map +1 -0
- package/dist/layer2/auth-antipatterns.d.ts +20 -0
- package/dist/layer2/auth-antipatterns.d.ts.map +1 -0
- package/dist/layer2/auth-antipatterns.js +333 -0
- package/dist/layer2/auth-antipatterns.js.map +1 -0
- package/dist/layer2/byok-patterns.d.ts +12 -0
- package/dist/layer2/byok-patterns.d.ts.map +1 -0
- package/dist/layer2/byok-patterns.js +299 -0
- package/dist/layer2/byok-patterns.js.map +1 -0
- package/dist/layer2/dangerous-functions.d.ts +7 -0
- package/dist/layer2/dangerous-functions.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions.js +1375 -0
- package/dist/layer2/dangerous-functions.js.map +1 -0
- package/dist/layer2/data-exposure.d.ts +16 -0
- package/dist/layer2/data-exposure.d.ts.map +1 -0
- package/dist/layer2/data-exposure.js +279 -0
- package/dist/layer2/data-exposure.js.map +1 -0
- package/dist/layer2/framework-checks.d.ts +7 -0
- package/dist/layer2/framework-checks.d.ts.map +1 -0
- package/dist/layer2/framework-checks.js +388 -0
- package/dist/layer2/framework-checks.js.map +1 -0
- package/dist/layer2/index.d.ts +58 -0
- package/dist/layer2/index.d.ts.map +1 -0
- package/dist/layer2/index.js +380 -0
- package/dist/layer2/index.js.map +1 -0
- package/dist/layer2/logic-gates.d.ts +7 -0
- package/dist/layer2/logic-gates.d.ts.map +1 -0
- package/dist/layer2/logic-gates.js +182 -0
- package/dist/layer2/logic-gates.js.map +1 -0
- package/dist/layer2/risky-imports.d.ts +7 -0
- package/dist/layer2/risky-imports.d.ts.map +1 -0
- package/dist/layer2/risky-imports.js +161 -0
- package/dist/layer2/risky-imports.js.map +1 -0
- package/dist/layer2/variables.d.ts +8 -0
- package/dist/layer2/variables.d.ts.map +1 -0
- package/dist/layer2/variables.js +152 -0
- package/dist/layer2/variables.js.map +1 -0
- package/dist/layer3/anthropic.d.ts +83 -0
- package/dist/layer3/anthropic.d.ts.map +1 -0
- package/dist/layer3/anthropic.js +1745 -0
- package/dist/layer3/anthropic.js.map +1 -0
- package/dist/layer3/index.d.ts +24 -0
- package/dist/layer3/index.d.ts.map +1 -0
- package/dist/layer3/index.js +119 -0
- package/dist/layer3/index.js.map +1 -0
- package/dist/layer3/openai.d.ts +25 -0
- package/dist/layer3/openai.d.ts.map +1 -0
- package/dist/layer3/openai.js +238 -0
- package/dist/layer3/openai.js.map +1 -0
- package/dist/layer3/package-check.d.ts +63 -0
- package/dist/layer3/package-check.d.ts.map +1 -0
- package/dist/layer3/package-check.js +508 -0
- package/dist/layer3/package-check.js.map +1 -0
- package/dist/modes/incremental.d.ts +66 -0
- package/dist/modes/incremental.d.ts.map +1 -0
- package/dist/modes/incremental.js +200 -0
- package/dist/modes/incremental.js.map +1 -0
- package/dist/tiers.d.ts +125 -0
- package/dist/tiers.d.ts.map +1 -0
- package/dist/tiers.js +234 -0
- package/dist/tiers.js.map +1 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +50 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/auth-helper-detector.d.ts +56 -0
- package/dist/utils/auth-helper-detector.d.ts.map +1 -0
- package/dist/utils/auth-helper-detector.js +360 -0
- package/dist/utils/auth-helper-detector.js.map +1 -0
- package/dist/utils/context-helpers.d.ts +96 -0
- package/dist/utils/context-helpers.d.ts.map +1 -0
- package/dist/utils/context-helpers.js +493 -0
- package/dist/utils/context-helpers.js.map +1 -0
- package/dist/utils/diff-detector.d.ts +53 -0
- package/dist/utils/diff-detector.d.ts.map +1 -0
- package/dist/utils/diff-detector.js +104 -0
- package/dist/utils/diff-detector.js.map +1 -0
- package/dist/utils/diff-parser.d.ts +80 -0
- package/dist/utils/diff-parser.d.ts.map +1 -0
- package/dist/utils/diff-parser.js +202 -0
- package/dist/utils/diff-parser.js.map +1 -0
- package/dist/utils/imported-auth-detector.d.ts +37 -0
- package/dist/utils/imported-auth-detector.d.ts.map +1 -0
- package/dist/utils/imported-auth-detector.js +251 -0
- package/dist/utils/imported-auth-detector.js.map +1 -0
- package/dist/utils/middleware-detector.d.ts +55 -0
- package/dist/utils/middleware-detector.d.ts.map +1 -0
- package/dist/utils/middleware-detector.js +260 -0
- package/dist/utils/middleware-detector.js.map +1 -0
- package/dist/utils/oauth-flow-detector.d.ts +41 -0
- package/dist/utils/oauth-flow-detector.d.ts.map +1 -0
- package/dist/utils/oauth-flow-detector.js +202 -0
- package/dist/utils/oauth-flow-detector.js.map +1 -0
- package/dist/utils/path-exclusions.d.ts +55 -0
- package/dist/utils/path-exclusions.d.ts.map +1 -0
- package/dist/utils/path-exclusions.js +222 -0
- package/dist/utils/path-exclusions.js.map +1 -0
- package/dist/utils/project-context-builder.d.ts +119 -0
- package/dist/utils/project-context-builder.d.ts.map +1 -0
- package/dist/utils/project-context-builder.js +534 -0
- package/dist/utils/project-context-builder.js.map +1 -0
- package/dist/utils/registry-clients.d.ts +93 -0
- package/dist/utils/registry-clients.d.ts.map +1 -0
- package/dist/utils/registry-clients.js +273 -0
- package/dist/utils/registry-clients.js.map +1 -0
- package/dist/utils/trpc-analyzer.d.ts +78 -0
- package/dist/utils/trpc-analyzer.d.ts.map +1 -0
- package/dist/utils/trpc-analyzer.js +297 -0
- package/dist/utils/trpc-analyzer.js.map +1 -0
- package/package.json +45 -0
- package/src/__tests__/benchmark/fixtures/false-positives.ts +227 -0
- package/src/__tests__/benchmark/fixtures/index.ts +68 -0
- package/src/__tests__/benchmark/fixtures/layer1/config-audit.ts +364 -0
- package/src/__tests__/benchmark/fixtures/layer1/hardcoded-secrets.ts +173 -0
- package/src/__tests__/benchmark/fixtures/layer1/high-entropy.ts +234 -0
- package/src/__tests__/benchmark/fixtures/layer1/index.ts +31 -0
- package/src/__tests__/benchmark/fixtures/layer1/sensitive-urls.ts +90 -0
- package/src/__tests__/benchmark/fixtures/layer1/weak-crypto.ts +197 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-agent-tools.ts +170 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-endpoint-protection.ts +418 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +189 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-fingerprinting.ts +316 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +178 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +184 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-schema-validation.ts +434 -0
- package/src/__tests__/benchmark/fixtures/layer2/auth-antipatterns.ts +159 -0
- package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +112 -0
- package/src/__tests__/benchmark/fixtures/layer2/dangerous-functions.ts +246 -0
- package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +168 -0
- package/src/__tests__/benchmark/fixtures/layer2/framework-checks.ts +346 -0
- package/src/__tests__/benchmark/fixtures/layer2/index.ts +67 -0
- package/src/__tests__/benchmark/fixtures/layer2/injection-vulnerabilities.ts +239 -0
- package/src/__tests__/benchmark/fixtures/layer2/logic-gates.ts +246 -0
- package/src/__tests__/benchmark/fixtures/layer2/risky-imports.ts +231 -0
- package/src/__tests__/benchmark/fixtures/layer2/variables.ts +167 -0
- package/src/__tests__/benchmark/index.ts +29 -0
- package/src/__tests__/benchmark/run-benchmark.ts +144 -0
- package/src/__tests__/benchmark/run-depth-validation.ts +206 -0
- package/src/__tests__/benchmark/run-real-world-test.ts +243 -0
- package/src/__tests__/benchmark/security-benchmark-script.ts +1737 -0
- package/src/__tests__/benchmark/tier-integration-script.ts +177 -0
- package/src/__tests__/benchmark/types.ts +144 -0
- package/src/__tests__/benchmark/utils/test-runner.ts +475 -0
- package/src/__tests__/regression/known-false-positives.test.ts +467 -0
- package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +178 -0
- package/src/__tests__/snapshots/scan-depth.test.ts +258 -0
- package/src/__tests__/validation/analyze-results.ts +542 -0
- package/src/__tests__/validation/extract-for-triage.ts +146 -0
- package/src/__tests__/validation/fp-deep-analysis.ts +327 -0
- package/src/__tests__/validation/run-validation.ts +364 -0
- package/src/__tests__/validation/triage-template.md +132 -0
- package/src/formatters/cli-terminal.ts +446 -0
- package/src/formatters/github-comment.ts +382 -0
- package/src/formatters/grouping.ts +190 -0
- package/src/formatters/index.ts +47 -0
- package/src/formatters/vscode-diagnostic.ts +243 -0
- package/src/index.ts +823 -0
- package/src/layer1/comments.ts +218 -0
- package/src/layer1/config-audit.ts +289 -0
- package/src/layer1/entropy.ts +583 -0
- package/src/layer1/file-flags.ts +127 -0
- package/src/layer1/index.ts +181 -0
- package/src/layer1/patterns.ts +516 -0
- package/src/layer1/urls.ts +334 -0
- package/src/layer1/weak-crypto.ts +328 -0
- package/src/layer2/ai-agent-tools.ts +601 -0
- package/src/layer2/ai-endpoint-protection.ts +387 -0
- package/src/layer2/ai-execution-sinks.ts +580 -0
- package/src/layer2/ai-fingerprinting.ts +758 -0
- package/src/layer2/ai-prompt-hygiene.ts +411 -0
- package/src/layer2/ai-rag-safety.ts +511 -0
- package/src/layer2/ai-schema-validation.ts +421 -0
- package/src/layer2/auth-antipatterns.ts +394 -0
- package/src/layer2/byok-patterns.ts +336 -0
- package/src/layer2/dangerous-functions.ts +1563 -0
- package/src/layer2/data-exposure.ts +315 -0
- package/src/layer2/framework-checks.ts +433 -0
- package/src/layer2/index.ts +473 -0
- package/src/layer2/logic-gates.ts +206 -0
- package/src/layer2/risky-imports.ts +186 -0
- package/src/layer2/variables.ts +166 -0
- package/src/layer3/anthropic.ts +2030 -0
- package/src/layer3/index.ts +130 -0
- package/src/layer3/package-check.ts +604 -0
- package/src/modes/incremental.ts +293 -0
- package/src/tiers.ts +318 -0
- package/src/types.ts +284 -0
- package/src/utils/auth-helper-detector.ts +443 -0
- package/src/utils/context-helpers.ts +535 -0
- package/src/utils/diff-detector.ts +135 -0
- package/src/utils/diff-parser.ts +272 -0
- package/src/utils/imported-auth-detector.ts +320 -0
- package/src/utils/middleware-detector.ts +333 -0
- package/src/utils/oauth-flow-detector.ts +246 -0
- package/src/utils/path-exclusions.ts +266 -0
- package/src/utils/project-context-builder.ts +707 -0
- package/src/utils/registry-clients.ts +351 -0
- package/src/utils/trpc-analyzer.ts +382 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* M7: Results Analysis Script
|
|
4
|
+
*
|
|
5
|
+
* Analyzes scan results from real-repo validation and generates
|
|
6
|
+
* summary metrics for documentation.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx tsx packages/scanner/src/__tests__/validation/analyze-results.ts
|
|
10
|
+
* npx tsx packages/scanner/src/__tests__/validation/analyze-results.ts --output docs/RESULTSCOMPARISON.md
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as fs from 'fs'
|
|
14
|
+
import * as path from 'path'
|
|
15
|
+
import type { ScanResult, Vulnerability, VulnerabilityCategory, VulnerabilitySeverity } from '../../types'
|
|
16
|
+
|
|
17
|
+
const RESULTS_DIR = path.join(__dirname, '../../../validation-results')
|
|
18
|
+
|
|
19
|
+
// M5 AI-era categories we're specifically validating
|
|
20
|
+
const M5_CATEGORIES: VulnerabilityCategory[] = [
|
|
21
|
+
'ai_rag_exfiltration',
|
|
22
|
+
'ai_endpoint_unprotected',
|
|
23
|
+
'ai_schema_mismatch',
|
|
24
|
+
'ai_prompt_injection',
|
|
25
|
+
'ai_unsafe_execution',
|
|
26
|
+
'ai_overpermissive_tool',
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
interface FindingSummary {
|
|
30
|
+
category: VulnerabilityCategory
|
|
31
|
+
count: number
|
|
32
|
+
bySeverity: Record<VulnerabilitySeverity, number>
|
|
33
|
+
examples: Array<{
|
|
34
|
+
file: string
|
|
35
|
+
line: number
|
|
36
|
+
title: string
|
|
37
|
+
severity: VulnerabilitySeverity
|
|
38
|
+
}>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface RepoAnalysis {
|
|
42
|
+
repoName: string
|
|
43
|
+
scanDepth: string
|
|
44
|
+
totalFindings: number
|
|
45
|
+
mediumPlusFindings: number
|
|
46
|
+
filesScanned: number
|
|
47
|
+
scanDuration: number
|
|
48
|
+
bySeverity: Record<VulnerabilitySeverity, number>
|
|
49
|
+
byCategory: Record<string, number>
|
|
50
|
+
m5Findings: FindingSummary[]
|
|
51
|
+
topCategories: Array<{ category: string; count: number }>
|
|
52
|
+
libraryCodeFindings: number
|
|
53
|
+
exampleCodeFindings: number
|
|
54
|
+
testCodeFindings: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface ValidationReport {
|
|
58
|
+
generatedAt: string
|
|
59
|
+
totalRepos: number
|
|
60
|
+
totalScans: number
|
|
61
|
+
analyses: RepoAnalysis[]
|
|
62
|
+
aggregateMetrics: {
|
|
63
|
+
totalFindings: number
|
|
64
|
+
totalMediumPlus: number
|
|
65
|
+
avgFindingsPerRepo: number
|
|
66
|
+
m5DetectorPerformance: Record<string, { total: number; mediumPlus: number }>
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Classify file path as library internal, example, or test code
|
|
72
|
+
*/
|
|
73
|
+
function classifyFilePath(filePath: string): 'library' | 'example' | 'test' | 'other' {
|
|
74
|
+
const lowerPath = filePath.toLowerCase()
|
|
75
|
+
|
|
76
|
+
// Test files
|
|
77
|
+
if (
|
|
78
|
+
lowerPath.includes('__tests__') ||
|
|
79
|
+
lowerPath.includes('/test/') ||
|
|
80
|
+
lowerPath.includes('/tests/') ||
|
|
81
|
+
lowerPath.includes('.test.') ||
|
|
82
|
+
lowerPath.includes('.spec.') ||
|
|
83
|
+
lowerPath.includes('_test.') ||
|
|
84
|
+
lowerPath.includes('/fixtures/')
|
|
85
|
+
) {
|
|
86
|
+
return 'test'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Example/demo files
|
|
90
|
+
if (
|
|
91
|
+
lowerPath.includes('/examples/') ||
|
|
92
|
+
lowerPath.includes('/example/') ||
|
|
93
|
+
lowerPath.includes('/demos/') ||
|
|
94
|
+
lowerPath.includes('/demo/') ||
|
|
95
|
+
lowerPath.includes('/templates/') ||
|
|
96
|
+
lowerPath.includes('/cookbook/')
|
|
97
|
+
) {
|
|
98
|
+
return 'example'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Library internal code
|
|
102
|
+
if (
|
|
103
|
+
lowerPath.includes('/src/') ||
|
|
104
|
+
lowerPath.includes('/lib/') ||
|
|
105
|
+
lowerPath.includes('/libs/') ||
|
|
106
|
+
lowerPath.includes('/packages/')
|
|
107
|
+
) {
|
|
108
|
+
return 'library'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return 'other'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Load scan results from JSON files
|
|
116
|
+
*/
|
|
117
|
+
function loadResults(): Map<string, ScanResult> {
|
|
118
|
+
const results = new Map<string, ScanResult>()
|
|
119
|
+
|
|
120
|
+
if (!fs.existsSync(RESULTS_DIR)) {
|
|
121
|
+
console.error(`Results directory not found: ${RESULTS_DIR}`)
|
|
122
|
+
console.error('Run run-validation.ts first to generate results.')
|
|
123
|
+
process.exit(1)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const files = fs.readdirSync(RESULTS_DIR).filter(f =>
|
|
127
|
+
f.endsWith('.json') && !f.includes('medium-plus')
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if (files.length === 0) {
|
|
131
|
+
console.error('No result files found in', RESULTS_DIR)
|
|
132
|
+
console.error('Run run-validation.ts first to generate results.')
|
|
133
|
+
process.exit(1)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (const file of files) {
|
|
137
|
+
const filePath = path.join(RESULTS_DIR, file)
|
|
138
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
139
|
+
const result = JSON.parse(content) as ScanResult
|
|
140
|
+
const key = file.replace('.json', '')
|
|
141
|
+
results.set(key, result)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return results
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Analyze a single scan result
|
|
149
|
+
*/
|
|
150
|
+
function analyzeResult(key: string, result: ScanResult): RepoAnalysis {
|
|
151
|
+
const [repoName, scanDepth] = key.split('-')
|
|
152
|
+
|
|
153
|
+
// Severity distribution
|
|
154
|
+
const bySeverity: Record<VulnerabilitySeverity, number> = {
|
|
155
|
+
critical: 0,
|
|
156
|
+
high: 0,
|
|
157
|
+
medium: 0,
|
|
158
|
+
low: 0,
|
|
159
|
+
info: 0,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Category distribution
|
|
163
|
+
const byCategory: Record<string, number> = {}
|
|
164
|
+
|
|
165
|
+
// Code type distribution
|
|
166
|
+
let libraryCodeFindings = 0
|
|
167
|
+
let exampleCodeFindings = 0
|
|
168
|
+
let testCodeFindings = 0
|
|
169
|
+
|
|
170
|
+
// M5 detector findings
|
|
171
|
+
const m5FindingsMap = new Map<VulnerabilityCategory, Vulnerability[]>()
|
|
172
|
+
for (const cat of M5_CATEGORIES) {
|
|
173
|
+
m5FindingsMap.set(cat, [])
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Process each finding
|
|
177
|
+
for (const vuln of result.vulnerabilities) {
|
|
178
|
+
// Severity
|
|
179
|
+
bySeverity[vuln.severity]++
|
|
180
|
+
|
|
181
|
+
// Category
|
|
182
|
+
byCategory[vuln.category] = (byCategory[vuln.category] || 0) + 1
|
|
183
|
+
|
|
184
|
+
// Code type
|
|
185
|
+
const codeType = classifyFilePath(vuln.filePath)
|
|
186
|
+
if (codeType === 'library') libraryCodeFindings++
|
|
187
|
+
else if (codeType === 'example') exampleCodeFindings++
|
|
188
|
+
else if (codeType === 'test') testCodeFindings++
|
|
189
|
+
|
|
190
|
+
// M5 categories
|
|
191
|
+
if (M5_CATEGORIES.includes(vuln.category as VulnerabilityCategory)) {
|
|
192
|
+
m5FindingsMap.get(vuln.category as VulnerabilityCategory)!.push(vuln)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Build M5 findings summary
|
|
197
|
+
const m5Findings: FindingSummary[] = []
|
|
198
|
+
for (const [category, findings] of m5FindingsMap) {
|
|
199
|
+
if (findings.length === 0) continue
|
|
200
|
+
|
|
201
|
+
const severityBreakdown: Record<VulnerabilitySeverity, number> = {
|
|
202
|
+
critical: 0,
|
|
203
|
+
high: 0,
|
|
204
|
+
medium: 0,
|
|
205
|
+
low: 0,
|
|
206
|
+
info: 0,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (const f of findings) {
|
|
210
|
+
severityBreakdown[f.severity]++
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Get top 5 examples
|
|
214
|
+
const examples = findings.slice(0, 5).map(f => ({
|
|
215
|
+
file: f.filePath,
|
|
216
|
+
line: f.lineNumber,
|
|
217
|
+
title: f.title,
|
|
218
|
+
severity: f.severity,
|
|
219
|
+
}))
|
|
220
|
+
|
|
221
|
+
m5Findings.push({
|
|
222
|
+
category,
|
|
223
|
+
count: findings.length,
|
|
224
|
+
bySeverity: severityBreakdown,
|
|
225
|
+
examples,
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Sort m5Findings by count
|
|
230
|
+
m5Findings.sort((a, b) => b.count - a.count)
|
|
231
|
+
|
|
232
|
+
// Top categories
|
|
233
|
+
const topCategories = Object.entries(byCategory)
|
|
234
|
+
.sort(([, a], [, b]) => b - a)
|
|
235
|
+
.slice(0, 10)
|
|
236
|
+
.map(([category, count]) => ({ category, count }))
|
|
237
|
+
|
|
238
|
+
// Medium+ count
|
|
239
|
+
const mediumPlusFindings = bySeverity.critical + bySeverity.high + bySeverity.medium
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
repoName,
|
|
243
|
+
scanDepth,
|
|
244
|
+
totalFindings: result.vulnerabilities.length,
|
|
245
|
+
mediumPlusFindings,
|
|
246
|
+
filesScanned: result.filesScanned,
|
|
247
|
+
scanDuration: result.scanDuration,
|
|
248
|
+
bySeverity,
|
|
249
|
+
byCategory,
|
|
250
|
+
m5Findings,
|
|
251
|
+
topCategories,
|
|
252
|
+
libraryCodeFindings,
|
|
253
|
+
exampleCodeFindings,
|
|
254
|
+
testCodeFindings,
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Generate aggregate metrics across all analyses
|
|
260
|
+
*/
|
|
261
|
+
function computeAggregateMetrics(analyses: RepoAnalysis[]): ValidationReport['aggregateMetrics'] {
|
|
262
|
+
let totalFindings = 0
|
|
263
|
+
let totalMediumPlus = 0
|
|
264
|
+
|
|
265
|
+
const m5Performance: Record<string, { total: number; mediumPlus: number }> = {}
|
|
266
|
+
for (const cat of M5_CATEGORIES) {
|
|
267
|
+
m5Performance[cat] = { total: 0, mediumPlus: 0 }
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (const analysis of analyses) {
|
|
271
|
+
totalFindings += analysis.totalFindings
|
|
272
|
+
totalMediumPlus += analysis.mediumPlusFindings
|
|
273
|
+
|
|
274
|
+
for (const m5 of analysis.m5Findings) {
|
|
275
|
+
m5Performance[m5.category].total += m5.count
|
|
276
|
+
m5Performance[m5.category].mediumPlus +=
|
|
277
|
+
m5.bySeverity.critical + m5.bySeverity.high + m5.bySeverity.medium
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
totalFindings,
|
|
283
|
+
totalMediumPlus,
|
|
284
|
+
avgFindingsPerRepo: analyses.length > 0 ? Math.round(totalFindings / analyses.length) : 0,
|
|
285
|
+
m5DetectorPerformance: m5Performance,
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Generate markdown report
|
|
291
|
+
*/
|
|
292
|
+
function generateMarkdownReport(report: ValidationReport): string {
|
|
293
|
+
const lines: string[] = []
|
|
294
|
+
|
|
295
|
+
lines.push('# M7: Real-Repo Validation Results')
|
|
296
|
+
lines.push('')
|
|
297
|
+
lines.push(`> Generated: ${report.generatedAt}`)
|
|
298
|
+
lines.push('')
|
|
299
|
+
|
|
300
|
+
// Executive Summary
|
|
301
|
+
lines.push('## Executive Summary')
|
|
302
|
+
lines.push('')
|
|
303
|
+
lines.push(`- **Repositories scanned:** ${report.totalRepos}`)
|
|
304
|
+
lines.push(`- **Total scans:** ${report.totalScans}`)
|
|
305
|
+
lines.push(`- **Total findings:** ${report.aggregateMetrics.totalFindings}`)
|
|
306
|
+
lines.push(`- **Medium+ findings (to triage):** ${report.aggregateMetrics.totalMediumPlus}`)
|
|
307
|
+
lines.push('')
|
|
308
|
+
|
|
309
|
+
// M5 Detector Performance
|
|
310
|
+
lines.push('## M5 AI-Era Detector Performance')
|
|
311
|
+
lines.push('')
|
|
312
|
+
lines.push('| Detector | Total Findings | Medium+ |')
|
|
313
|
+
lines.push('|----------|----------------|---------|')
|
|
314
|
+
|
|
315
|
+
for (const [category, stats] of Object.entries(report.aggregateMetrics.m5DetectorPerformance)) {
|
|
316
|
+
if (stats.total > 0) {
|
|
317
|
+
lines.push(`| ${category} | ${stats.total} | ${stats.mediumPlus} |`)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const emptyM5 = Object.entries(report.aggregateMetrics.m5DetectorPerformance)
|
|
322
|
+
.filter(([, stats]) => stats.total === 0)
|
|
323
|
+
.map(([cat]) => cat)
|
|
324
|
+
|
|
325
|
+
if (emptyM5.length > 0) {
|
|
326
|
+
lines.push('')
|
|
327
|
+
lines.push(`**No findings for:** ${emptyM5.join(', ')}`)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
lines.push('')
|
|
331
|
+
|
|
332
|
+
// Per-Repo Results
|
|
333
|
+
lines.push('## Per-Repository Results')
|
|
334
|
+
lines.push('')
|
|
335
|
+
|
|
336
|
+
// Group analyses by repo
|
|
337
|
+
const byRepo = new Map<string, RepoAnalysis[]>()
|
|
338
|
+
for (const analysis of report.analyses) {
|
|
339
|
+
const existing = byRepo.get(analysis.repoName) || []
|
|
340
|
+
existing.push(analysis)
|
|
341
|
+
byRepo.set(analysis.repoName, existing)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
for (const [repoName, analyses] of byRepo) {
|
|
345
|
+
lines.push(`### ${repoName}`)
|
|
346
|
+
lines.push('')
|
|
347
|
+
|
|
348
|
+
for (const analysis of analyses) {
|
|
349
|
+
lines.push(`#### Depth: ${analysis.scanDepth}`)
|
|
350
|
+
lines.push('')
|
|
351
|
+
lines.push(`- Files scanned: ${analysis.filesScanned}`)
|
|
352
|
+
lines.push(`- Total findings: ${analysis.totalFindings}`)
|
|
353
|
+
lines.push(`- Duration: ${analysis.scanDuration}ms`)
|
|
354
|
+
lines.push('')
|
|
355
|
+
lines.push('**Severity Breakdown:**')
|
|
356
|
+
lines.push(`| Critical | High | Medium | Low | Info |`)
|
|
357
|
+
lines.push(`|----------|------|--------|-----|------|`)
|
|
358
|
+
lines.push(`| ${analysis.bySeverity.critical} | ${analysis.bySeverity.high} | ${analysis.bySeverity.medium} | ${analysis.bySeverity.low} | ${analysis.bySeverity.info} |`)
|
|
359
|
+
lines.push('')
|
|
360
|
+
|
|
361
|
+
// Code type breakdown
|
|
362
|
+
lines.push('**Finding Distribution by Code Type:**')
|
|
363
|
+
lines.push(`- Library internals: ${analysis.libraryCodeFindings}`)
|
|
364
|
+
lines.push(`- Examples/demos: ${analysis.exampleCodeFindings}`)
|
|
365
|
+
lines.push(`- Test files: ${analysis.testCodeFindings}`)
|
|
366
|
+
lines.push('')
|
|
367
|
+
|
|
368
|
+
// Top categories
|
|
369
|
+
if (analysis.topCategories.length > 0) {
|
|
370
|
+
lines.push('**Top Categories:**')
|
|
371
|
+
for (const { category, count } of analysis.topCategories.slice(0, 5)) {
|
|
372
|
+
lines.push(`- ${category}: ${count}`)
|
|
373
|
+
}
|
|
374
|
+
lines.push('')
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// M5 findings with examples
|
|
378
|
+
if (analysis.m5Findings.length > 0) {
|
|
379
|
+
lines.push('**M5 Detector Findings:**')
|
|
380
|
+
for (const m5 of analysis.m5Findings) {
|
|
381
|
+
lines.push(`- **${m5.category}**: ${m5.count} findings`)
|
|
382
|
+
if (m5.examples.length > 0) {
|
|
383
|
+
for (const ex of m5.examples.slice(0, 3)) {
|
|
384
|
+
lines.push(` - \`${ex.file}:${ex.line}\` (${ex.severity}): ${ex.title}`)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
lines.push('')
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Triage Checklist
|
|
394
|
+
lines.push('## FP Triage Checklist')
|
|
395
|
+
lines.push('')
|
|
396
|
+
lines.push('For each medium+ finding, classify as:')
|
|
397
|
+
lines.push('- [ ] **True Positive** - Real security issue')
|
|
398
|
+
lines.push('- [ ] **False Positive** - Safe code incorrectly flagged')
|
|
399
|
+
lines.push('- [ ] **Borderline** - Context-dependent, may need tuning')
|
|
400
|
+
lines.push('')
|
|
401
|
+
lines.push('### Triage Template')
|
|
402
|
+
lines.push('')
|
|
403
|
+
lines.push('```markdown')
|
|
404
|
+
lines.push('### Finding: [category]')
|
|
405
|
+
lines.push('**File:** [path]:[line]')
|
|
406
|
+
lines.push('**Severity:** [severity]')
|
|
407
|
+
lines.push('**Classification:** [ ] TP [ ] FP [ ] Borderline')
|
|
408
|
+
lines.push('**Context:** Library internal / Example code / Test file')
|
|
409
|
+
lines.push('**Reasoning:** [Why this is/isn\'t a real issue]')
|
|
410
|
+
lines.push('**Action:** [ ] Keep [ ] Tune detector [ ] Add to known-FP fixtures')
|
|
411
|
+
lines.push('```')
|
|
412
|
+
lines.push('')
|
|
413
|
+
|
|
414
|
+
// Next Steps
|
|
415
|
+
lines.push('## Next Steps')
|
|
416
|
+
lines.push('')
|
|
417
|
+
lines.push('1. Review all medium+ findings in this report')
|
|
418
|
+
lines.push('2. Classify each as TP/FP/Borderline')
|
|
419
|
+
lines.push('3. Calculate FP rate: `FP / (TP + FP)` - target: <20%')
|
|
420
|
+
lines.push('4. Add FP patterns to `known-false-positives.test.ts`')
|
|
421
|
+
lines.push('5. Tune detectors if FP rate exceeds 20%')
|
|
422
|
+
lines.push('')
|
|
423
|
+
|
|
424
|
+
return lines.join('\n')
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Print console summary
|
|
429
|
+
*/
|
|
430
|
+
function printConsoleSummary(report: ValidationReport): void {
|
|
431
|
+
console.log('\n' + '='.repeat(60))
|
|
432
|
+
console.log('M7: VALIDATION ANALYSIS SUMMARY')
|
|
433
|
+
console.log('='.repeat(60))
|
|
434
|
+
|
|
435
|
+
console.log(`\nGenerated: ${report.generatedAt}`)
|
|
436
|
+
console.log(`Repositories: ${report.totalRepos}`)
|
|
437
|
+
console.log(`Total scans: ${report.totalScans}`)
|
|
438
|
+
|
|
439
|
+
console.log('\n--- Aggregate Metrics ---')
|
|
440
|
+
console.log(`Total findings: ${report.aggregateMetrics.totalFindings}`)
|
|
441
|
+
console.log(`Medium+ findings: ${report.aggregateMetrics.totalMediumPlus} (to triage)`)
|
|
442
|
+
console.log(`Avg per repo: ${report.aggregateMetrics.avgFindingsPerRepo}`)
|
|
443
|
+
|
|
444
|
+
console.log('\n--- M5 Detector Performance ---')
|
|
445
|
+
console.log('Category | Total | Medium+')
|
|
446
|
+
console.log('-'.repeat(50))
|
|
447
|
+
|
|
448
|
+
for (const [category, stats] of Object.entries(report.aggregateMetrics.m5DetectorPerformance)) {
|
|
449
|
+
const catPadded = category.padEnd(30)
|
|
450
|
+
console.log(`${catPadded}| ${String(stats.total).padEnd(6)}| ${stats.mediumPlus}`)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
console.log('\n--- Per-Repo Breakdown ---')
|
|
454
|
+
for (const analysis of report.analyses) {
|
|
455
|
+
console.log(`\n${analysis.repoName} (${analysis.scanDepth}):`)
|
|
456
|
+
console.log(` Files: ${analysis.filesScanned}, Findings: ${analysis.totalFindings}, Medium+: ${analysis.mediumPlusFindings}`)
|
|
457
|
+
console.log(` Code types: Library=${analysis.libraryCodeFindings}, Example=${analysis.exampleCodeFindings}, Test=${analysis.testCodeFindings}`)
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Main entry point
|
|
463
|
+
*/
|
|
464
|
+
async function main() {
|
|
465
|
+
const args = process.argv.slice(2)
|
|
466
|
+
let outputPath: string | null = null
|
|
467
|
+
|
|
468
|
+
// Parse args
|
|
469
|
+
for (let i = 0; i < args.length; i++) {
|
|
470
|
+
if (args[i] === '--output' && args[i + 1]) {
|
|
471
|
+
outputPath = args[i + 1]
|
|
472
|
+
i++
|
|
473
|
+
} else if (args[i] === '--help' || args[i] === '-h') {
|
|
474
|
+
console.log(`
|
|
475
|
+
M7: Results Analysis Script
|
|
476
|
+
|
|
477
|
+
Usage:
|
|
478
|
+
npx tsx analyze-results.ts [options]
|
|
479
|
+
|
|
480
|
+
Options:
|
|
481
|
+
--output <path> Write markdown report to file
|
|
482
|
+
--help, -h Show this help
|
|
483
|
+
|
|
484
|
+
Examples:
|
|
485
|
+
npx tsx analyze-results.ts # Print summary
|
|
486
|
+
npx tsx analyze-results.ts --output docs/RESULTSCOMPARISON.md # Generate report
|
|
487
|
+
`)
|
|
488
|
+
process.exit(0)
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Load results
|
|
493
|
+
const results = loadResults()
|
|
494
|
+
console.log(`Loaded ${results.size} result files`)
|
|
495
|
+
|
|
496
|
+
// Analyze each result
|
|
497
|
+
const analyses: RepoAnalysis[] = []
|
|
498
|
+
for (const [key, result] of results) {
|
|
499
|
+
const analysis = analyzeResult(key, result)
|
|
500
|
+
analyses.push(analysis)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Get unique repo count
|
|
504
|
+
const uniqueRepos = new Set(analyses.map(a => a.repoName))
|
|
505
|
+
|
|
506
|
+
// Build report
|
|
507
|
+
const report: ValidationReport = {
|
|
508
|
+
generatedAt: new Date().toISOString(),
|
|
509
|
+
totalRepos: uniqueRepos.size,
|
|
510
|
+
totalScans: analyses.length,
|
|
511
|
+
analyses,
|
|
512
|
+
aggregateMetrics: computeAggregateMetrics(analyses),
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Print console summary
|
|
516
|
+
printConsoleSummary(report)
|
|
517
|
+
|
|
518
|
+
// Generate markdown if output path specified
|
|
519
|
+
if (outputPath) {
|
|
520
|
+
const markdown = generateMarkdownReport(report)
|
|
521
|
+
const fullPath = path.isAbsolute(outputPath)
|
|
522
|
+
? outputPath
|
|
523
|
+
: path.join(process.cwd(), outputPath)
|
|
524
|
+
|
|
525
|
+
// Ensure directory exists
|
|
526
|
+
const dir = path.dirname(fullPath)
|
|
527
|
+
if (!fs.existsSync(dir)) {
|
|
528
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
fs.writeFileSync(fullPath, markdown)
|
|
532
|
+
console.log(`\nMarkdown report saved to: ${fullPath}`)
|
|
533
|
+
} else {
|
|
534
|
+
console.log('\nTip: Use --output docs/RESULTSCOMPARISON.md to generate full report')
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Run
|
|
539
|
+
main().catch(err => {
|
|
540
|
+
console.error('Analysis failed:', err)
|
|
541
|
+
process.exit(1)
|
|
542
|
+
})
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Extract medium+ findings for systematic triage
|
|
4
|
+
*
|
|
5
|
+
* Outputs findings grouped by category with file paths for code review
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs'
|
|
9
|
+
import * as path from 'path'
|
|
10
|
+
import type { ScanResult, Vulnerability } from '../../types'
|
|
11
|
+
|
|
12
|
+
const RESULTS_DIR = path.join(__dirname, '../../../validation-results')
|
|
13
|
+
const REPOS_DIR = path.join(__dirname, '../../../validation-repos')
|
|
14
|
+
|
|
15
|
+
interface TriageFinding {
|
|
16
|
+
id: string
|
|
17
|
+
repo: string
|
|
18
|
+
file: string
|
|
19
|
+
line: number
|
|
20
|
+
severity: string
|
|
21
|
+
category: string
|
|
22
|
+
title: string
|
|
23
|
+
description: string
|
|
24
|
+
lineContent: string
|
|
25
|
+
codeType: 'library' | 'example' | 'test' | 'other'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function classifyCodeType(filePath: string): 'library' | 'example' | 'test' | 'other' {
|
|
29
|
+
const lowerPath = filePath.toLowerCase()
|
|
30
|
+
|
|
31
|
+
if (lowerPath.includes('__tests__') || lowerPath.includes('/test/') ||
|
|
32
|
+
lowerPath.includes('/tests/') || lowerPath.includes('.test.') ||
|
|
33
|
+
lowerPath.includes('.spec.') || lowerPath.includes('/fixtures/')) {
|
|
34
|
+
return 'test'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (lowerPath.includes('/examples/') || lowerPath.includes('/example/') ||
|
|
38
|
+
lowerPath.includes('/demos/') || lowerPath.includes('/demo/') ||
|
|
39
|
+
lowerPath.includes('/templates/') || lowerPath.includes('/cookbook/')) {
|
|
40
|
+
return 'example'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (lowerPath.includes('/src/') || lowerPath.includes('/lib/') ||
|
|
44
|
+
lowerPath.includes('/libs/') || lowerPath.includes('/packages/')) {
|
|
45
|
+
return 'library'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return 'other'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function extractMediumPlusFindings(): TriageFinding[] {
|
|
52
|
+
const findings: TriageFinding[] = []
|
|
53
|
+
|
|
54
|
+
const files = fs.readdirSync(RESULTS_DIR).filter(f => f.endsWith('.json'))
|
|
55
|
+
|
|
56
|
+
for (const file of files) {
|
|
57
|
+
const repoName = file.replace('-cheap.json', '').replace('-validated.json', '')
|
|
58
|
+
const content = fs.readFileSync(path.join(RESULTS_DIR, file), 'utf-8')
|
|
59
|
+
const result = JSON.parse(content) as ScanResult
|
|
60
|
+
|
|
61
|
+
for (const vuln of result.vulnerabilities) {
|
|
62
|
+
if (['critical', 'high', 'medium'].includes(vuln.severity)) {
|
|
63
|
+
findings.push({
|
|
64
|
+
id: vuln.id,
|
|
65
|
+
repo: repoName,
|
|
66
|
+
file: vuln.filePath,
|
|
67
|
+
line: vuln.lineNumber,
|
|
68
|
+
severity: vuln.severity,
|
|
69
|
+
category: vuln.category,
|
|
70
|
+
title: vuln.title,
|
|
71
|
+
description: vuln.description,
|
|
72
|
+
lineContent: vuln.lineContent,
|
|
73
|
+
codeType: classifyCodeType(vuln.filePath),
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return findings
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function groupByCategory(findings: TriageFinding[]): Map<string, TriageFinding[]> {
|
|
83
|
+
const groups = new Map<string, TriageFinding[]>()
|
|
84
|
+
|
|
85
|
+
for (const f of findings) {
|
|
86
|
+
const existing = groups.get(f.category) || []
|
|
87
|
+
existing.push(f)
|
|
88
|
+
groups.set(f.category, existing)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return groups
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function main() {
|
|
95
|
+
const findings = extractMediumPlusFindings()
|
|
96
|
+
const grouped = groupByCategory(findings)
|
|
97
|
+
|
|
98
|
+
console.log(`\n${'='.repeat(60)}`)
|
|
99
|
+
console.log('MEDIUM+ FINDINGS FOR TRIAGE')
|
|
100
|
+
console.log('='.repeat(60))
|
|
101
|
+
console.log(`Total: ${findings.length} findings\n`)
|
|
102
|
+
|
|
103
|
+
// Summary by category
|
|
104
|
+
console.log('By Category:')
|
|
105
|
+
const sortedCategories = Array.from(grouped.entries())
|
|
106
|
+
.sort((a, b) => b[1].length - a[1].length)
|
|
107
|
+
|
|
108
|
+
for (const [category, catFindings] of sortedCategories) {
|
|
109
|
+
const bySeverity = {
|
|
110
|
+
critical: catFindings.filter(f => f.severity === 'critical').length,
|
|
111
|
+
high: catFindings.filter(f => f.severity === 'high').length,
|
|
112
|
+
medium: catFindings.filter(f => f.severity === 'medium').length,
|
|
113
|
+
}
|
|
114
|
+
console.log(` ${category}: ${catFindings.length} (C:${bySeverity.critical} H:${bySeverity.high} M:${bySeverity.medium})`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// By code type
|
|
118
|
+
const byCodeType = {
|
|
119
|
+
library: findings.filter(f => f.codeType === 'library').length,
|
|
120
|
+
example: findings.filter(f => f.codeType === 'example').length,
|
|
121
|
+
test: findings.filter(f => f.codeType === 'test').length,
|
|
122
|
+
other: findings.filter(f => f.codeType === 'other').length,
|
|
123
|
+
}
|
|
124
|
+
console.log(`\nBy Code Type:`)
|
|
125
|
+
console.log(` Library: ${byCodeType.library}`)
|
|
126
|
+
console.log(` Example: ${byCodeType.example}`)
|
|
127
|
+
console.log(` Test: ${byCodeType.test}`)
|
|
128
|
+
console.log(` Other: ${byCodeType.other}`)
|
|
129
|
+
|
|
130
|
+
// Output JSON for further processing
|
|
131
|
+
const outputPath = path.join(RESULTS_DIR, 'medium-plus-findings.json')
|
|
132
|
+
fs.writeFileSync(outputPath, JSON.stringify(findings, null, 2))
|
|
133
|
+
console.log(`\nFindings exported to: ${outputPath}`)
|
|
134
|
+
|
|
135
|
+
// Output summary by repo
|
|
136
|
+
console.log('\nBy Repository:')
|
|
137
|
+
const byRepo = new Map<string, number>()
|
|
138
|
+
for (const f of findings) {
|
|
139
|
+
byRepo.set(f.repo, (byRepo.get(f.repo) || 0) + 1)
|
|
140
|
+
}
|
|
141
|
+
for (const [repo, count] of byRepo) {
|
|
142
|
+
console.log(` ${repo}: ${count}`)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
main()
|