@codacy/tools-trivy-0 0.16.0 → 0.18.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/README.md +116 -2
- package/dist/index.js +197 -32
- package/dist/metadata.js +6 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
- [Overview](#overview)
|
|
6
6
|
- [Umbrella patterns](#umbrella-patterns)
|
|
7
|
+
- [Issue parity with `codacy-trivy`](#issue-parity-with-codacy-trivy)
|
|
8
|
+
- [`sourceId` — why every issue carries its CVE](#sourceid--why-every-issue-carries-its-cve)
|
|
9
|
+
- [Scan flags that affect what is found](#scan-flags-that-affect-what-is-found)
|
|
10
|
+
- [Line-number resolution](#line-number-resolution)
|
|
11
|
+
- [Fix version](#fix-version)
|
|
12
|
+
- [Known divergences](#known-divergences)
|
|
7
13
|
- [DB update management](#db-update-management)
|
|
8
14
|
- [Malicious package detection](#malicious-package-detection)
|
|
9
15
|
- [Updating patterns](#updating-patterns)
|
|
@@ -40,6 +46,113 @@ Trivy uses umbrella patterns rather than individual CVE patterns:
|
|
|
40
46
|
| `Trivy_secret` | Error | Hardcoded secrets |
|
|
41
47
|
| `Trivy_malicious_packages` | Error | Malicious packages (via OSV.dev API) |
|
|
42
48
|
|
|
49
|
+
## Issue parity with `codacy-trivy`
|
|
50
|
+
|
|
51
|
+
Codacy Cloud analyses repositories with the `codacy-trivy` Docker engine, so a
|
|
52
|
+
repository scanned locally and uploaded must produce the same results as the same
|
|
53
|
+
repository scanned on the cloud. Three pieces of this adapter exist only to hold
|
|
54
|
+
that line; the reference implementation is `codacy-trivy`
|
|
55
|
+
`internal/tool/tool.go`, and the ports live in `src/vuln-mapping.ts`
|
|
56
|
+
(`fallbackSearchForLineNumber`, `findLeastDisruptiveFixedVersion`,
|
|
57
|
+
`purlPrettyPrint`). **Change them only alongside that file.**
|
|
58
|
+
|
|
59
|
+
### `sourceId` — why every issue carries its CVE
|
|
60
|
+
|
|
61
|
+
Codacy derives a result's deterministic UUID from an MD5 over four keys: filename,
|
|
62
|
+
pattern id, the line's text with whitespace stripped, and `sourceId`
|
|
63
|
+
(`codacy-worker` `shared/rules/…/rules/components/ResultUUIDRules.scala`).
|
|
64
|
+
|
|
65
|
+
Trivy reports many CVEs under one of four severity umbrella patterns, so two CVEs
|
|
66
|
+
on the same lockfile line share three of those four keys. **Without `sourceId`
|
|
67
|
+
they collapse into a single Codacy result.** Every issue this adapter produces
|
|
68
|
+
therefore sets it:
|
|
69
|
+
|
|
70
|
+
| Finding | `sourceId` |
|
|
71
|
+
| ----------------- | ----------------------------- |
|
|
72
|
+
| Vulnerability | `VulnerabilityID` (the CVE) |
|
|
73
|
+
| Secret | Trivy's `RuleID` |
|
|
74
|
+
| Malicious package | The OSV advisory id (`MAL-…`) |
|
|
75
|
+
|
|
76
|
+
`src/metadata.ts` sets `needsSourceIdUpload: true`, which is what makes
|
|
77
|
+
`upload` include the field in the v2 payload (mirroring
|
|
78
|
+
`needs_source_id_upload` in the Go CLI's Trivy plugin manifest). Adapters without
|
|
79
|
+
the flag omit the field entirely — sending it where the cloud engine does not
|
|
80
|
+
would give the same finding a different UUID.
|
|
81
|
+
|
|
82
|
+
### Scan flags that affect what is found
|
|
83
|
+
|
|
84
|
+
The vulnerability scan passes **`--detection-priority comprehensive`**, matching
|
|
85
|
+
`codacy-trivy`'s `DetectionPriority: PriorityComprehensive`. It reports more
|
|
86
|
+
vulnerabilities — some of which may be false positives — and it is **required for Go
|
|
87
|
+
standard-library findings**: verified against trivy 0.70, a `go.mod` scan without it
|
|
88
|
+
reports **zero** `stdlib` vulnerabilities (6 issues instead of 26 on a small
|
|
89
|
+
`go 1.20` module). Removing the flag would silently drop every stdlib finding and
|
|
90
|
+
make the go.mod handling below dead code.
|
|
91
|
+
|
|
92
|
+
### Line-number resolution
|
|
93
|
+
|
|
94
|
+
Trivy reports **no line** for a vulnerable dependency, so the line is reconstructed:
|
|
95
|
+
|
|
96
|
+
1. Look up the package's `Locations[0].StartLine`, **keyed by PURL** — the key the
|
|
97
|
+
cloud engine uses. `PkgID` is a secondary key for the rare package Trivy reports
|
|
98
|
+
without a PURL.
|
|
99
|
+
2. On a miss or a zero, scan the manifest top to bottom for the first line whose
|
|
100
|
+
trimmed text contains the package name.
|
|
101
|
+
3. Still nothing ⇒ emit a **file-level error** (`Line numbers not supported`,
|
|
102
|
+
deduplicated to one per file), not an issue on line 1. A confidently wrong
|
|
103
|
+
location is worse than an honest "unknown"; `codacy-trivy` makes the same call
|
|
104
|
+
via `mapIssuesWithoutLineNumber`.
|
|
105
|
+
|
|
106
|
+
**go.mod standard library.** Trivy reports standard-library vulnerabilities against
|
|
107
|
+
a package named `stdlib`, which never literally appears in `go.mod`. Trivy
|
|
108
|
+
[documents](https://trivy.dev/v0.59/docs/coverage/language/golang/#gomod-stdlib)
|
|
109
|
+
using the minimum of the `toolchain` and `go` directives, but in practice always
|
|
110
|
+
uses `toolchain` when it exists. So the scan returns the `toolchain` line as soon
|
|
111
|
+
as it sees one, and only falls back to the `go` directive line after reading the
|
|
112
|
+
whole file. This is not derivable from Trivy's docs — do not "simplify" it.
|
|
113
|
+
|
|
114
|
+
### Fix version
|
|
115
|
+
|
|
116
|
+
Trivy's `FixedVersion` is a comma-separated **list** of every fixing version
|
|
117
|
+
(`"1.9.0, 1.10.0, 2.0.1"`). The message reports only the least disruptive one: the
|
|
118
|
+
smallest candidate strictly greater than the installed version, by semantic
|
|
119
|
+
version. When no candidate is greater (or the scheme isn't semver — Ruby's `~>`,
|
|
120
|
+
Maven qualifiers), Trivy's raw value is passed through; when `FixedVersion` is
|
|
121
|
+
empty the message reads `(no fix available)`.
|
|
122
|
+
|
|
123
|
+
Message format, matching the cloud engine verbatim:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
Insecure dependency {pretty-printed PURL} ({CVE}: {title}) (update to {version})
|
|
127
|
+
Insecure dependency {pretty-printed PURL} ({CVE}: {title}) (no fix available)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Comparison uses a port of Go's `golang.org/x/mod/semver` (`compareSemver`), not
|
|
131
|
+
npm semver: it accepts two-component versions like `v1.2`, and treats an
|
|
132
|
+
unparseable version as lower than any valid one, which is what makes non-semver
|
|
133
|
+
schemes fall through instead of being ranked.
|
|
134
|
+
|
|
135
|
+
### Known divergences
|
|
136
|
+
|
|
137
|
+
Both are deliberate; see `docs/tech-debt.md`.
|
|
138
|
+
|
|
139
|
+
- **Fix-version ordering.** `codacy-trivy` sorts the candidate strings _before_
|
|
140
|
+
adding the `v` prefix, so `x/mod/semver` sees them all as invalid, compares them
|
|
141
|
+
equal, and falls back to a lexicographic tiebreak — picking `1.10.0` over
|
|
142
|
+
`1.9.0`. We sort on the prefixed values and return the genuinely smallest.
|
|
143
|
+
- **Packages without a PURL.** `codacy-trivy` skips those vulnerabilities entirely.
|
|
144
|
+
We keep them, resolving the line by name and rendering the package as
|
|
145
|
+
`name@version`, because silently dropping a real vulnerability is worse than a
|
|
146
|
+
message that differs from the cloud's.
|
|
147
|
+
- **Secret message text.** Ours appends the secret's category
|
|
148
|
+
(`Possible hardcoded secret: {title} ({category})`); the cloud engine stops at
|
|
149
|
+
the title.
|
|
150
|
+
- **Offline scan.** `codacy-trivy` sets `OfflineScan: true` (its container is
|
|
151
|
+
air-gapped); we don't, so Trivy may resolve some Java/Maven dependencies over the
|
|
152
|
+
network and report slightly more accurate results for those ecosystems. Not
|
|
153
|
+
changed here: making local scans less accurate to match an air-gap constraint we
|
|
154
|
+
don't have would be the wrong trade.
|
|
155
|
+
|
|
43
156
|
## DB update management
|
|
44
157
|
|
|
45
158
|
The adapter reads Trivy's `metadata.json` (at `{cacheDir}/db/metadata.json`) to
|
|
@@ -89,11 +202,12 @@ pnpm test # Run tests (requires trivy in PATH or auto-install)
|
|
|
89
202
|
|
|
90
203
|
## Notes for maintainers
|
|
91
204
|
|
|
92
|
-
- Trivy runs **two separate scan invocations**: one for vulnerabilities (`--scanners vuln`) and one for secrets (`--scanners secret`). Malicious package detection piggybacks on the vuln scan output (no extra invocation). Which scans run depends on which umbrella patterns are enabled.
|
|
205
|
+
- Trivy runs **two separate scan invocations**: one for vulnerabilities (`--scanners vuln --detection-priority comprehensive`) and one for secrets (`--scanners secret`). Malicious package detection piggybacks on the vuln scan output (no extra invocation). Which scans run depends on which umbrella patterns are enabled.
|
|
93
206
|
- The vulnerability database is downloaded lazily on first run to `--cache-dir ~/.codacy/cache/trivy/`. Internet access is required on first use. DB staleness is managed via `metadata.json` → `NextUpdate`.
|
|
94
207
|
- Trivy scans **whole directories**, not individual files. Results are filtered to `ctx.targetFiles` after scanning.
|
|
95
208
|
- The binary is downloaded from GitHub releases as a `.tar.gz` archive.
|
|
96
209
|
- Vulnerability severity (CRITICAL/HIGH/MEDIUM/LOW) maps to the corresponding umbrella pattern.
|
|
97
|
-
- Message format
|
|
210
|
+
- Message format, line resolution and `sourceId` are all parity-critical — see [Issue parity with `codacy-trivy`](#issue-parity-with-codacy-trivy) before touching them.
|
|
211
|
+
- A vulnerability whose line cannot be resolved becomes a file-level `AnalysisError` (`kind: "LineNumbersNotSupported"`), one per file, rather than an issue on line 1.
|
|
98
212
|
- Malicious package detection uses the OSV.dev batch API. On failure, it degrades gracefully (warning only).
|
|
99
213
|
- Config file: checks for `trivy.yaml` in the repo root.
|
package/dist/index.js
CHANGED
|
@@ -252,9 +252,120 @@ var descriptor = {
|
|
|
252
252
|
filePatterns: ["*"],
|
|
253
253
|
supportedOS: ["linux", "darwin", "win32"],
|
|
254
254
|
supportedArch: ["x64", "arm64"],
|
|
255
|
-
supportedRunners: ["local", "container"]
|
|
255
|
+
supportedRunners: ["local", "container"],
|
|
256
|
+
// Trivy reports every CVE under one of four severity umbrella patterns, so its
|
|
257
|
+
// issues must upload `sourceId` (the CVE id) or two CVEs on the same manifest
|
|
258
|
+
// line collapse into a single Codacy result. Matches `needs_source_id_upload`
|
|
259
|
+
// in the Go CLI's Trivy plugin manifest.
|
|
260
|
+
needsSourceIdUpload: true
|
|
256
261
|
};
|
|
257
262
|
|
|
263
|
+
// src/vuln-mapping.ts
|
|
264
|
+
function purlPrettyPrint(purl) {
|
|
265
|
+
const stripped = purl.startsWith("pkg:") ? purl.slice("pkg:".length) : purl;
|
|
266
|
+
try {
|
|
267
|
+
return decodeURIComponent(stripped);
|
|
268
|
+
} catch {
|
|
269
|
+
return stripped;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function fallbackSearchForLineNumber(lines, fileName, pkgName) {
|
|
273
|
+
if (!pkgName) return 0;
|
|
274
|
+
const isGoModStdLib = fileName.endsWith("go.mod") && pkgName === "stdlib";
|
|
275
|
+
let goDirectiveLine = 0;
|
|
276
|
+
for (let i = 0; i < lines.length; i++) {
|
|
277
|
+
const lineText = lines[i].trim();
|
|
278
|
+
const lineNumber = i + 1;
|
|
279
|
+
if (isGoModStdLib) {
|
|
280
|
+
if (lineText.startsWith("toolchain ")) return lineNumber;
|
|
281
|
+
if (lineText.startsWith("go ")) goDirectiveLine = lineNumber;
|
|
282
|
+
} else if (lineText.includes(pkgName)) {
|
|
283
|
+
return lineNumber;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return goDirectiveLine;
|
|
287
|
+
}
|
|
288
|
+
function findLeastDisruptiveFixedVersion(installedVersion, fixedVersion) {
|
|
289
|
+
const sanitize = (version) => `v${version.replace(/^v/, "")}`;
|
|
290
|
+
const sanitizedInstalled = sanitize(installedVersion);
|
|
291
|
+
const candidates = fixedVersion.split(",").map((v) => v.trim()).filter((v) => compareSemver(sanitize(v), sanitizedInstalled) > 0);
|
|
292
|
+
if (candidates.length === 0) return fixedVersion;
|
|
293
|
+
candidates.sort(
|
|
294
|
+
(a, b) => compareSemver(sanitize(a), sanitize(b)) || (a < b ? -1 : a > b ? 1 : 0)
|
|
295
|
+
);
|
|
296
|
+
return candidates[0];
|
|
297
|
+
}
|
|
298
|
+
function compareSemver(v, w) {
|
|
299
|
+
const pv = parseSemver(v);
|
|
300
|
+
const pw = parseSemver(w);
|
|
301
|
+
if (!pv && !pw) return 0;
|
|
302
|
+
if (!pv) return -1;
|
|
303
|
+
if (!pw) return 1;
|
|
304
|
+
return compareNumeric(pv.major, pw.major) || compareNumeric(pv.minor, pw.minor) || compareNumeric(pv.patch, pw.patch) || comparePrerelease(pv.prerelease, pw.prerelease);
|
|
305
|
+
}
|
|
306
|
+
var NUMERIC_IDENT = /^(0|[1-9]\d*)$/;
|
|
307
|
+
var ALPHANUMERIC_IDENT = /^[0-9A-Za-z-]+$/;
|
|
308
|
+
function parseSemver(version) {
|
|
309
|
+
if (!version.startsWith("v")) return null;
|
|
310
|
+
let rest = version.slice(1);
|
|
311
|
+
const plusIndex = rest.indexOf("+");
|
|
312
|
+
if (plusIndex >= 0) {
|
|
313
|
+
const build = rest.slice(plusIndex + 1);
|
|
314
|
+
rest = rest.slice(0, plusIndex);
|
|
315
|
+
if (!isValidDotSeparated(build, false)) return null;
|
|
316
|
+
}
|
|
317
|
+
let prerelease = "";
|
|
318
|
+
const dashIndex = rest.indexOf("-");
|
|
319
|
+
if (dashIndex >= 0) {
|
|
320
|
+
prerelease = rest.slice(dashIndex);
|
|
321
|
+
rest = rest.slice(0, dashIndex);
|
|
322
|
+
if (!isValidDotSeparated(prerelease.slice(1), true)) return null;
|
|
323
|
+
}
|
|
324
|
+
const parts = rest.split(".");
|
|
325
|
+
if (parts.length > 3) return null;
|
|
326
|
+
if (!parts.every((p) => NUMERIC_IDENT.test(p))) return null;
|
|
327
|
+
return {
|
|
328
|
+
major: parts[0],
|
|
329
|
+
minor: parts[1] ?? "0",
|
|
330
|
+
patch: parts[2] ?? "0",
|
|
331
|
+
prerelease
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function isValidDotSeparated(value, isPrerelease) {
|
|
335
|
+
if (value === "") return false;
|
|
336
|
+
return value.split(".").every((ident) => {
|
|
337
|
+
if (!ALPHANUMERIC_IDENT.test(ident)) return false;
|
|
338
|
+
if (isPrerelease && /^\d+$/.test(ident) && !NUMERIC_IDENT.test(ident)) return false;
|
|
339
|
+
return true;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
function compareNumeric(x, y) {
|
|
343
|
+
if (x === y) return 0;
|
|
344
|
+
if (x.length !== y.length) return x.length < y.length ? -1 : 1;
|
|
345
|
+
return x < y ? -1 : 1;
|
|
346
|
+
}
|
|
347
|
+
function comparePrerelease(x, y) {
|
|
348
|
+
if (x === y) return 0;
|
|
349
|
+
if (x === "") return 1;
|
|
350
|
+
if (y === "") return -1;
|
|
351
|
+
const xs = x.slice(1).split(".");
|
|
352
|
+
const ys = y.slice(1).split(".");
|
|
353
|
+
for (let i = 0; i < Math.min(xs.length, ys.length); i++) {
|
|
354
|
+
const dx = xs[i];
|
|
355
|
+
const dy = ys[i];
|
|
356
|
+
if (dx === dy) continue;
|
|
357
|
+
const nx = /^\d+$/.test(dx);
|
|
358
|
+
const ny = /^\d+$/.test(dy);
|
|
359
|
+
if (nx !== ny) return nx ? -1 : 1;
|
|
360
|
+
if (nx) {
|
|
361
|
+
const byLength = compareNumeric(dx, dy);
|
|
362
|
+
if (byLength !== 0) return byLength;
|
|
363
|
+
}
|
|
364
|
+
return dx < dy ? -1 : 1;
|
|
365
|
+
}
|
|
366
|
+
return xs.length < ys.length ? -1 : 1;
|
|
367
|
+
}
|
|
368
|
+
|
|
258
369
|
// src/adapter.ts
|
|
259
370
|
var TOOL_ID = "Trivy";
|
|
260
371
|
var PATTERN_META = new Map(
|
|
@@ -403,9 +514,23 @@ async function runTrivyScan(binary, args, cwd, errors) {
|
|
|
403
514
|
return null;
|
|
404
515
|
}
|
|
405
516
|
}
|
|
406
|
-
|
|
517
|
+
var LINE_NUMBERS_NOT_SUPPORTED = "Line numbers not supported";
|
|
518
|
+
function recordUnlocatableFinding(errors, seenFiles, filePath) {
|
|
519
|
+
if (seenFiles.has(filePath)) return;
|
|
520
|
+
seenFiles.add(filePath);
|
|
521
|
+
errors.push({
|
|
522
|
+
toolId: TOOL_ID,
|
|
523
|
+
phase: "outputParse",
|
|
524
|
+
kind: "LineNumbersNotSupported",
|
|
525
|
+
message: LINE_NUMBERS_NOT_SUPPORTED,
|
|
526
|
+
level: "warning",
|
|
527
|
+
filePath
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
async function parseVulnerabilities(output, enabledPatterns, targetFilesSet, repositoryRoot, unlocatableFiles) {
|
|
407
531
|
const issues = [];
|
|
408
|
-
|
|
532
|
+
const errors = [];
|
|
533
|
+
if (!output.Results) return { issues, errors };
|
|
409
534
|
const linesCache = /* @__PURE__ */ new Map();
|
|
410
535
|
for (const result of output.Results) {
|
|
411
536
|
if (!result.Vulnerabilities) continue;
|
|
@@ -415,14 +540,21 @@ async function parseVulnerabilities(output, enabledPatterns, targetFilesSet, rep
|
|
|
415
540
|
linesCache.set(relPath, await (0, import_tooling.readFileLines)(import_path.default.join(repositoryRoot, relPath)));
|
|
416
541
|
}
|
|
417
542
|
const lines = linesCache.get(relPath);
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
543
|
+
const lineByPurl = /* @__PURE__ */ new Map();
|
|
544
|
+
const lineByPkgId = /* @__PURE__ */ new Map();
|
|
545
|
+
for (const pkg of result.Packages ?? []) {
|
|
546
|
+
const startLine = pkg.Locations?.[0]?.StartLine ?? 0;
|
|
547
|
+
if (pkg.Identifier?.PURL) lineByPurl.set(pkg.Identifier.PURL, startLine);
|
|
548
|
+
if (pkg.ID) lineByPkgId.set(pkg.ID, startLine);
|
|
425
549
|
}
|
|
550
|
+
const fallbackLines = /* @__PURE__ */ new Map();
|
|
551
|
+
const resolveFallbackLine = (pkgName) => {
|
|
552
|
+
const cached = fallbackLines.get(pkgName);
|
|
553
|
+
if (cached !== void 0) return cached;
|
|
554
|
+
const found = fallbackSearchForLineNumber(lines, relPath, pkgName);
|
|
555
|
+
fallbackLines.set(pkgName, found);
|
|
556
|
+
return found;
|
|
557
|
+
};
|
|
426
558
|
for (const vuln of result.Vulnerabilities) {
|
|
427
559
|
const patternId = VULN_SEVERITY_TO_PATTERN[vuln.Severity];
|
|
428
560
|
if (!patternId) continue;
|
|
@@ -431,23 +563,34 @@ async function parseVulnerabilities(output, enabledPatterns, targetFilesSet, rep
|
|
|
431
563
|
category: "Security",
|
|
432
564
|
severity: "Info"
|
|
433
565
|
};
|
|
434
|
-
const
|
|
435
|
-
const
|
|
436
|
-
const line =
|
|
566
|
+
const purl = vuln.PkgIdentifier?.PURL;
|
|
567
|
+
const reportedLine = (purl !== void 0 ? lineByPurl.get(purl) : void 0) ?? (vuln.PkgID !== void 0 ? lineByPkgId.get(vuln.PkgID) : void 0) ?? 0;
|
|
568
|
+
const line = reportedLine > 0 ? reportedLine : resolveFallbackLine(vuln.PkgName);
|
|
569
|
+
if (line <= 0) {
|
|
570
|
+
recordUnlocatableFinding(errors, unlocatableFiles, relPath);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
const fixedVersion = findLeastDisruptiveFixedVersion(
|
|
574
|
+
vuln.InstalledVersion,
|
|
575
|
+
vuln.FixedVersion ?? ""
|
|
576
|
+
);
|
|
577
|
+
const fix = fixedVersion ? `(update to ${fixedVersion})` : "(no fix available)";
|
|
578
|
+
const pkgLabel = purl ? purlPrettyPrint(purl) : `${vuln.PkgName}@${vuln.InstalledVersion}`;
|
|
437
579
|
issues.push({
|
|
438
580
|
filePath: relPath,
|
|
439
581
|
patternId,
|
|
440
582
|
toolId: TOOL_ID,
|
|
441
583
|
line,
|
|
442
584
|
column: 1,
|
|
443
|
-
message: `Insecure dependency ${
|
|
585
|
+
message: `Insecure dependency ${pkgLabel} (${vuln.VulnerabilityID}: ${vuln.Title ?? ""}) ${fix}`,
|
|
444
586
|
lineContent: (0, import_tooling.getLineContent)(lines, line),
|
|
445
587
|
category: meta.category,
|
|
446
|
-
severity: meta.severity
|
|
588
|
+
severity: meta.severity,
|
|
589
|
+
...vuln.VulnerabilityID ? { sourceId: vuln.VulnerabilityID } : {}
|
|
447
590
|
});
|
|
448
591
|
}
|
|
449
592
|
}
|
|
450
|
-
return issues;
|
|
593
|
+
return { issues, errors };
|
|
451
594
|
}
|
|
452
595
|
async function parseSecrets(output, enabledPatterns, targetFilesSet, repositoryRoot) {
|
|
453
596
|
const issues = [];
|
|
@@ -479,13 +622,14 @@ async function parseSecrets(output, enabledPatterns, targetFilesSet, repositoryR
|
|
|
479
622
|
lineContent: (0, import_tooling.getLineContent)(lines, secret.StartLine),
|
|
480
623
|
multilineContent: (0, import_tooling.getMultilineContent)(lines, secret.StartLine, secret.EndLine),
|
|
481
624
|
category: meta.category,
|
|
482
|
-
severity: meta.severity
|
|
625
|
+
severity: meta.severity,
|
|
626
|
+
...secret.RuleID ? { sourceId: secret.RuleID } : {}
|
|
483
627
|
});
|
|
484
628
|
}
|
|
485
629
|
}
|
|
486
630
|
return issues;
|
|
487
631
|
}
|
|
488
|
-
async function parseMaliciousPackages(output, enabledPatterns, targetFilesSet, repositoryRoot, logger) {
|
|
632
|
+
async function parseMaliciousPackages(output, enabledPatterns, targetFilesSet, repositoryRoot, logger, unlocatableFiles) {
|
|
489
633
|
const issues = [];
|
|
490
634
|
const errors = [];
|
|
491
635
|
const patternId = "Trivy_malicious_packages";
|
|
@@ -509,8 +653,13 @@ async function parseMaliciousPackages(output, enabledPatterns, targetFilesSet, r
|
|
|
509
653
|
const key = `${ecosystem}:${pkg.Name}:${pkg.Version}:${relPath}`;
|
|
510
654
|
if (seen.has(key)) continue;
|
|
511
655
|
seen.add(key);
|
|
512
|
-
|
|
513
|
-
|
|
656
|
+
packages.push({
|
|
657
|
+
ecosystem,
|
|
658
|
+
name: pkg.Name,
|
|
659
|
+
version: pkg.Version,
|
|
660
|
+
filePath: relPath,
|
|
661
|
+
reportedLine: pkg.Locations?.[0]?.StartLine ?? 0
|
|
662
|
+
});
|
|
514
663
|
}
|
|
515
664
|
}
|
|
516
665
|
if (packages.length === 0) return { issues, errors };
|
|
@@ -564,18 +713,24 @@ async function parseMaliciousPackages(output, enabledPatterns, targetFilesSet, r
|
|
|
564
713
|
linesCache.set(pkg.filePath, await (0, import_tooling.readFileLines)(import_path.default.join(repositoryRoot, pkg.filePath)));
|
|
565
714
|
}
|
|
566
715
|
const lines = linesCache.get(pkg.filePath);
|
|
716
|
+
const line = pkg.reportedLine > 0 ? pkg.reportedLine : fallbackSearchForLineNumber(lines, pkg.filePath, pkg.name);
|
|
717
|
+
if (line <= 0) {
|
|
718
|
+
recordUnlocatableFinding(errors, unlocatableFiles, pkg.filePath);
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
567
721
|
for (const vuln of malVulns) {
|
|
568
722
|
const summary = vuln.summary ? ` ${vuln.summary}` : "";
|
|
569
723
|
issues.push({
|
|
570
724
|
filePath: pkg.filePath,
|
|
571
725
|
patternId,
|
|
572
726
|
toolId: TOOL_ID,
|
|
573
|
-
line
|
|
727
|
+
line,
|
|
574
728
|
column: 1,
|
|
575
729
|
message: `Malicious package detected: ${pkg.name}@${pkg.version} (${vuln.id}).${summary}`,
|
|
576
|
-
lineContent: (0, import_tooling.getLineContent)(lines,
|
|
730
|
+
lineContent: (0, import_tooling.getLineContent)(lines, line),
|
|
577
731
|
category: meta.category,
|
|
578
|
-
severity: meta.severity
|
|
732
|
+
severity: meta.severity,
|
|
733
|
+
...vuln.id ? { sourceId: vuln.id } : {}
|
|
579
734
|
});
|
|
580
735
|
}
|
|
581
736
|
}
|
|
@@ -742,6 +897,7 @@ var adapter = {
|
|
|
742
897
|
}
|
|
743
898
|
const enabledPatterns = new Set(codacyConfig.patterns.map((p) => p.patternId));
|
|
744
899
|
const targetFilesSet = new Set(ctx.targetFiles);
|
|
900
|
+
const unlocatableFiles = /* @__PURE__ */ new Set();
|
|
745
901
|
const vulnPatternIds = Object.values(VULN_SEVERITY_TO_PATTERN);
|
|
746
902
|
const runMaliciousScan = enabledPatterns.size === 0 || enabledPatterns.has("Trivy_malicious_packages");
|
|
747
903
|
const runVulnScan = enabledPatterns.size === 0 || vulnPatternIds.some((id) => enabledPatterns.has(id)) || runMaliciousScan;
|
|
@@ -776,7 +932,14 @@ var adapter = {
|
|
|
776
932
|
"vuln",
|
|
777
933
|
"--pkg-types",
|
|
778
934
|
"library",
|
|
779
|
-
"--list-all-pkgs"
|
|
935
|
+
"--list-all-pkgs",
|
|
936
|
+
// Matches codacy-trivy's `DetectionPriority: PriorityComprehensive`.
|
|
937
|
+
// Detects more vulnerabilities, potentially including some false positives
|
|
938
|
+
// — and it is REQUIRED for Go standard-library findings: without it Trivy
|
|
939
|
+
// reports zero `stdlib` vulnerabilities, so a go.mod scan silently misses
|
|
940
|
+
// every one of them and reports a fraction of the cloud's issue count.
|
|
941
|
+
"--detection-priority",
|
|
942
|
+
"comprehensive"
|
|
780
943
|
];
|
|
781
944
|
const severities = [];
|
|
782
945
|
if (enabledPatterns.size === 0) {
|
|
@@ -794,21 +957,23 @@ var adapter = {
|
|
|
794
957
|
vulnArgs.push(ctx.repositoryRoot);
|
|
795
958
|
const vulnOutput = await runTrivyScan(binary, vulnArgs, ctx.repositoryRoot, errors);
|
|
796
959
|
if (vulnOutput) {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
)
|
|
960
|
+
const vulnResult = await parseVulnerabilities(
|
|
961
|
+
vulnOutput,
|
|
962
|
+
enabledPatterns,
|
|
963
|
+
targetFilesSet,
|
|
964
|
+
ctx.repositoryRoot,
|
|
965
|
+
unlocatableFiles
|
|
804
966
|
);
|
|
967
|
+
issues.push(...vulnResult.issues);
|
|
968
|
+
errors.push(...vulnResult.errors);
|
|
805
969
|
if (runMaliciousScan && ctx.runner !== "container") {
|
|
806
970
|
const malResult = await parseMaliciousPackages(
|
|
807
971
|
vulnOutput,
|
|
808
972
|
enabledPatterns,
|
|
809
973
|
targetFilesSet,
|
|
810
974
|
ctx.repositoryRoot,
|
|
811
|
-
ctx.logger
|
|
975
|
+
ctx.logger,
|
|
976
|
+
unlocatableFiles
|
|
812
977
|
);
|
|
813
978
|
issues.push(...malResult.issues);
|
|
814
979
|
errors.push(...malResult.errors);
|
package/dist/metadata.js
CHANGED
|
@@ -44,7 +44,12 @@ var descriptor = {
|
|
|
44
44
|
filePatterns: ["*"],
|
|
45
45
|
supportedOS: ["linux", "darwin", "win32"],
|
|
46
46
|
supportedArch: ["x64", "arm64"],
|
|
47
|
-
supportedRunners: ["local", "container"]
|
|
47
|
+
supportedRunners: ["local", "container"],
|
|
48
|
+
// Trivy reports every CVE under one of four severity umbrella patterns, so its
|
|
49
|
+
// issues must upload `sourceId` (the CVE id) or two CVEs on the same manifest
|
|
50
|
+
// line collapse into a single Codacy result. Matches `needs_source_id_upload`
|
|
51
|
+
// in the Go CLI's Trivy plugin manifest.
|
|
52
|
+
needsSourceIdUpload: true
|
|
48
53
|
};
|
|
49
54
|
// Annotate the CommonJS export names for ESM import in node:
|
|
50
55
|
0 && (module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codacy/tools-trivy-0",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Trivy adapter — CLI-mode security scanner",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"dist"
|
|
9
9
|
],
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@codacy/tooling": "0.
|
|
11
|
+
"@codacy/tooling": "0.18.0"
|
|
12
12
|
},
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"author": "Codacy <support@codacy.com> (https://www.codacy.com)",
|