@codacy/tools-trivy-0 0.23.0 → 0.24.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 +263 -30
- 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",
|
|
@@ -486,14 +640,18 @@ async function checkDbFreshness(cacheDir) {
|
|
|
486
640
|
return { fresh: false, error: message };
|
|
487
641
|
}
|
|
488
642
|
}
|
|
489
|
-
async function runTrivyScan(binary, args, cwd, errors) {
|
|
490
|
-
const { stdout, stderr, exitCode } = await (0, import_tooling.spawnTool)(binary, args, {
|
|
643
|
+
async function runTrivyScan(binary, args, cwd, errors, spawnOptions = {}) {
|
|
644
|
+
const { stdout, stderr, exitCode } = await (0, import_tooling.spawnTool)(binary, args, {
|
|
645
|
+
cwd,
|
|
646
|
+
env: TRIVY_ENV,
|
|
647
|
+
...spawnOptions
|
|
648
|
+
});
|
|
491
649
|
if (exitCode !== 0) {
|
|
492
650
|
errors.push({
|
|
493
651
|
toolId: TOOL_ID,
|
|
494
652
|
phase: "toolInvoke",
|
|
495
653
|
kind: "NonZeroExit",
|
|
496
|
-
message:
|
|
654
|
+
message: trivyExitMessage(exitCode, stderr),
|
|
497
655
|
level: "error"
|
|
498
656
|
});
|
|
499
657
|
return null;
|
|
@@ -514,7 +672,6 @@ async function runTrivyScan(binary, args, cwd, errors) {
|
|
|
514
672
|
return null;
|
|
515
673
|
}
|
|
516
674
|
}
|
|
517
|
-
var LINE_NUMBERS_NOT_SUPPORTED = "Line numbers not supported";
|
|
518
675
|
function recordUnlocatableFinding(errors, seenFiles, filePath) {
|
|
519
676
|
if (seenFiles.has(filePath)) return;
|
|
520
677
|
seenFiles.add(filePath);
|
|
@@ -522,9 +679,10 @@ function recordUnlocatableFinding(errors, seenFiles, filePath) {
|
|
|
522
679
|
toolId: TOOL_ID,
|
|
523
680
|
phase: "outputParse",
|
|
524
681
|
kind: "LineNumbersNotSupported",
|
|
525
|
-
message: LINE_NUMBERS_NOT_SUPPORTED,
|
|
682
|
+
message: import_tooling.LINE_NUMBERS_NOT_SUPPORTED,
|
|
526
683
|
level: "warning",
|
|
527
|
-
filePath
|
|
684
|
+
filePath,
|
|
685
|
+
fileNotAnalyzed: true
|
|
528
686
|
});
|
|
529
687
|
}
|
|
530
688
|
async function parseVulnerabilities(output, enabledPatterns, targetFilesSet, repositoryRoot, unlocatableFiles) {
|
|
@@ -556,13 +714,9 @@ async function parseVulnerabilities(output, enabledPatterns, targetFilesSet, rep
|
|
|
556
714
|
return found;
|
|
557
715
|
};
|
|
558
716
|
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
|
-
};
|
|
717
|
+
const described = describeVulnerability(vuln);
|
|
718
|
+
if (!described) continue;
|
|
719
|
+
if (enabledPatterns.size > 0 && !enabledPatterns.has(described.patternId)) continue;
|
|
566
720
|
const purl = vuln.PkgIdentifier?.PURL;
|
|
567
721
|
const reportedLine = (purl !== void 0 ? lineByPurl.get(purl) : void 0) ?? (vuln.PkgID !== void 0 ? lineByPkgId.get(vuln.PkgID) : void 0) ?? 0;
|
|
568
722
|
const line = reportedLine > 0 ? reportedLine : resolveFallbackLine(vuln.PkgName);
|
|
@@ -570,23 +724,13 @@ async function parseVulnerabilities(output, enabledPatterns, targetFilesSet, rep
|
|
|
570
724
|
recordUnlocatableFinding(errors, unlocatableFiles, relPath);
|
|
571
725
|
continue;
|
|
572
726
|
}
|
|
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
727
|
issues.push({
|
|
728
|
+
...described,
|
|
580
729
|
filePath: relPath,
|
|
581
|
-
patternId,
|
|
582
730
|
toolId: TOOL_ID,
|
|
583
731
|
line,
|
|
584
732
|
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 } : {}
|
|
733
|
+
lineContent: (0, import_tooling.getLineContent)(lines, line)
|
|
590
734
|
});
|
|
591
735
|
}
|
|
592
736
|
}
|
|
@@ -737,12 +881,101 @@ async function parseMaliciousPackages(output, enabledPatterns, targetFilesSet, r
|
|
|
737
881
|
logger.info("Malicious package check complete", { issuesFound: issues.length });
|
|
738
882
|
return { issues, errors };
|
|
739
883
|
}
|
|
884
|
+
async function scanImage(ctx, options) {
|
|
885
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
886
|
+
const errors = [];
|
|
887
|
+
const fail = (kind, message) => {
|
|
888
|
+
errors.push({ toolId: TOOL_ID, phase: "toolInvoke", kind, message, level: "error" });
|
|
889
|
+
const result = (0, import_tooling.buildResult)(TOOL_ID, startedAt, /* @__PURE__ */ new Date(), [], errors, 0);
|
|
890
|
+
return { ...result, summary: { ...result.summary, status: "failed" } };
|
|
891
|
+
};
|
|
892
|
+
try {
|
|
893
|
+
(0, import_tooling.validateImageReference)(options.image);
|
|
894
|
+
} catch (err) {
|
|
895
|
+
return fail("InvalidImageReference", errorMessage(err));
|
|
896
|
+
}
|
|
897
|
+
ctx.logger.info("Starting image scan", { toolId: TOOL_ID, image: options.image });
|
|
898
|
+
try {
|
|
899
|
+
const binary = await resolveBinaryPath(ctx);
|
|
900
|
+
const cacheDir = await ensureCacheDir(ctx.globalDir);
|
|
901
|
+
const skipDbUpdate = await shouldSkipDbUpdate(ctx, cacheDir);
|
|
902
|
+
const args = buildImageScanArgs({ cacheDir, skipDbUpdate }, options);
|
|
903
|
+
const output = await runTrivyScan(binary, args, ctx.repositoryRoot, errors, {
|
|
904
|
+
signal: ctx.signal,
|
|
905
|
+
logger: ctx.logger
|
|
906
|
+
});
|
|
907
|
+
if (!output) {
|
|
908
|
+
const result = (0, import_tooling.buildResult)(TOOL_ID, startedAt, /* @__PURE__ */ new Date(), [], errors, 0);
|
|
909
|
+
return { ...result, summary: { ...result.summary, status: "failed" } };
|
|
910
|
+
}
|
|
911
|
+
const issues = parseImageVulnerabilities(output, TOOL_ID);
|
|
912
|
+
ctx.logger.info("Image scan complete", { toolId: TOOL_ID, issueCount: issues.length });
|
|
913
|
+
return (0, import_tooling.buildResult)(TOOL_ID, startedAt, /* @__PURE__ */ new Date(), issues, errors, 1);
|
|
914
|
+
} catch (err) {
|
|
915
|
+
if (ctx.signal?.aborted) return fail("Cancelled", "Image scan cancelled by caller");
|
|
916
|
+
return fail("InvocationError", `Failed to run trivy: ${errorMessage(err)}`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
async function generateImageSbom(ctx, options) {
|
|
920
|
+
const failure = (kind, message) => ({
|
|
921
|
+
success: false,
|
|
922
|
+
error: { toolId: TOOL_ID, phase: "toolInvoke", kind, message, level: "error" }
|
|
923
|
+
});
|
|
924
|
+
try {
|
|
925
|
+
(0, import_tooling.validateImageReference)(options.image);
|
|
926
|
+
} catch (err) {
|
|
927
|
+
return failure("InvalidImageReference", errorMessage(err));
|
|
928
|
+
}
|
|
929
|
+
try {
|
|
930
|
+
const binary = await resolveBinaryPath(ctx);
|
|
931
|
+
const cacheDir = await ensureCacheDir(ctx.globalDir);
|
|
932
|
+
const args = buildSbomArgs({ cacheDir }, options);
|
|
933
|
+
ctx.logger.info("Generating SBOM", { toolId: TOOL_ID, image: options.image });
|
|
934
|
+
const { stderr, exitCode } = await (0, import_tooling.spawnTool)(binary, args, {
|
|
935
|
+
cwd: ctx.repositoryRoot,
|
|
936
|
+
env: TRIVY_ENV,
|
|
937
|
+
logger: ctx.logger,
|
|
938
|
+
signal: ctx.signal
|
|
939
|
+
});
|
|
940
|
+
if (exitCode !== 0) {
|
|
941
|
+
return failure("NonZeroExit", trivyExitMessage(exitCode, stderr));
|
|
942
|
+
}
|
|
943
|
+
const stat = await import_promises.default.stat(options.outputPath).catch(() => null);
|
|
944
|
+
if (!stat || stat.size === 0) {
|
|
945
|
+
return failure(
|
|
946
|
+
"MissingOutput",
|
|
947
|
+
`trivy exited cleanly but wrote no SBOM to ${options.outputPath}`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
return { success: true };
|
|
951
|
+
} catch (err) {
|
|
952
|
+
if (ctx.signal?.aborted) return failure("Cancelled", "SBOM generation cancelled by caller");
|
|
953
|
+
return failure("InvocationError", `Failed to run trivy: ${errorMessage(err)}`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
async function shouldSkipDbUpdate(ctx, cacheDir) {
|
|
957
|
+
if (ctx.runner === "container") return true;
|
|
958
|
+
const status = await checkDbFreshness(cacheDir);
|
|
959
|
+
if (!status.fresh) {
|
|
960
|
+
ctx.logger.info(
|
|
961
|
+
status.error?.includes("ENOENT") ? "Trivy vulnerability database not found. Downloading (~40MB)..." : "Trivy DB update due, checking for new data..."
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
return status.fresh;
|
|
965
|
+
}
|
|
966
|
+
function errorMessage(err) {
|
|
967
|
+
return err instanceof Error ? err.message : String(err);
|
|
968
|
+
}
|
|
740
969
|
var adapter = {
|
|
741
970
|
...descriptor,
|
|
742
971
|
patternsMetadata: patterns_default,
|
|
743
972
|
compatibleVersions: ">=0.50.0",
|
|
744
973
|
website: "https://trivy.dev/",
|
|
745
974
|
prerequisites: [],
|
|
975
|
+
containerImage: {
|
|
976
|
+
scan: scanImage,
|
|
977
|
+
generateSbom: generateImageSbom
|
|
978
|
+
},
|
|
746
979
|
/**
|
|
747
980
|
* Checks whether the `trivy` binary is present and runnable.
|
|
748
981
|
*
|
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.24.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.24.0"
|
|
12
12
|
},
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"author": "Codacy <support@codacy.com> (https://www.codacy.com)",
|