Vulnerability Scanning: CVE Database Lookups, SAST Pattern Matching, and Severity-Based Release Gating
Your CI pipeline runs three security scanners. Snyk finds a CRITICAL CVE in log4j-core 2.14.0. SonarQube finds a hardcoded password. Trivy finds 15 vulnerabilities in your node:latest container. Each tool generates its own report, its own severity scale, its own dashboard. Nobody consolidates them. This example builds a unified vulnerability scanning pipeline using Conductor that runs SAST code analysis, SCA dependency scanning against a real CVE database (Log4Shell, Spring4Shell, Jackson deserialization, Commons Text), and container image analysis -- then triages all findings into a single prioritized report that gates releases when critical vulnerabilities are present.
vs_scan_code --> vs_scan_dependencies --> vs_scan_containers --> vs_triage_findings
The scan workers operate independently on different inputs. The triage worker consolidates dependencyFindings and codeFindings into a unified risk assessment.
ScanCodeWorker scans source code strings against four vulnerability patterns using compiled java.util.regex.Pattern:
| Pattern Name | Regex | What It Catches |
|---|---|---|
SQL_INJECTION |
(?i)(execute|query).*\\+.*\\b(request|input|param) |
String concatenation in SQL queries |
HARDCODED_SECRET |
(?i)(password|secret|apikey|api_key)\\s*=\\s*"[^"]+" |
Credentials in source code |
INSECURE_RANDOM |
new\\s+Random\\(\\) |
java.util.Random instead of SecureRandom |
EVAL_INJECTION |
(?i)(eval|exec)\\s*\\( |
Dynamic code execution |
The SQL injection pattern specifically looks for string concatenation (+) between a query method and user input -- catching db.execute("SELECT * FROM users WHERE id=" + request.getParameter("id")) but not parameterized queries. Clean code like int x = 42; System.out.println(x); returns zero findings.
ScanDependenciesWorker checks dependencies against an embedded vulnerability database with real CVE entries:
| Artifact | CVE | CVSS | Severity | Fixed In |
|---|---|---|---|---|
log4j-core |
CVE-2021-44228 | 10.0 | CRITICAL | 2.17.0 |
spring-core |
CVE-2022-22965 | 9.8 | CRITICAL | 5.3.18 |
jackson-databind |
CVE-2020-36518 | 7.5 | HIGH | 2.13.2.1 |
commons-text |
CVE-2022-42889 | 9.8 | CRITICAL | 1.10.0 |
Version comparison uses a real semver-aware algorithm that splits on . and -, parsing each segment as an integer:
static int compareVersions(String v1, String v2) {
String[] p1 = v1.split("[.-]");
String[] p2 = v2.split("[.-]");
for (int i = 0; i < Math.max(p1.length, p2.length); i++) {
int a = 0, b = 0;
if (i < p1.length) try { a = Integer.parseInt(p1[i]); } catch (NumberFormatException ignored) {}
if (i < p2.length) try { b = Integer.parseInt(p2[i]); } catch (NumberFormatException ignored) {}
if (a != b) return a - b;
}
return 0;
}So log4j-core:2.14.0 is vulnerable (below 2.17.0), but log4j-core:2.21.0 is clean. Dependencies not in the database (like guava:33.0.0) return zero findings.
ScanContainersWorker evaluates container images based on base image characteristics:
| Image Type | Estimated Vulns | Recommendation |
|---|---|---|
:latest or no tag |
15 | "Pin to specific version" |
distroless |
0 | "No action needed" |
alpine |
3 | "No action needed" |
| Other | 8 | "No action needed" |
The usesLatestTag flag is set when the image ends with :latest or contains no : at all.
TriageFindingsWorker merges dependency and code findings, counts by severity, and makes a release decision:
boolean blockRelease = critical > 0 || high > 2;Any CRITICAL finding blocks the release. More than 2 HIGH findings also block. The worker validates CVSS scores are in the [0.0, 10.0] range and rejects findings with invalid scores.
An optional minimumSeverity filter (CRITICAL, HIGH, MEDIUM, LOW) returns only findings at or above the threshold. The severity ordering is CRITICAL > HIGH > MEDIUM > LOW -- setting minimumSeverity: "CRITICAL" filters out all HIGH/MEDIUM/LOW findings from filteredFindings while still counting them in totalFindings.
MainExampleTest (21 tests):
- Dependency scanning: known vulnerable version detected, updated version clean, clean deps return zero, empty list OK, missing/invalid dependencies type, missing artifact ID, missing version, CVSS score included and validated, CVSS validation boundaries (0.0, 5.5, 10.0 valid; -0.1, 10.1 invalid)
- Code scanning: SQL injection detected, clean code returns zero, missing code input
- Container scanning:
:latesttag detected (15 vulns), distroless is clean (0 vulns), missing image - Triage: missing findings, empty = LOW risk, CRITICAL blocks release, CRITICAL-only filter, invalid severity name, invalid CVSS rejected
VulnScanIntegrationTest (3 tests):
- Full pipeline with vulnerable deps (log4j + jackson) producing CRITICAL risk and release block
- Full pipeline with clean deps producing LOW risk and no block
- CRITICAL-only filter in pipeline: 2 total findings but only 1 passes CRITICAL filter
24 tests total covering the full scanning pipeline, CVE database lookups, and release gating logic.
How to run: See RUNNING.md | Production guidance: See PRODUCTION.md