@codacy/tools-trivy-0 0.23.1 → 0.25.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 +30 -1
- package/dist/index.js +272 -34
- package/dist/metadata.js +6 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
- [Known divergences](#known-divergences)
|
|
13
13
|
- [DB update management](#db-update-management)
|
|
14
14
|
- [Malicious package detection](#malicious-package-detection)
|
|
15
|
+
- [Container image mode](#container-image-mode)
|
|
15
16
|
- [Updating patterns](#updating-patterns)
|
|
16
17
|
- [Updating the Trivy version](#updating-the-trivy-version)
|
|
17
18
|
- [Development](#development)
|
|
@@ -177,6 +178,34 @@ is extracted and cross-referenced against the OpenSSF malicious packages index (
|
|
|
177
178
|
- On API failure (network error, timeout), logs a warning and skips — never blocks the core scan
|
|
178
179
|
- Message format: `Malicious package detected: {name}@{version} ({MAL-ID}). {summary}`
|
|
179
180
|
|
|
181
|
+
## Container image mode
|
|
182
|
+
|
|
183
|
+
Besides the repository scan, the adapter implements the optional `containerImage` capability
|
|
184
|
+
(`src/image.ts`), which backs the CLI's `container-scan` and `upload-sbom` commands. The descriptor
|
|
185
|
+
advertises it with `scansContainerImages: true`.
|
|
186
|
+
|
|
187
|
+
| Method | Invocation |
|
|
188
|
+
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
189
|
+
| `scan` | `trivy image --format json --cache-dir … [--skip-db-update --skip-java-db-update] --scanners vuln [--ignore-unfixed] --severity … --pkg-types … <image>` |
|
|
190
|
+
| `generateSbom` | `trivy image --format cyclonedx\|spdx-json --output <path> --cache-dir … <image>` |
|
|
191
|
+
|
|
192
|
+
- The image reference is re-validated with `validateImageReference` right before it becomes an argv
|
|
193
|
+
element, even though the runner validated it first.
|
|
194
|
+
- `--exit-code` is deliberately **not** passed. The finding count comes from the parsed JSON, so any
|
|
195
|
+
non-zero exit is a real failure — no stderr heuristics like cli-v2's to decide _whether_ it failed.
|
|
196
|
+
- Findings reuse `describeVulnerability` (`src/findings.ts`), the same mapping the repository scan
|
|
197
|
+
uses: umbrella pattern by severity, `codacy-trivy`'s message format, CVE as `sourceId`, `UNKNOWN`
|
|
198
|
+
dropped. The location is Trivy's `Target` (the OS layer or an in-image manifest) at line 1.
|
|
199
|
+
- `generateSbom` succeeds only if Trivy exits 0 **and** wrote a non-empty file.
|
|
200
|
+
- Every Trivy spawn (both modes) sets `TRIVY_SKIP_VERSION_CHECK=true`, and a failure's stderr goes
|
|
201
|
+
through `summarizeTrivyStderr` (`src/trivy-errors.ts`) rather than being echoed: Trivy's stderr is
|
|
202
|
+
a log stream (INFO lines, an ANSI version notice, then a FATAL Go error-wrap chain), and the text
|
|
203
|
+
formatter truncates errors at 240 characters, which used to cut it off before the cause. The env
|
|
204
|
+
var rather than `--skip-version-check`, because an older global Trivy would reject the flag.
|
|
205
|
+
- `test/fixtures/image-scan.json` is trimmed from a real `trivy image` run against
|
|
206
|
+
`nginx:1.21.0-alpine`, plus one synthetic `UNKNOWN` finding and one synthetic library finding.
|
|
207
|
+
`CODACY_TRIVY_IMAGE_IT=1 pnpm test` runs the opt-in integration test against the real registry.
|
|
208
|
+
|
|
180
209
|
## Updating patterns
|
|
181
210
|
|
|
182
211
|
```bash
|
|
@@ -189,7 +218,7 @@ git add src/patterns.json
|
|
|
189
218
|
|
|
190
219
|
## Updating the Trivy version
|
|
191
220
|
|
|
192
|
-
1. Update `preferredVersion` in `src/
|
|
221
|
+
1. Update `preferredVersion` in `src/metadata.ts`
|
|
193
222
|
2. Update the download URL template if the release format changed
|
|
194
223
|
3. Run `pnpm test` to verify compatibility
|
|
195
224
|
|
package/dist/index.js
CHANGED
|
@@ -233,18 +233,19 @@ var patterns_default = [
|
|
|
233
233
|
// src/metadata.ts
|
|
234
234
|
var descriptor = {
|
|
235
235
|
id: "Trivy",
|
|
236
|
+
shortName: "trivy",
|
|
236
237
|
displayName: "Trivy",
|
|
237
238
|
preferredVersion: "0.72.0",
|
|
238
239
|
languages: [
|
|
239
240
|
"Python",
|
|
240
|
-
"
|
|
241
|
+
"Javascript",
|
|
241
242
|
"TypeScript",
|
|
242
243
|
"Go",
|
|
243
244
|
"Java",
|
|
244
245
|
"Ruby",
|
|
245
246
|
"Rust",
|
|
246
247
|
"PHP",
|
|
247
|
-
"
|
|
248
|
+
"CSharp",
|
|
248
249
|
"Dart",
|
|
249
250
|
"Elixir",
|
|
250
251
|
"Swift"
|
|
@@ -257,7 +258,9 @@ var descriptor = {
|
|
|
257
258
|
// issues must upload `sourceId` (the CVE id) or two CVEs on the same manifest
|
|
258
259
|
// line collapse into a single Codacy result. Matches `needs_source_id_upload`
|
|
259
260
|
// in the Go CLI's Trivy plugin manifest.
|
|
260
|
-
needsSourceIdUpload: true
|
|
261
|
+
needsSourceIdUpload: true,
|
|
262
|
+
// Implements `containerImage` (`trivy image`) for `container-scan` / `upload-sbom`.
|
|
263
|
+
scansContainerImages: true
|
|
261
264
|
};
|
|
262
265
|
|
|
263
266
|
// src/vuln-mapping.ts
|
|
@@ -366,8 +369,7 @@ function comparePrerelease(x, y) {
|
|
|
366
369
|
return xs.length < ys.length ? -1 : 1;
|
|
367
370
|
}
|
|
368
371
|
|
|
369
|
-
// src/
|
|
370
|
-
var TOOL_ID = "Trivy";
|
|
372
|
+
// src/findings.ts
|
|
371
373
|
var PATTERN_META = new Map(
|
|
372
374
|
patterns_default.map((p) => [
|
|
373
375
|
p.id,
|
|
@@ -383,6 +385,158 @@ var VULN_SEVERITY_TO_PATTERN = {
|
|
|
383
385
|
MEDIUM: "Trivy_vulnerability_medium",
|
|
384
386
|
LOW: "Trivy_vulnerability_minor"
|
|
385
387
|
};
|
|
388
|
+
function describeVulnerability(vuln) {
|
|
389
|
+
const patternId = VULN_SEVERITY_TO_PATTERN[vuln.Severity];
|
|
390
|
+
if (!patternId) return void 0;
|
|
391
|
+
const meta = PATTERN_META.get(patternId) ?? {
|
|
392
|
+
category: "Security",
|
|
393
|
+
severity: "Info"
|
|
394
|
+
};
|
|
395
|
+
const fixedVersion = findLeastDisruptiveFixedVersion(
|
|
396
|
+
vuln.InstalledVersion,
|
|
397
|
+
vuln.FixedVersion ?? ""
|
|
398
|
+
);
|
|
399
|
+
const fix = fixedVersion ? `(update to ${fixedVersion})` : "(no fix available)";
|
|
400
|
+
const purl = vuln.PkgIdentifier?.PURL;
|
|
401
|
+
const pkgLabel = purl ? purlPrettyPrint(purl) : `${vuln.PkgName}@${vuln.InstalledVersion}`;
|
|
402
|
+
return {
|
|
403
|
+
patternId,
|
|
404
|
+
message: `Insecure dependency ${pkgLabel} (${vuln.VulnerabilityID}: ${vuln.Title ?? ""}) ${fix}`,
|
|
405
|
+
category: meta.category,
|
|
406
|
+
severity: meta.severity,
|
|
407
|
+
...vuln.VulnerabilityID ? { sourceId: vuln.VulnerabilityID } : {}
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/image.ts
|
|
412
|
+
function buildImageScanArgs(base, options) {
|
|
413
|
+
const args = [
|
|
414
|
+
"image",
|
|
415
|
+
"--format",
|
|
416
|
+
"json",
|
|
417
|
+
"--cache-dir",
|
|
418
|
+
base.cacheDir,
|
|
419
|
+
...dbUpdateArgs(base),
|
|
420
|
+
"--scanners",
|
|
421
|
+
"vuln"
|
|
422
|
+
];
|
|
423
|
+
if (options.ignoreUnfixed) args.push("--ignore-unfixed");
|
|
424
|
+
args.push("--severity", options.severities.join(","));
|
|
425
|
+
args.push("--pkg-types", options.pkgTypes.join(","));
|
|
426
|
+
args.push(options.image);
|
|
427
|
+
return args;
|
|
428
|
+
}
|
|
429
|
+
function buildSbomArgs(base, options) {
|
|
430
|
+
return [
|
|
431
|
+
"image",
|
|
432
|
+
"--format",
|
|
433
|
+
options.format,
|
|
434
|
+
"--output",
|
|
435
|
+
options.outputPath,
|
|
436
|
+
"--cache-dir",
|
|
437
|
+
base.cacheDir,
|
|
438
|
+
options.image
|
|
439
|
+
];
|
|
440
|
+
}
|
|
441
|
+
function dbUpdateArgs(base) {
|
|
442
|
+
return base.skipDbUpdate ? ["--skip-db-update", "--skip-java-db-update"] : [];
|
|
443
|
+
}
|
|
444
|
+
function parseImageVulnerabilities(output, toolId) {
|
|
445
|
+
const issues = [];
|
|
446
|
+
for (const result of output.Results ?? []) {
|
|
447
|
+
for (const vuln of result.Vulnerabilities ?? []) {
|
|
448
|
+
const described = describeVulnerability(vuln);
|
|
449
|
+
if (!described) continue;
|
|
450
|
+
issues.push({
|
|
451
|
+
...described,
|
|
452
|
+
filePath: result.Target,
|
|
453
|
+
toolId,
|
|
454
|
+
line: 1,
|
|
455
|
+
column: 1,
|
|
456
|
+
lineContent: ""
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return issues;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/trivy-errors.ts
|
|
464
|
+
var TRIVY_ENV = { TRIVY_SKIP_VERSION_CHECK: "true" };
|
|
465
|
+
function stripAnsi(text) {
|
|
466
|
+
return text.replace(/\x1b\[[0-9;]*[A-Za-z]/g, "");
|
|
467
|
+
}
|
|
468
|
+
var WRAPPER_PREFIXES = [
|
|
469
|
+
/^run error:\s*/i,
|
|
470
|
+
/^(?:image|fs|filesystem|repository|rootfs|sbom|vm) scan error:\s*/i,
|
|
471
|
+
/^scan error:\s*/i,
|
|
472
|
+
/^unable to initialize (?:a scan service|artifact|container image):\s*/i
|
|
473
|
+
];
|
|
474
|
+
function stripWrappers(chain) {
|
|
475
|
+
let current = chain.trim();
|
|
476
|
+
let changed = true;
|
|
477
|
+
while (changed) {
|
|
478
|
+
changed = false;
|
|
479
|
+
for (const prefix of WRAPPER_PREFIXES) {
|
|
480
|
+
const next = current.replace(prefix, "");
|
|
481
|
+
if (next !== current) {
|
|
482
|
+
current = next;
|
|
483
|
+
changed = true;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return current;
|
|
488
|
+
}
|
|
489
|
+
function summarizeTrivyStderr(stderr) {
|
|
490
|
+
const text = stripAnsi(stderr);
|
|
491
|
+
const lines = text.split(/\r?\n/);
|
|
492
|
+
const fatalIndex = lines.findIndex((l) => /\tFATAL\t|\sFATAL\s/.test(l));
|
|
493
|
+
if (fatalIndex !== -1) {
|
|
494
|
+
const chain = lines[fatalIndex].replace(/^.*?\bFATAL\s+(?:Fatal error\s+)?/, "");
|
|
495
|
+
const details = lines.slice(fatalIndex + 1).map((l) => l.trim());
|
|
496
|
+
const missing = chain.match(/unable to find the specified image "([^"]+)"/);
|
|
497
|
+
if (missing) {
|
|
498
|
+
const remote = details.find((l) => /^\*\s*remote error:/i.test(l))?.replace(/^\*\s*remote error:\s*/i, "");
|
|
499
|
+
const base = `Image "${missing[1]}" not found locally (Docker, containerd, Podman) or in its registry`;
|
|
500
|
+
if (!remote) return base;
|
|
501
|
+
if (/UNAUTHORIZED|authentication required|denied/i.test(remote)) {
|
|
502
|
+
return `${base}: access denied \u2014 the image or tag does not exist, or it is private and needs a registry login`;
|
|
503
|
+
}
|
|
504
|
+
return `${base}: ${remote.replace(/\s*\[map\[.*$/, "")}`;
|
|
505
|
+
}
|
|
506
|
+
return stripWrappers(chain).replace(/:\s*\d+ errors? occurred:?$/i, "");
|
|
507
|
+
}
|
|
508
|
+
return withoutNoise(lines).join(" ");
|
|
509
|
+
}
|
|
510
|
+
var TIMESTAMPED = /^\d{4}-\d{2}-\d{2}T\S+\s+/;
|
|
511
|
+
var QUIET_LOG_LINE = /^\d{4}-\d{2}-\d{2}T\S+\s+(?:DEBUG|INFO)\b/;
|
|
512
|
+
function withoutNoise(lines) {
|
|
513
|
+
const kept = [];
|
|
514
|
+
let inNotice = false;
|
|
515
|
+
for (const raw of lines) {
|
|
516
|
+
const line = raw.trim();
|
|
517
|
+
if (/Notices:/.test(line)) {
|
|
518
|
+
inNotice = true;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
if (inNotice) {
|
|
522
|
+
if (/--skip-version-check/.test(line)) {
|
|
523
|
+
inNotice = false;
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
if (!TIMESTAMPED.test(line)) continue;
|
|
527
|
+
inNotice = false;
|
|
528
|
+
}
|
|
529
|
+
if (line.length === 0 || QUIET_LOG_LINE.test(line)) continue;
|
|
530
|
+
kept.push(line.replace(TIMESTAMPED, "").replace(/\t/g, " "));
|
|
531
|
+
}
|
|
532
|
+
return kept;
|
|
533
|
+
}
|
|
534
|
+
function trivyExitMessage(exitCode, stderr) {
|
|
535
|
+
return summarizeTrivyStderr(stderr) || `trivy exited with code ${exitCode}`;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/adapter.ts
|
|
539
|
+
var TOOL_ID = "Trivy";
|
|
386
540
|
var PATTERN_TO_SEVERITY = {
|
|
387
541
|
Trivy_vulnerability_critical: "CRITICAL",
|
|
388
542
|
Trivy_vulnerability_high: "HIGH",
|
|
@@ -443,7 +597,11 @@ var PLATFORM_MAP = {
|
|
|
443
597
|
"linux-arm64": "Linux-ARM64",
|
|
444
598
|
// Windows ships a `.zip` (trivy_{version}_windows-64bit.zip) with `trivy.exe`
|
|
445
599
|
// at the archive root — handled in install (extension + exeName).
|
|
446
|
-
"win32-x64": "windows-64bit"
|
|
600
|
+
"win32-x64": "windows-64bit",
|
|
601
|
+
// Trivy publishes no Windows ARM64 build; Windows on ARM runs the x64 binary
|
|
602
|
+
// under emulation, so use it rather than reporting the platform unsupported
|
|
603
|
+
// (the descriptor already declares arm64).
|
|
604
|
+
"win32-arm64": "windows-64bit"
|
|
447
605
|
};
|
|
448
606
|
async function getVersionFromBinary(binaryPath) {
|
|
449
607
|
try {
|
|
@@ -486,14 +644,18 @@ async function checkDbFreshness(cacheDir) {
|
|
|
486
644
|
return { fresh: false, error: message };
|
|
487
645
|
}
|
|
488
646
|
}
|
|
489
|
-
async function runTrivyScan(binary, args, cwd, errors) {
|
|
490
|
-
const { stdout, stderr, exitCode } = await (0, import_tooling.spawnTool)(binary, args, {
|
|
647
|
+
async function runTrivyScan(binary, args, cwd, errors, spawnOptions = {}) {
|
|
648
|
+
const { stdout, stderr, exitCode } = await (0, import_tooling.spawnTool)(binary, args, {
|
|
649
|
+
cwd,
|
|
650
|
+
env: TRIVY_ENV,
|
|
651
|
+
...spawnOptions
|
|
652
|
+
});
|
|
491
653
|
if (exitCode !== 0) {
|
|
492
654
|
errors.push({
|
|
493
655
|
toolId: TOOL_ID,
|
|
494
656
|
phase: "toolInvoke",
|
|
495
657
|
kind: "NonZeroExit",
|
|
496
|
-
message:
|
|
658
|
+
message: trivyExitMessage(exitCode, stderr),
|
|
497
659
|
level: "error"
|
|
498
660
|
});
|
|
499
661
|
return null;
|
|
@@ -514,7 +676,6 @@ async function runTrivyScan(binary, args, cwd, errors) {
|
|
|
514
676
|
return null;
|
|
515
677
|
}
|
|
516
678
|
}
|
|
517
|
-
var LINE_NUMBERS_NOT_SUPPORTED = "Line numbers not supported";
|
|
518
679
|
function recordUnlocatableFinding(errors, seenFiles, filePath) {
|
|
519
680
|
if (seenFiles.has(filePath)) return;
|
|
520
681
|
seenFiles.add(filePath);
|
|
@@ -522,9 +683,10 @@ function recordUnlocatableFinding(errors, seenFiles, filePath) {
|
|
|
522
683
|
toolId: TOOL_ID,
|
|
523
684
|
phase: "outputParse",
|
|
524
685
|
kind: "LineNumbersNotSupported",
|
|
525
|
-
message: LINE_NUMBERS_NOT_SUPPORTED,
|
|
686
|
+
message: import_tooling.LINE_NUMBERS_NOT_SUPPORTED,
|
|
526
687
|
level: "warning",
|
|
527
|
-
filePath
|
|
688
|
+
filePath,
|
|
689
|
+
fileNotAnalyzed: true
|
|
528
690
|
});
|
|
529
691
|
}
|
|
530
692
|
async function parseVulnerabilities(output, enabledPatterns, targetFilesSet, repositoryRoot, unlocatableFiles) {
|
|
@@ -556,13 +718,9 @@ async function parseVulnerabilities(output, enabledPatterns, targetFilesSet, rep
|
|
|
556
718
|
return found;
|
|
557
719
|
};
|
|
558
720
|
for (const vuln of result.Vulnerabilities) {
|
|
559
|
-
const
|
|
560
|
-
if (!
|
|
561
|
-
if (enabledPatterns.size > 0 && !enabledPatterns.has(patternId)) continue;
|
|
562
|
-
const meta = PATTERN_META.get(patternId) ?? {
|
|
563
|
-
category: "Security",
|
|
564
|
-
severity: "Info"
|
|
565
|
-
};
|
|
721
|
+
const described = describeVulnerability(vuln);
|
|
722
|
+
if (!described) continue;
|
|
723
|
+
if (enabledPatterns.size > 0 && !enabledPatterns.has(described.patternId)) continue;
|
|
566
724
|
const purl = vuln.PkgIdentifier?.PURL;
|
|
567
725
|
const reportedLine = (purl !== void 0 ? lineByPurl.get(purl) : void 0) ?? (vuln.PkgID !== void 0 ? lineByPkgId.get(vuln.PkgID) : void 0) ?? 0;
|
|
568
726
|
const line = reportedLine > 0 ? reportedLine : resolveFallbackLine(vuln.PkgName);
|
|
@@ -570,23 +728,13 @@ async function parseVulnerabilities(output, enabledPatterns, targetFilesSet, rep
|
|
|
570
728
|
recordUnlocatableFinding(errors, unlocatableFiles, relPath);
|
|
571
729
|
continue;
|
|
572
730
|
}
|
|
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}`;
|
|
579
731
|
issues.push({
|
|
732
|
+
...described,
|
|
580
733
|
filePath: relPath,
|
|
581
|
-
patternId,
|
|
582
734
|
toolId: TOOL_ID,
|
|
583
735
|
line,
|
|
584
736
|
column: 1,
|
|
585
|
-
|
|
586
|
-
lineContent: (0, import_tooling.getLineContent)(lines, line),
|
|
587
|
-
category: meta.category,
|
|
588
|
-
severity: meta.severity,
|
|
589
|
-
...vuln.VulnerabilityID ? { sourceId: vuln.VulnerabilityID } : {}
|
|
737
|
+
lineContent: (0, import_tooling.getLineContent)(lines, line)
|
|
590
738
|
});
|
|
591
739
|
}
|
|
592
740
|
}
|
|
@@ -737,12 +885,101 @@ async function parseMaliciousPackages(output, enabledPatterns, targetFilesSet, r
|
|
|
737
885
|
logger.info("Malicious package check complete", { issuesFound: issues.length });
|
|
738
886
|
return { issues, errors };
|
|
739
887
|
}
|
|
888
|
+
async function scanImage(ctx, options) {
|
|
889
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
890
|
+
const errors = [];
|
|
891
|
+
const fail = (kind, message) => {
|
|
892
|
+
errors.push({ toolId: TOOL_ID, phase: "toolInvoke", kind, message, level: "error" });
|
|
893
|
+
const result = (0, import_tooling.buildResult)(TOOL_ID, startedAt, /* @__PURE__ */ new Date(), [], errors, 0);
|
|
894
|
+
return { ...result, summary: { ...result.summary, status: "failed" } };
|
|
895
|
+
};
|
|
896
|
+
try {
|
|
897
|
+
(0, import_tooling.validateImageReference)(options.image);
|
|
898
|
+
} catch (err) {
|
|
899
|
+
return fail("InvalidImageReference", errorMessage(err));
|
|
900
|
+
}
|
|
901
|
+
ctx.logger.info("Starting image scan", { toolId: TOOL_ID, image: options.image });
|
|
902
|
+
try {
|
|
903
|
+
const binary = await resolveBinaryPath(ctx);
|
|
904
|
+
const cacheDir = await ensureCacheDir(ctx.globalDir);
|
|
905
|
+
const skipDbUpdate = await shouldSkipDbUpdate(ctx, cacheDir);
|
|
906
|
+
const args = buildImageScanArgs({ cacheDir, skipDbUpdate }, options);
|
|
907
|
+
const output = await runTrivyScan(binary, args, ctx.repositoryRoot, errors, {
|
|
908
|
+
signal: ctx.signal,
|
|
909
|
+
logger: ctx.logger
|
|
910
|
+
});
|
|
911
|
+
if (!output) {
|
|
912
|
+
const result = (0, import_tooling.buildResult)(TOOL_ID, startedAt, /* @__PURE__ */ new Date(), [], errors, 0);
|
|
913
|
+
return { ...result, summary: { ...result.summary, status: "failed" } };
|
|
914
|
+
}
|
|
915
|
+
const issues = parseImageVulnerabilities(output, TOOL_ID);
|
|
916
|
+
ctx.logger.info("Image scan complete", { toolId: TOOL_ID, issueCount: issues.length });
|
|
917
|
+
return (0, import_tooling.buildResult)(TOOL_ID, startedAt, /* @__PURE__ */ new Date(), issues, errors, 1);
|
|
918
|
+
} catch (err) {
|
|
919
|
+
if (ctx.signal?.aborted) return fail("Cancelled", "Image scan cancelled by caller");
|
|
920
|
+
return fail("InvocationError", `Failed to run trivy: ${errorMessage(err)}`);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
async function generateImageSbom(ctx, options) {
|
|
924
|
+
const failure = (kind, message) => ({
|
|
925
|
+
success: false,
|
|
926
|
+
error: { toolId: TOOL_ID, phase: "toolInvoke", kind, message, level: "error" }
|
|
927
|
+
});
|
|
928
|
+
try {
|
|
929
|
+
(0, import_tooling.validateImageReference)(options.image);
|
|
930
|
+
} catch (err) {
|
|
931
|
+
return failure("InvalidImageReference", errorMessage(err));
|
|
932
|
+
}
|
|
933
|
+
try {
|
|
934
|
+
const binary = await resolveBinaryPath(ctx);
|
|
935
|
+
const cacheDir = await ensureCacheDir(ctx.globalDir);
|
|
936
|
+
const args = buildSbomArgs({ cacheDir }, options);
|
|
937
|
+
ctx.logger.info("Generating SBOM", { toolId: TOOL_ID, image: options.image });
|
|
938
|
+
const { stderr, exitCode } = await (0, import_tooling.spawnTool)(binary, args, {
|
|
939
|
+
cwd: ctx.repositoryRoot,
|
|
940
|
+
env: TRIVY_ENV,
|
|
941
|
+
logger: ctx.logger,
|
|
942
|
+
signal: ctx.signal
|
|
943
|
+
});
|
|
944
|
+
if (exitCode !== 0) {
|
|
945
|
+
return failure("NonZeroExit", trivyExitMessage(exitCode, stderr));
|
|
946
|
+
}
|
|
947
|
+
const stat = await import_promises.default.stat(options.outputPath).catch(() => null);
|
|
948
|
+
if (!stat || stat.size === 0) {
|
|
949
|
+
return failure(
|
|
950
|
+
"MissingOutput",
|
|
951
|
+
`trivy exited cleanly but wrote no SBOM to ${options.outputPath}`
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
return { success: true };
|
|
955
|
+
} catch (err) {
|
|
956
|
+
if (ctx.signal?.aborted) return failure("Cancelled", "SBOM generation cancelled by caller");
|
|
957
|
+
return failure("InvocationError", `Failed to run trivy: ${errorMessage(err)}`);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
async function shouldSkipDbUpdate(ctx, cacheDir) {
|
|
961
|
+
if (ctx.runner === "container") return true;
|
|
962
|
+
const status = await checkDbFreshness(cacheDir);
|
|
963
|
+
if (!status.fresh) {
|
|
964
|
+
ctx.logger.info(
|
|
965
|
+
status.error?.includes("ENOENT") ? "Trivy vulnerability database not found. Downloading (~40MB)..." : "Trivy DB update due, checking for new data..."
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
return status.fresh;
|
|
969
|
+
}
|
|
970
|
+
function errorMessage(err) {
|
|
971
|
+
return err instanceof Error ? err.message : String(err);
|
|
972
|
+
}
|
|
740
973
|
var adapter = {
|
|
741
974
|
...descriptor,
|
|
742
975
|
patternsMetadata: patterns_default,
|
|
743
976
|
compatibleVersions: ">=0.50.0",
|
|
744
977
|
website: "https://trivy.dev/",
|
|
745
978
|
prerequisites: [],
|
|
979
|
+
containerImage: {
|
|
980
|
+
scan: scanImage,
|
|
981
|
+
generateSbom: generateImageSbom
|
|
982
|
+
},
|
|
746
983
|
/**
|
|
747
984
|
* Checks whether the `trivy` binary is present and runnable.
|
|
748
985
|
*
|
|
@@ -789,8 +1026,9 @@ var adapter = {
|
|
|
789
1026
|
/**
|
|
790
1027
|
* Downloads trivy from GitHub releases and extracts it to ctx.toolInstallDir.
|
|
791
1028
|
*
|
|
792
|
-
* Detects the current platform/arch and downloads the
|
|
793
|
-
* Sets executable permission after
|
|
1029
|
+
* Detects the current platform/arch and downloads the matching archive
|
|
1030
|
+
* (`.zip` on Windows, `.tar.gz` elsewhere). Sets executable permission after
|
|
1031
|
+
* extraction (not needed on Windows).
|
|
794
1032
|
*/
|
|
795
1033
|
async install(ctx) {
|
|
796
1034
|
ctx.logger.info("Starting install", { toolId: TOOL_ID });
|
|
@@ -809,7 +1047,7 @@ var adapter = {
|
|
|
809
1047
|
};
|
|
810
1048
|
}
|
|
811
1049
|
const version = adapter.preferredVersion;
|
|
812
|
-
const archiveExt =
|
|
1050
|
+
const archiveExt = ctx.platform.os === "win32" ? "zip" : "tar.gz";
|
|
813
1051
|
const url = `https://github.com/aquasecurity/trivy/releases/download/v${version}/trivy_${version}_${platformSuffix}.${archiveExt}`;
|
|
814
1052
|
const destPath = import_path.default.join(ctx.toolInstallDir, (0, import_tooling.exeName)(BINARY));
|
|
815
1053
|
try {
|
package/dist/metadata.js
CHANGED
|
@@ -25,18 +25,19 @@ __export(metadata_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(metadata_exports);
|
|
26
26
|
var descriptor = {
|
|
27
27
|
id: "Trivy",
|
|
28
|
+
shortName: "trivy",
|
|
28
29
|
displayName: "Trivy",
|
|
29
30
|
preferredVersion: "0.72.0",
|
|
30
31
|
languages: [
|
|
31
32
|
"Python",
|
|
32
|
-
"
|
|
33
|
+
"Javascript",
|
|
33
34
|
"TypeScript",
|
|
34
35
|
"Go",
|
|
35
36
|
"Java",
|
|
36
37
|
"Ruby",
|
|
37
38
|
"Rust",
|
|
38
39
|
"PHP",
|
|
39
|
-
"
|
|
40
|
+
"CSharp",
|
|
40
41
|
"Dart",
|
|
41
42
|
"Elixir",
|
|
42
43
|
"Swift"
|
|
@@ -49,7 +50,9 @@ var descriptor = {
|
|
|
49
50
|
// issues must upload `sourceId` (the CVE id) or two CVEs on the same manifest
|
|
50
51
|
// line collapse into a single Codacy result. Matches `needs_source_id_upload`
|
|
51
52
|
// in the Go CLI's Trivy plugin manifest.
|
|
52
|
-
needsSourceIdUpload: true
|
|
53
|
+
needsSourceIdUpload: true,
|
|
54
|
+
// Implements `containerImage` (`trivy image`) for `container-scan` / `upload-sbom`.
|
|
55
|
+
scansContainerImages: true
|
|
53
56
|
};
|
|
54
57
|
// Annotate the CommonJS export names for ESM import in node:
|
|
55
58
|
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.25.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.25.0"
|
|
12
12
|
},
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"author": "Codacy <support@codacy.com> (https://www.codacy.com)",
|