@preventive/triage 1.0.0-alpha.35 → 1.0.0-alpha.37

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.
@@ -0,0 +1,2 @@
1
+ export function isDependencyFile(file: unknown, directory: string): boolean
2
+ export function dependencyDirectory(reports: Iterable<{ groups?: Iterable<Iterable<{ file?: unknown }>>; tree?: unknown }>): 'node_modules' | 'vendor' | 'dependencies'
@@ -0,0 +1,24 @@
1
+ // Match whole path segments, using the same directory precedence as the
2
+ // findings dependency filter. Less-specific directory names can be own source.
3
+ export function isDependencyFile(file, directory) {
4
+ if (typeof file !== 'string') return false
5
+ const path = file.replaceAll('\\', '/')
6
+ return path.startsWith(`${directory}/`) || path.includes(`/${directory}/`)
7
+ }
8
+
9
+ export function dependencyDirectory(reports) {
10
+ let vendor = false
11
+ for (const report of reports) {
12
+ for (const group of report.groups ?? []) {
13
+ for (const finding of group) {
14
+ if (isDependencyFile(finding.file, 'node_modules')) return 'node_modules'
15
+ if (isDependencyFile(finding.file, 'vendor')) vendor = true
16
+ }
17
+ }
18
+ for (const file of Object.keys(report.tree ?? {})) {
19
+ if (isDependencyFile(file, 'node_modules')) return 'node_modules'
20
+ if (isDependencyFile(file, 'vendor')) vendor = true
21
+ }
22
+ }
23
+ return vendor ? 'vendor' : 'dependencies'
24
+ }
@@ -1,5 +1,7 @@
1
1
  import { parseLinkedFindings } from '../../client/linked-findings.js'
2
- import { detectFormat, loadFindings, parseCodexCsvToScans, readReport } from '@preventive/report'
2
+ import { dependencyDirectory, isDependencyFile } from '../../client/dependency-paths.js'
3
+ import { detectFormat, loadFindings, parseCodexCsvToScans, readReport, reportEntries, reportRepoGithub } from '@preventive/report'
4
+ import { commonFileDirectory } from './repository-alias.ts'
3
5
 
4
6
  // A managed blob retains one server identity even when a CSV contains several
5
7
  // scans. Keep all their findings (and upstream ids) under that identity. CSV
@@ -40,3 +42,31 @@ export function managedFindingSourcePaths(findings: unknown[]): Set<string> {
40
42
  }
41
43
  return paths
42
44
  }
45
+
46
+ // The report's own findings, in either entry shape, outside the dependency
47
+ // directory the local report view would choose: node_modules, else vendor,
48
+ // else dependencies.
49
+ function ownFindings(data: unknown) {
50
+ const objects = (reportEntries(data) ?? []).flat().filter((finding): finding is { file?: unknown } => finding != null && typeof finding === 'object')
51
+ const directory = dependencyDirectory([{ groups: [objects], tree: (data as { tree?: unknown } | null)?.tree }])
52
+ return { directory, own: objects.filter(finding => !isDependencyFile(finding.file, directory)) }
53
+ }
54
+
55
+ // Findings name the repository of their own file. Like the local report view,
56
+ // a report without its own repository belongs to the one repository named by
57
+ // its own findings (Claude Security and Codex exports name one on every finding).
58
+ export function findingsRepository(data: unknown): string | null {
59
+ const repos = new Map<string, string>()
60
+ for (const finding of ownFindings(data).own) {
61
+ const github = reportRepoGithub(finding)
62
+ if (github) repos.set(github.toLowerCase(), github)
63
+ }
64
+ return repos.size === 1 ? [...repos.values()][0]! : null
65
+ }
66
+
67
+ // The directory shared by the files the report's own findings cite, for alias
68
+ // matching. Dependency paths, including cited evidence, cannot hide it.
69
+ export function ownFileDirectory(data: unknown): string {
70
+ const { directory, own } = ownFindings(data)
71
+ return commonFileDirectory([...managedFindingSourcePaths(own)].filter(path => !isDependencyFile(path, directory)))
72
+ }