@codacy/tools-trivy-0 0.3.0 → 0.5.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 +47 -19
- package/dist/index.js +232 -66
- package/dist/metadata.d.ts +13 -0
- package/dist/metadata.js +52 -0
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# @codacy/tools-trivy-0
|
|
2
2
|
|
|
3
3
|
## Table of Contents
|
|
4
|
+
|
|
4
5
|
- [Overview](#overview)
|
|
5
6
|
- [Umbrella patterns](#umbrella-patterns)
|
|
7
|
+
- [DB update management](#db-update-management)
|
|
8
|
+
- [Malicious package detection](#malicious-package-detection)
|
|
6
9
|
- [Updating patterns](#updating-patterns)
|
|
7
10
|
- [Updating the Trivy version](#updating-the-trivy-version)
|
|
8
11
|
- [Development](#development)
|
|
@@ -14,28 +17,52 @@
|
|
|
14
17
|
|
|
15
18
|
Security scanner using the [Trivy](https://trivy.dev/) binary. Scans for vulnerabilities in dependencies and hardcoded secrets. Uses the CLI execution strategy with two separate scan modes (vulnerability + secret).
|
|
16
19
|
|
|
17
|
-
| Property
|
|
18
|
-
|
|
19
|
-
| Tool ID
|
|
20
|
-
| Codacy UUID
|
|
21
|
-
| Strategy
|
|
22
|
-
| Languages
|
|
23
|
-
| Binary
|
|
24
|
-
| File patterns | `*` (auto-detects dependency manifests)
|
|
25
|
-
| Pattern count | 6 (umbrella patterns)
|
|
20
|
+
| Property | Value |
|
|
21
|
+
| ------------- | ------------------------------------------- |
|
|
22
|
+
| Tool ID | `Trivy` |
|
|
23
|
+
| Codacy UUID | `2fd7fbe0-33f9-4ab3-ab73-e9b62404e2cb` |
|
|
24
|
+
| Strategy | CLI |
|
|
25
|
+
| Languages | Multi-language (scans dependency manifests) |
|
|
26
|
+
| Binary | `trivy` |
|
|
27
|
+
| File patterns | `*` (auto-detects dependency manifests) |
|
|
28
|
+
| Pattern count | 6 (umbrella patterns) |
|
|
26
29
|
|
|
27
30
|
## Umbrella patterns
|
|
28
31
|
|
|
29
32
|
Trivy uses umbrella patterns rather than individual CVE patterns:
|
|
30
33
|
|
|
31
|
-
| Pattern ID
|
|
32
|
-
|
|
33
|
-
| `Trivy_vulnerability_critical` | Error
|
|
34
|
-
| `Trivy_vulnerability_high`
|
|
35
|
-
| `Trivy_vulnerability_medium`
|
|
36
|
-
| `Trivy_vulnerability_minor`
|
|
37
|
-
| `Trivy_secret`
|
|
38
|
-
| `Trivy_malicious_packages`
|
|
34
|
+
| Pattern ID | Severity | Description |
|
|
35
|
+
| ------------------------------ | -------- | ------------------------------------ |
|
|
36
|
+
| `Trivy_vulnerability_critical` | Error | Critical vulnerabilities |
|
|
37
|
+
| `Trivy_vulnerability_high` | High | High-severity vulnerabilities |
|
|
38
|
+
| `Trivy_vulnerability_medium` | Warning | Medium-severity vulnerabilities |
|
|
39
|
+
| `Trivy_vulnerability_minor` | Info | Low-severity vulnerabilities |
|
|
40
|
+
| `Trivy_secret` | Error | Hardcoded secrets |
|
|
41
|
+
| `Trivy_malicious_packages` | Error | Malicious packages (via OSV.dev API) |
|
|
42
|
+
|
|
43
|
+
## DB update management
|
|
44
|
+
|
|
45
|
+
The adapter reads Trivy's `metadata.json` (at `{cacheDir}/db/metadata.json`) to
|
|
46
|
+
determine if the vulnerability database is still fresh. The `NextUpdate` field
|
|
47
|
+
tells us when Trivy expects the next DB update.
|
|
48
|
+
|
|
49
|
+
| Scenario | Behavior |
|
|
50
|
+
| ------------------------------ | ------------------------------------------------------------------ |
|
|
51
|
+
| DB fresh (`now < NextUpdate`) | `--skip-db-update --skip-java-db-update` passed — no network check |
|
|
52
|
+
| DB stale (`now >= NextUpdate`) | Trivy checks for updates normally |
|
|
53
|
+
| First run (no metadata) | Trivy downloads the DB (~40MB) |
|
|
54
|
+
| Container mode | Always skips DB updates (DBs are pre-baked) |
|
|
55
|
+
|
|
56
|
+
## Malicious package detection
|
|
57
|
+
|
|
58
|
+
Detects known malicious packages by querying the [OSV.dev batch API](https://api.osv.dev/v1/querybatch).
|
|
59
|
+
This piggybacks on the vulnerability scan output — the package list from `--list-all-pkgs`
|
|
60
|
+
is extracted and cross-referenced against the OpenSSF malicious packages index (`MAL-*` IDs).
|
|
61
|
+
|
|
62
|
+
- Covers 14 ecosystems: npm, PyPI, Go, RubyGems, crates.io, NuGet, Maven, Packagist, Pub, Hex, ConanCenter, CocoaPods, SwiftURL, conda
|
|
63
|
+
- Uses a 10-second timeout per API call
|
|
64
|
+
- On API failure (network error, timeout), logs a warning and skips — never blocks the core scan
|
|
65
|
+
- Message format: `Malicious package detected: {name}@{version} ({MAL-ID}). {summary}`
|
|
39
66
|
|
|
40
67
|
## Updating patterns
|
|
41
68
|
|
|
@@ -62,10 +89,11 @@ pnpm test # Run tests (requires trivy in PATH or auto-install)
|
|
|
62
89
|
|
|
63
90
|
## Notes for maintainers
|
|
64
91
|
|
|
65
|
-
- Trivy runs **two separate scan invocations**: one for vulnerabilities (`--scanners vuln`) and one for secrets (`--scanners secret`). Which scans run depends on which umbrella patterns are enabled.
|
|
66
|
-
- The vulnerability database is downloaded lazily on first run to `--cache-dir ~/.codacy/cache/trivy/`. Internet access is required on first use.
|
|
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.
|
|
93
|
+
- 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`.
|
|
67
94
|
- Trivy scans **whole directories**, not individual files. Results are filtered to `ctx.targetFiles` after scanning.
|
|
68
95
|
- The binary is downloaded from GitHub releases as a `.tar.gz` archive.
|
|
69
96
|
- Vulnerability severity (CRITICAL/HIGH/MEDIUM/LOW) maps to the corresponding umbrella pattern.
|
|
70
97
|
- Message format: `Insecure dependency {pkg}@{version} ({CVE}: {title}). Update to {fixedVersion}.`
|
|
98
|
+
- Malicious package detection uses the OSV.dev batch API. On failure, it degrades gracefully (warning only).
|
|
71
99
|
- Config file: checks for `trivy.yaml` in the repo root.
|
package/dist/index.js
CHANGED
|
@@ -92,19 +92,33 @@ var patterns_default = [
|
|
|
92
92
|
}
|
|
93
93
|
];
|
|
94
94
|
|
|
95
|
+
// src/metadata.ts
|
|
96
|
+
var descriptor = {
|
|
97
|
+
id: "Trivy",
|
|
98
|
+
displayName: "Trivy",
|
|
99
|
+
preferredVersion: "0.69.3",
|
|
100
|
+
languages: [
|
|
101
|
+
"Python",
|
|
102
|
+
"JavaScript",
|
|
103
|
+
"TypeScript",
|
|
104
|
+
"Go",
|
|
105
|
+
"Java",
|
|
106
|
+
"Ruby",
|
|
107
|
+
"Rust",
|
|
108
|
+
"PHP",
|
|
109
|
+
"C#",
|
|
110
|
+
"Dart",
|
|
111
|
+
"Elixir",
|
|
112
|
+
"Swift"
|
|
113
|
+
],
|
|
114
|
+
filePatterns: ["*"],
|
|
115
|
+
supportedOS: ["linux", "darwin"],
|
|
116
|
+
supportedArch: ["x64", "arm64"],
|
|
117
|
+
supportedRunners: ["local", "container"]
|
|
118
|
+
};
|
|
119
|
+
|
|
95
120
|
// src/adapter.ts
|
|
96
121
|
var TOOL_ID = "Trivy";
|
|
97
|
-
function getLineContent(lines, lineNumber) {
|
|
98
|
-
return lines[lineNumber - 1] ?? "";
|
|
99
|
-
}
|
|
100
|
-
async function readFileLines(filePath) {
|
|
101
|
-
try {
|
|
102
|
-
const content = await import_promises.default.readFile(filePath, "utf-8");
|
|
103
|
-
return content.split("\n");
|
|
104
|
-
} catch {
|
|
105
|
-
return [];
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
122
|
var PATTERN_META = new Map(
|
|
109
123
|
patterns_default.map((p) => [
|
|
110
124
|
p.id,
|
|
@@ -126,6 +140,52 @@ var PATTERN_TO_SEVERITY = {
|
|
|
126
140
|
Trivy_vulnerability_medium: "MEDIUM",
|
|
127
141
|
Trivy_vulnerability_minor: "LOW"
|
|
128
142
|
};
|
|
143
|
+
var TRIVY_TYPE_TO_OSV_ECOSYSTEM = {
|
|
144
|
+
npm: "npm",
|
|
145
|
+
pip: "PyPI",
|
|
146
|
+
pipenv: "PyPI",
|
|
147
|
+
poetry: "PyPI",
|
|
148
|
+
conda: "conda",
|
|
149
|
+
gomod: "Go",
|
|
150
|
+
gem: "RubyGems",
|
|
151
|
+
bundler: "RubyGems",
|
|
152
|
+
cargo: "crates.io",
|
|
153
|
+
nuget: "NuGet",
|
|
154
|
+
pom: "Maven",
|
|
155
|
+
gradle: "Maven",
|
|
156
|
+
composer: "Packagist",
|
|
157
|
+
pub: "Pub",
|
|
158
|
+
hex: "Hex",
|
|
159
|
+
conan: "ConanCenter",
|
|
160
|
+
cocoapods: "CocoaPods",
|
|
161
|
+
swift: "SwiftURL"
|
|
162
|
+
};
|
|
163
|
+
var OSV_BATCH_SIZE = 1e3;
|
|
164
|
+
var OSV_TIMEOUT_MS = 1e4;
|
|
165
|
+
async function queryOsvBatch(queries) {
|
|
166
|
+
const allResults = [];
|
|
167
|
+
for (let i = 0; i < queries.length; i += OSV_BATCH_SIZE) {
|
|
168
|
+
const batch = queries.slice(i, i + OSV_BATCH_SIZE);
|
|
169
|
+
const controller = new AbortController();
|
|
170
|
+
const timer = setTimeout(() => controller.abort(), OSV_TIMEOUT_MS);
|
|
171
|
+
try {
|
|
172
|
+
const response = await fetch("https://api.osv.dev/v1/querybatch", {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: { "Content-Type": "application/json" },
|
|
175
|
+
body: JSON.stringify({ queries: batch }),
|
|
176
|
+
signal: controller.signal
|
|
177
|
+
});
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
throw new Error(`OSV API returned HTTP ${response.status}: ${response.statusText}`);
|
|
180
|
+
}
|
|
181
|
+
const data = await response.json();
|
|
182
|
+
allResults.push(...data.results ?? []);
|
|
183
|
+
} finally {
|
|
184
|
+
clearTimeout(timer);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { results: allResults };
|
|
188
|
+
}
|
|
129
189
|
var BINARY = "trivy";
|
|
130
190
|
var PLATFORM_MAP = {
|
|
131
191
|
"darwin-arm64": "macOS-ARM64",
|
|
@@ -151,31 +211,29 @@ async function resolveBinaryPath(ctx) {
|
|
|
151
211
|
if (version) return codacyBin;
|
|
152
212
|
return BINARY;
|
|
153
213
|
}
|
|
154
|
-
function buildResult(startedAt, completedAt, issues, errors, filesAnalyzed) {
|
|
155
|
-
const durationMs = completedAt.getTime() - startedAt.getTime();
|
|
156
|
-
return {
|
|
157
|
-
metadata: {
|
|
158
|
-
startedAt: startedAt.toISOString(),
|
|
159
|
-
completedAt: completedAt.toISOString(),
|
|
160
|
-
durationMs
|
|
161
|
-
},
|
|
162
|
-
issues,
|
|
163
|
-
errors,
|
|
164
|
-
summary: {
|
|
165
|
-
toolId: TOOL_ID,
|
|
166
|
-
status: errors.some((e) => e.level === "error") ? "partial" : "success",
|
|
167
|
-
issueCount: issues.length,
|
|
168
|
-
errorCount: errors.length,
|
|
169
|
-
durationMs,
|
|
170
|
-
filesAnalyzed
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
214
|
async function ensureCacheDir(globalDir) {
|
|
175
215
|
const cacheDir = import_path.default.join(globalDir, "cache", "trivy");
|
|
176
216
|
await import_promises.default.mkdir(cacheDir, { recursive: true });
|
|
177
217
|
return cacheDir;
|
|
178
218
|
}
|
|
219
|
+
async function checkDbFreshness(cacheDir) {
|
|
220
|
+
const metadataPath = import_path.default.join(cacheDir, "db", "metadata.json");
|
|
221
|
+
try {
|
|
222
|
+
const raw = await import_promises.default.readFile(metadataPath, "utf-8");
|
|
223
|
+
const meta = JSON.parse(raw);
|
|
224
|
+
if (!meta.NextUpdate) {
|
|
225
|
+
return { fresh: false, error: "metadata.json missing NextUpdate field" };
|
|
226
|
+
}
|
|
227
|
+
const nextUpdateMs = Date.parse(meta.NextUpdate);
|
|
228
|
+
if (Number.isNaN(nextUpdateMs)) {
|
|
229
|
+
return { fresh: false, error: `Invalid NextUpdate timestamp: ${meta.NextUpdate}` };
|
|
230
|
+
}
|
|
231
|
+
return Date.now() < nextUpdateMs ? { fresh: true, nextUpdate: meta.NextUpdate } : { fresh: false, nextUpdate: meta.NextUpdate };
|
|
232
|
+
} catch (err) {
|
|
233
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
234
|
+
return { fresh: false, error: message };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
179
237
|
async function runTrivyScan(binary, args, cwd, errors) {
|
|
180
238
|
const { stdout, stderr, exitCode } = await (0, import_tooling.spawnTool)(binary, args, { cwd });
|
|
181
239
|
if (exitCode !== 0) {
|
|
@@ -213,7 +271,7 @@ async function parseVulnerabilities(output, enabledPatterns, targetFilesSet, rep
|
|
|
213
271
|
const relPath = result.Target;
|
|
214
272
|
if (targetFilesSet.size > 0 && !targetFilesSet.has(relPath)) continue;
|
|
215
273
|
if (!linesCache.has(relPath)) {
|
|
216
|
-
linesCache.set(relPath, await readFileLines(import_path.default.join(repositoryRoot, relPath)));
|
|
274
|
+
linesCache.set(relPath, await (0, import_tooling.readFileLines)(import_path.default.join(repositoryRoot, relPath)));
|
|
217
275
|
}
|
|
218
276
|
const lines = linesCache.get(relPath);
|
|
219
277
|
const pkgLineByID = /* @__PURE__ */ new Map();
|
|
@@ -242,7 +300,7 @@ async function parseVulnerabilities(output, enabledPatterns, targetFilesSet, rep
|
|
|
242
300
|
line,
|
|
243
301
|
column: 1,
|
|
244
302
|
message: `Insecure dependency ${vuln.PkgName}@${vuln.InstalledVersion} (${vuln.VulnerabilityID}${title}). ${fix}`,
|
|
245
|
-
lineContent: getLineContent(lines, line),
|
|
303
|
+
lineContent: (0, import_tooling.getLineContent)(lines, line),
|
|
246
304
|
category: meta.category,
|
|
247
305
|
severity: meta.severity
|
|
248
306
|
});
|
|
@@ -265,7 +323,7 @@ async function parseSecrets(output, enabledPatterns, targetFilesSet, repositoryR
|
|
|
265
323
|
const relPath = result.Target;
|
|
266
324
|
if (targetFilesSet.size > 0 && !targetFilesSet.has(relPath)) continue;
|
|
267
325
|
if (!linesCache.has(relPath)) {
|
|
268
|
-
linesCache.set(relPath, await readFileLines(import_path.default.join(repositoryRoot, relPath)));
|
|
326
|
+
linesCache.set(relPath, await (0, import_tooling.readFileLines)(import_path.default.join(repositoryRoot, relPath)));
|
|
269
327
|
}
|
|
270
328
|
const lines = linesCache.get(relPath);
|
|
271
329
|
for (const secret of result.Secrets) {
|
|
@@ -277,7 +335,7 @@ async function parseSecrets(output, enabledPatterns, targetFilesSet, repositoryR
|
|
|
277
335
|
column: 1,
|
|
278
336
|
endLine: secret.EndLine,
|
|
279
337
|
message: `Possible hardcoded secret: ${secret.Title} (${secret.Category})`,
|
|
280
|
-
lineContent: getLineContent(lines, secret.StartLine),
|
|
338
|
+
lineContent: (0, import_tooling.getLineContent)(lines, secret.StartLine),
|
|
281
339
|
multilineContent: (0, import_tooling.getMultilineContent)(lines, secret.StartLine, secret.EndLine),
|
|
282
340
|
category: meta.category,
|
|
283
341
|
severity: meta.severity
|
|
@@ -286,30 +344,107 @@ async function parseSecrets(output, enabledPatterns, targetFilesSet, repositoryR
|
|
|
286
344
|
}
|
|
287
345
|
return issues;
|
|
288
346
|
}
|
|
347
|
+
async function parseMaliciousPackages(output, enabledPatterns, targetFilesSet, repositoryRoot, logger) {
|
|
348
|
+
const issues = [];
|
|
349
|
+
const errors = [];
|
|
350
|
+
const patternId = "Trivy_malicious_packages";
|
|
351
|
+
if (enabledPatterns.size > 0 && !enabledPatterns.has(patternId)) {
|
|
352
|
+
return { issues, errors };
|
|
353
|
+
}
|
|
354
|
+
if (!output.Results) return { issues, errors };
|
|
355
|
+
const meta = PATTERN_META.get(patternId) ?? {
|
|
356
|
+
category: "Security",
|
|
357
|
+
severity: "Error"
|
|
358
|
+
};
|
|
359
|
+
const packages = [];
|
|
360
|
+
const seen = /* @__PURE__ */ new Set();
|
|
361
|
+
for (const result of output.Results) {
|
|
362
|
+
if (!result.Packages || !result.Type) continue;
|
|
363
|
+
const ecosystem = TRIVY_TYPE_TO_OSV_ECOSYSTEM[result.Type];
|
|
364
|
+
if (!ecosystem) continue;
|
|
365
|
+
const relPath = result.Target;
|
|
366
|
+
if (targetFilesSet.size > 0 && !targetFilesSet.has(relPath)) continue;
|
|
367
|
+
for (const pkg of result.Packages) {
|
|
368
|
+
const key = `${ecosystem}:${pkg.Name}:${pkg.Version}:${relPath}`;
|
|
369
|
+
if (seen.has(key)) continue;
|
|
370
|
+
seen.add(key);
|
|
371
|
+
const line = pkg.Locations && pkg.Locations.length > 0 && pkg.Locations[0].StartLine > 0 ? pkg.Locations[0].StartLine : 1;
|
|
372
|
+
packages.push({ ecosystem, name: pkg.Name, version: pkg.Version, filePath: relPath, line });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (packages.length === 0) return { issues, errors };
|
|
376
|
+
const queryMap = /* @__PURE__ */ new Map();
|
|
377
|
+
for (const pkg of packages) {
|
|
378
|
+
const queryKey = `${pkg.ecosystem}:${pkg.name}:${pkg.version}`;
|
|
379
|
+
if (!queryMap.has(queryKey)) {
|
|
380
|
+
queryMap.set(queryKey, {
|
|
381
|
+
package: { name: pkg.name, ecosystem: pkg.ecosystem },
|
|
382
|
+
version: pkg.version
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const queries = Array.from(queryMap.values());
|
|
387
|
+
let osvResponse;
|
|
388
|
+
try {
|
|
389
|
+
logger.debug("Querying OSV.dev for malicious packages", { packageCount: queries.length });
|
|
390
|
+
osvResponse = await queryOsvBatch(queries);
|
|
391
|
+
} catch (err) {
|
|
392
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
393
|
+
logger.warn(`Malicious package check skipped: ${message}`);
|
|
394
|
+
errors.push({
|
|
395
|
+
toolId: TOOL_ID,
|
|
396
|
+
phase: "toolInvoke",
|
|
397
|
+
kind: "NetworkError",
|
|
398
|
+
message: `OSV.dev API call failed: ${message}. Malicious package detection skipped.`,
|
|
399
|
+
level: "warning"
|
|
400
|
+
});
|
|
401
|
+
return { issues, errors };
|
|
402
|
+
}
|
|
403
|
+
const maliciousByPkg = /* @__PURE__ */ new Map();
|
|
404
|
+
const queryList = Array.from(queryMap.keys());
|
|
405
|
+
for (let i = 0; i < osvResponse.results.length; i++) {
|
|
406
|
+
const result = osvResponse.results[i];
|
|
407
|
+
if (!result.vulns) continue;
|
|
408
|
+
const malVulns = result.vulns.filter((v) => v.id.startsWith("MAL-"));
|
|
409
|
+
if (malVulns.length > 0 && i < queryList.length) {
|
|
410
|
+
maliciousByPkg.set(queryList[i], malVulns);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (maliciousByPkg.size === 0) {
|
|
414
|
+
logger.debug("No malicious packages detected");
|
|
415
|
+
return { issues, errors };
|
|
416
|
+
}
|
|
417
|
+
const linesCache = /* @__PURE__ */ new Map();
|
|
418
|
+
for (const pkg of packages) {
|
|
419
|
+
const queryKey = `${pkg.ecosystem}:${pkg.name}:${pkg.version}`;
|
|
420
|
+
const malVulns = maliciousByPkg.get(queryKey);
|
|
421
|
+
if (!malVulns) continue;
|
|
422
|
+
if (!linesCache.has(pkg.filePath)) {
|
|
423
|
+
linesCache.set(pkg.filePath, await (0, import_tooling.readFileLines)(import_path.default.join(repositoryRoot, pkg.filePath)));
|
|
424
|
+
}
|
|
425
|
+
const lines = linesCache.get(pkg.filePath);
|
|
426
|
+
for (const vuln of malVulns) {
|
|
427
|
+
const summary = vuln.summary ? ` ${vuln.summary}` : "";
|
|
428
|
+
issues.push({
|
|
429
|
+
filePath: pkg.filePath,
|
|
430
|
+
patternId,
|
|
431
|
+
toolId: TOOL_ID,
|
|
432
|
+
line: pkg.line,
|
|
433
|
+
column: 1,
|
|
434
|
+
message: `Malicious package detected: ${pkg.name}@${pkg.version} (${vuln.id}).${summary}`,
|
|
435
|
+
lineContent: (0, import_tooling.getLineContent)(lines, pkg.line),
|
|
436
|
+
category: meta.category,
|
|
437
|
+
severity: meta.severity
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
logger.info("Malicious package check complete", { issuesFound: issues.length });
|
|
442
|
+
return { issues, errors };
|
|
443
|
+
}
|
|
289
444
|
var adapter = {
|
|
290
|
-
|
|
291
|
-
displayName: "Trivy",
|
|
292
|
-
preferredVersion: "0.69.3",
|
|
445
|
+
...descriptor,
|
|
293
446
|
compatibleVersions: ">=0.50.0",
|
|
294
447
|
website: "https://trivy.dev/",
|
|
295
|
-
languages: [
|
|
296
|
-
"Python",
|
|
297
|
-
"JavaScript",
|
|
298
|
-
"TypeScript",
|
|
299
|
-
"Go",
|
|
300
|
-
"Java",
|
|
301
|
-
"Ruby",
|
|
302
|
-
"Rust",
|
|
303
|
-
"PHP",
|
|
304
|
-
"C#",
|
|
305
|
-
"Dart",
|
|
306
|
-
"Elixir",
|
|
307
|
-
"Swift"
|
|
308
|
-
],
|
|
309
|
-
filePatterns: ["*"],
|
|
310
|
-
supportedOS: ["linux", "darwin"],
|
|
311
|
-
supportedArch: ["x64", "arm64"],
|
|
312
|
-
supportedRunners: ["local", "container"],
|
|
313
448
|
prerequisites: [],
|
|
314
449
|
/**
|
|
315
450
|
* Checks whether the `trivy` binary is present and runnable.
|
|
@@ -446,13 +581,7 @@ var adapter = {
|
|
|
446
581
|
const issues = [];
|
|
447
582
|
const errors = [];
|
|
448
583
|
if (ctx.targetFiles.length === 0) {
|
|
449
|
-
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, 0);
|
|
450
|
-
}
|
|
451
|
-
if (!(codacyConfig.useLocalConfigurationFile && codacyConfig.localConfigurationFile) && codacyConfig.patterns.length === 0) {
|
|
452
|
-
ctx.logger.info("Skipping analysis: no local config and no patterns enabled", {
|
|
453
|
-
toolId: TOOL_ID
|
|
454
|
-
});
|
|
455
|
-
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
584
|
+
return (0, import_tooling.buildResult)(TOOL_ID, startedAt, /* @__PURE__ */ new Date(), issues, errors, 0);
|
|
456
585
|
}
|
|
457
586
|
const binary = await resolveBinaryPath(ctx);
|
|
458
587
|
let cacheDir;
|
|
@@ -466,14 +595,33 @@ var adapter = {
|
|
|
466
595
|
message: `Failed to create cache directory: ${err instanceof Error ? err.message : String(err)}`,
|
|
467
596
|
level: "error"
|
|
468
597
|
});
|
|
469
|
-
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
598
|
+
return (0, import_tooling.buildResult)(TOOL_ID, startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
470
599
|
}
|
|
471
600
|
const enabledPatterns = new Set(codacyConfig.patterns.map((p) => p.patternId));
|
|
472
601
|
const targetFilesSet = new Set(ctx.targetFiles);
|
|
473
602
|
const vulnPatternIds = Object.values(VULN_SEVERITY_TO_PATTERN);
|
|
474
|
-
const
|
|
603
|
+
const runMaliciousScan = enabledPatterns.size === 0 || enabledPatterns.has("Trivy_malicious_packages");
|
|
604
|
+
const runVulnScan = enabledPatterns.size === 0 || vulnPatternIds.some((id) => enabledPatterns.has(id)) || runMaliciousScan;
|
|
475
605
|
const runSecretScan = enabledPatterns.size === 0 || enabledPatterns.has("Trivy_secret");
|
|
476
606
|
const baseArgs = ["filesystem", "--format", "json", "--cache-dir", cacheDir];
|
|
607
|
+
if (runVulnScan) {
|
|
608
|
+
if (ctx.runner === "container") {
|
|
609
|
+
baseArgs.push("--skip-db-update", "--skip-java-db-update");
|
|
610
|
+
ctx.logger.debug("Container mode: skipping DB updates");
|
|
611
|
+
} else {
|
|
612
|
+
const dbStatus = await checkDbFreshness(cacheDir);
|
|
613
|
+
if (dbStatus.fresh) {
|
|
614
|
+
baseArgs.push("--skip-db-update", "--skip-java-db-update");
|
|
615
|
+
ctx.logger.debug("Trivy DB is fresh, skipping update check", {
|
|
616
|
+
nextUpdate: dbStatus.nextUpdate
|
|
617
|
+
});
|
|
618
|
+
} else {
|
|
619
|
+
ctx.logger.info(
|
|
620
|
+
dbStatus.error?.includes("ENOENT") ? "Trivy vulnerability database not found. Downloading (~40MB)..." : "Trivy DB update due, checking for new data..."
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
477
625
|
if (codacyConfig.useLocalConfigurationFile && codacyConfig.localConfigurationFile) {
|
|
478
626
|
baseArgs.push("--config", codacyConfig.localConfigurationFile);
|
|
479
627
|
}
|
|
@@ -511,6 +659,17 @@ var adapter = {
|
|
|
511
659
|
ctx.repositoryRoot
|
|
512
660
|
)
|
|
513
661
|
);
|
|
662
|
+
if (runMaliciousScan && ctx.runner !== "container") {
|
|
663
|
+
const malResult = await parseMaliciousPackages(
|
|
664
|
+
vulnOutput,
|
|
665
|
+
enabledPatterns,
|
|
666
|
+
targetFilesSet,
|
|
667
|
+
ctx.repositoryRoot,
|
|
668
|
+
ctx.logger
|
|
669
|
+
);
|
|
670
|
+
issues.push(...malResult.issues);
|
|
671
|
+
errors.push(...malResult.errors);
|
|
672
|
+
}
|
|
514
673
|
}
|
|
515
674
|
}
|
|
516
675
|
if (runSecretScan) {
|
|
@@ -536,7 +695,14 @@ var adapter = {
|
|
|
536
695
|
level: "error"
|
|
537
696
|
});
|
|
538
697
|
}
|
|
539
|
-
const result =
|
|
698
|
+
const result = (0, import_tooling.buildResult)(
|
|
699
|
+
TOOL_ID,
|
|
700
|
+
startedAt,
|
|
701
|
+
/* @__PURE__ */ new Date(),
|
|
702
|
+
issues,
|
|
703
|
+
errors,
|
|
704
|
+
ctx.targetFiles.length
|
|
705
|
+
);
|
|
540
706
|
ctx.logger.info("Analysis complete", {
|
|
541
707
|
toolId: TOOL_ID,
|
|
542
708
|
issueCount: result.summary.issueCount,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AdapterDescriptor } from '@codacy/tooling';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight adapter descriptor for lazy loading.
|
|
5
|
+
*
|
|
6
|
+
* This module contains only plain-data metadata fields and has zero heavy
|
|
7
|
+
* imports (no patterns.json, no runtime utilities). The CLI imports this
|
|
8
|
+
* at startup for adapter registration without loading the full adapter module.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
declare const descriptor: AdapterDescriptor;
|
|
12
|
+
|
|
13
|
+
export { descriptor };
|
package/dist/metadata.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/metadata.ts
|
|
21
|
+
var metadata_exports = {};
|
|
22
|
+
__export(metadata_exports, {
|
|
23
|
+
descriptor: () => descriptor
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(metadata_exports);
|
|
26
|
+
var descriptor = {
|
|
27
|
+
id: "Trivy",
|
|
28
|
+
displayName: "Trivy",
|
|
29
|
+
preferredVersion: "0.69.3",
|
|
30
|
+
languages: [
|
|
31
|
+
"Python",
|
|
32
|
+
"JavaScript",
|
|
33
|
+
"TypeScript",
|
|
34
|
+
"Go",
|
|
35
|
+
"Java",
|
|
36
|
+
"Ruby",
|
|
37
|
+
"Rust",
|
|
38
|
+
"PHP",
|
|
39
|
+
"C#",
|
|
40
|
+
"Dart",
|
|
41
|
+
"Elixir",
|
|
42
|
+
"Swift"
|
|
43
|
+
],
|
|
44
|
+
filePatterns: ["*"],
|
|
45
|
+
supportedOS: ["linux", "darwin"],
|
|
46
|
+
supportedArch: ["x64", "arm64"],
|
|
47
|
+
supportedRunners: ["local", "container"]
|
|
48
|
+
};
|
|
49
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
50
|
+
0 && (module.exports = {
|
|
51
|
+
descriptor
|
|
52
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codacy/tools-trivy-0",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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.5.0"
|
|
12
12
|
},
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"author": "Codacy <support@codacy.com> (https://www.codacy.com)",
|
|
@@ -21,8 +21,18 @@
|
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=20.0.0"
|
|
23
23
|
},
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./metadata": {
|
|
30
|
+
"types": "./dist/metadata.d.ts",
|
|
31
|
+
"default": "./dist/metadata.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
24
34
|
"scripts": {
|
|
25
|
-
"build": "tsup src/index.ts --format cjs --dts --clean",
|
|
35
|
+
"build": "tsup src/index.ts src/metadata.ts --format cjs --dts --clean",
|
|
26
36
|
"test": "vitest run",
|
|
27
37
|
"lint": "eslint src/",
|
|
28
38
|
"clean": "rm -rf dist",
|