Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
<deploy-plugin.version>3.1.1</deploy-plugin.version>

<!-- Dependencies -->
<trustify-da-api.version>2.0.9</trustify-da-api.version>
<trustify-da-api.version>2.0.10</trustify-da-api.version>
<cyclonedx.version>11.0.1</cyclonedx.version>
<sentry.version>7.8.0</sentry.version>
<spdx.version>2.0.2</spdx.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ private Constants() {}
public static final String CACHE_LICENSES_PROPERTY = "cacheLicenses";
public static final String CACHE_LICENSES_HITS_PROPERTY = "cacheLicensesHits";

public static final String TRUSTIFY_RECOMMEND_PATH = "/api/v2/purl/recommend";
public static final String TRUSTIFY_ANALYZE_PATH = "/api/v2/vulnerability/analyze";
public static final String TRUSTIFY_RECOMMEND_PATH = "/api/v3/purl/recommend";
public static final String TRUSTIFY_ANALYZE_PATH = "/api/v3/vulnerability/analyze";
public static final String TRUSTIFY_HEALTH_PATH = "/.well-known/trustify";

public static final String DEPS_DEV_LICENSES_PATH = "/v3alpha/purlbatch";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import io.github.guacsec.trustifyda.api.v5.RecommendationReport;
import io.github.guacsec.trustifyda.api.v5.RecommendationSource;
import io.github.guacsec.trustifyda.api.v5.RecommendationSummary;
import io.github.guacsec.trustifyda.api.v5.Remediation;
import io.github.guacsec.trustifyda.api.v5.Source;
import io.github.guacsec.trustifyda.api.v5.SourceSummary;
import io.github.guacsec.trustifyda.api.v5.TransitiveDependencyReport;
Expand Down Expand Up @@ -535,15 +536,23 @@ private void incrementCounter(PackageItem item, VulnerabilityCounter counter, bo
counter.direct.addAndGet(vulnerabilities);
}
if (i.getRemediation() != null
&& i.getRemediation().getTrustedContent() != null
&& i.getRemediation().getTrustedContent().getRef() != null) {
&& (hasUpstreamRemediation(i.getRemediation())
|| hasTrustedContentRemediation(i.getRemediation()))) {
counter.remediations.incrementAndGet();
}
});
}

// The number of vulnerabilities is the total count of public CVEs
// or if it is private it will be 1
private boolean hasUpstreamRemediation(Remediation r) {
return (r.getFixedIn() != null && !r.getFixedIn().isEmpty())
|| (r.getVersionRanges() != null && !r.getVersionRanges().isEmpty())
|| (r.getRemediations() != null && !r.getRemediations().isEmpty());
}

private boolean hasTrustedContentRemediation(Remediation r) {
return r.getTrustedContent() != null && r.getTrustedContent().getRef() != null;
}

private int countVulnerabilities(Issue i) {
if (i.getCves() != null && !i.getCves().isEmpty()) {
return i.getCves().size();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,12 @@ private void setTrustedContent(
.status(Vulnerability.Status.toString(vuln.getStatus()))
.justification(
Vulnerability.Justification.toString(vuln.getJustification()));
issue.remediation(new Remediation().trustedContent(remediation));
var existing = issue.getRemediation();
if (existing == null) {
existing = new Remediation();
}
existing.trustedContent(remediation);
issue.remediation(existing);
});
}
providerResponse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -37,15 +36,16 @@
import io.github.guacsec.trustifyda.api.PackageRef;
import io.github.guacsec.trustifyda.api.v5.Issue;
import io.github.guacsec.trustifyda.api.v5.Remediation;
import io.github.guacsec.trustifyda.api.v5.RemediationCategory;
import io.github.guacsec.trustifyda.api.v5.RemediationInfo;
import io.github.guacsec.trustifyda.api.v5.SeverityUtils;
import io.github.guacsec.trustifyda.api.v5.VersionRange;
import io.github.guacsec.trustifyda.integration.Constants;
import io.github.guacsec.trustifyda.integration.backend.JsonUtils;
import io.github.guacsec.trustifyda.integration.providers.ProviderResponseHandler;
import io.github.guacsec.trustifyda.model.DependencyTree;
import io.github.guacsec.trustifyda.model.PackageItem;
import io.github.guacsec.trustifyda.model.ProviderResponse;
import io.github.guacsec.trustifyda.model.trustify.AdvisoryScore;
import io.github.guacsec.trustifyda.model.trustify.ScoreType;
Comment on lines -47 to -48

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AdvisoryScore.java and ScoreType.java are no longer imported or used anywhere in the codebase after this PR, delete both files

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — both files deleted in afb0649.

import io.quarkus.runtime.annotations.RegisterForReflection;

import jakarta.enterprise.context.ApplicationScoped;
Expand All @@ -56,14 +56,7 @@
public class TrustifyResponseHandler extends ProviderResponseHandler {

private static final Logger LOGGER = Logger.getLogger(TrustifyResponseHandler.class);
private static final String DEFAULT_SOURCE = "manual";

private static final Map<ScoreType, Integer> SCORE_TYPE_ORDER =
Map.of(
ScoreType.V4, 1,
ScoreType.V3_1, 2,
ScoreType.V3_0, 3,
ScoreType.V2, 4);
private static final String DEFAULT_SOURCE = "unknown";

@Inject ObjectMapper mapper;

Expand Down Expand Up @@ -112,26 +105,27 @@ private List<Issue> toIssues(JsonNode response) {
return;
}

var status = (ObjectNode) vuln.get("status");
if (status == null || !status.hasNonNull("affected")) {
var purlStatuses = (ArrayNode) vuln.get("purl_statuses");
if (purlStatuses == null || purlStatuses.isEmpty()) {
return;
}
var affected = (ArrayNode) status.get("affected");
var title = JsonUtils.getTextValue(vuln, "title");
final String iTitle;
if (title == null) {
iTitle = JsonUtils.getTextValue(vuln, "description");
} else {
iTitle = title;
}

Map<String, Issue> issuesByCveSource = new HashMap<>();

affected.forEach(
data -> {
if (data.hasNonNull("withdrawn")) {
purlStatuses.forEach(
purlStatus -> {
var advisory = purlStatus.get("advisory");
if (advisory != null && advisory.hasNonNull("withdrawn")) {
return;
}
var source = getSource(data);
var source = getSource(purlStatus);
if (source == null) {
return;
}
Expand All @@ -140,7 +134,7 @@ private List<Issue> toIssues(JsonNode response) {
return;
}
var issue = new Issue().id(id).title(iTitle).source(source).cves(List.of(id));
setCvssData(issue, data);
setCvssData(issue, vuln, purlStatus);
issuesByCveSource.put(key, issue);
});
issues.addAll(issuesByCveSource.values());
Expand All @@ -163,69 +157,122 @@ private List<String> getWarnings(JsonNode entryValue) {
return warnings;
}

private void setCvssData(Issue issue, JsonNode node) {
var scores = (ArrayNode) node.get("scores");

if (scores != null && !scores.isEmpty()) {
var advisoryScores = getAdvisoryScore(issue.getId(), scores);
if (!advisoryScores.isEmpty()) {
var score = advisoryScores.get(0);
issue.cvssScore(score.score().floatValue());
if (score.severity() != null) {
issue.setSeverity(score.severity());
} else {
issue.setSeverity(SeverityUtils.fromScore(score.score().floatValue()));
private void setCvssData(Issue issue, JsonNode vuln, JsonNode purlStatus) {
Float score = null;
String severity = null;

var baseScore = vuln.get("base_score");
if (baseScore != null && !baseScore.isNull()) {
score = baseScore.has("score") ? (float) baseScore.get("score").asDouble() : null;
severity = JsonUtils.getTextValue(baseScore, "severity");
}

if (score == null) {
var scores = purlStatus.get("scores");
if (scores != null && scores.isArray() && !scores.isEmpty()) {
for (var entry : scores) {
var entryValue = entry.has("value") ? (float) entry.get("value").asDouble() : null;
if (entryValue != null && (score == null || entryValue > score)) {
score = entryValue;
severity = JsonUtils.getTextValue(entry, "severity");
}
}
}
}
var ranges = (ArrayNode) node.get("ranges");
if (ranges == null) {
return;

if (score != null) {
issue.cvssScore(score);
}
if (severity != null) {
try {
issue.setSeverity(SeverityUtils.fromValue(severity.toUpperCase()));
} catch (IllegalArgumentException e) {
LOGGER.infof("Unknown severity value: %s, falling back to score-based severity", severity);
if (score != null) {
issue.setSeverity(SeverityUtils.fromScore(score));
}
}
} else if (score != null) {
issue.setSeverity(SeverityUtils.fromScore(score));
}

var r = new Remediation();
ranges.forEach(
rangeNode -> {
var events = (ArrayNode) rangeNode.get("events");
events.forEach(
eventNode -> {
var fixed = JsonUtils.getTextValue(eventNode, "fixed");
if (fixed != null) {
r.addFixedInItem(fixed);
}
});
});
issue.setRemediation(r);
}
boolean hasRemediation = false;

private List<AdvisoryScore> getAdvisoryScore(String cveId, ArrayNode scores) {
var result = new ArrayList<AdvisoryScore>();
scores.forEach(
score -> {
try {
var scoreType = ScoreType.fromValue(JsonUtils.getTextValue(score, "type"));
var severity = JsonUtils.getTextValue(score, "severity");
var scoreValue = score.get("value").asDouble();
result.add(
new AdvisoryScore(
scoreType, SeverityUtils.fromValue(severity.toUpperCase()), scoreValue));
} catch (IllegalArgumentException e) {
LOGGER.infof(
"Unable to parse advisory score: %s for CVE %s: %s",
score.toString(), cveId, e.getMessage());
}
});
var versionRange = purlStatus.get("version_range");
if (versionRange != null && !versionRange.isNull()) {
var vr = new VersionRange();
var schemeId = JsonUtils.getTextValue(versionRange, "version_scheme_id");
if (schemeId != null) {
vr.versionSchemeId(schemeId);
}
var lowVersion = JsonUtils.getTextValue(versionRange, "low_version");
if (lowVersion != null) {
vr.lowVersion(lowVersion);
}
var lowInclusive = JsonUtils.getBooleanValue(versionRange, "low_inclusive");
if (lowInclusive != null) {
vr.lowInclusive(lowInclusive);
}
var highVersion = JsonUtils.getTextValue(versionRange, "high_version");
var highInclusive = JsonUtils.getBooleanValue(versionRange, "high_inclusive");
if (highVersion != null) {
vr.highVersion(highVersion);
if (highInclusive == null || !highInclusive) {
r.addFixedInItem(highVersion);
}
}
if (highInclusive != null) {
vr.highInclusive(highInclusive);
}
r.addVersionRangesItem(vr);
hasRemediation = true;
}

result.sort(
Comparator.comparing(advisoryScore -> SCORE_TYPE_ORDER.get(advisoryScore.scoreType())));
return result;
var remediations = (ArrayNode) purlStatus.get("remediations");
if (remediations != null && !remediations.isEmpty()) {
remediations.forEach(
rem -> {
var info = new RemediationInfo();
var category = JsonUtils.getTextValue(rem, "category");
if (category != null) {
try {
info.category(RemediationCategory.fromValue(category.toUpperCase()));
} catch (IllegalArgumentException e) {
LOGGER.infof("Unknown remediation category: %s", category);
}
}
var details = JsonUtils.getTextValue(rem, "details");
if (details != null) {
info.details(details);
}
var url = JsonUtils.getTextValue(rem, "url");
if (url != null) {
info.url(url);
}
r.addRemediationsItem(info);
});
hasRemediation = true;
}

if (hasRemediation) {
issue.setRemediation(r);
}
}

private String getSource(JsonNode node) {
var labels = node.get("labels");
var importer = JsonUtils.getTextValue(labels, "importer");
if (importer == null || importer.isBlank()) {
private String getSource(JsonNode purlStatus) {
var advisory = purlStatus.get("advisory");
if (advisory == null) {
return DEFAULT_SOURCE;
}
var issuer = advisory.get("issuer");
if (issuer == null || issuer.isNull()) {
return DEFAULT_SOURCE;
}
var name = JsonUtils.getTextValue(issuer, "name");
if (name == null || name.isBlank()) {
return DEFAULT_SOURCE;
}
return importer;
return name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,14 @@ private Set<String> enrichExistingDependencies(
depReport
.getIssues()
.forEach(
issue -> issue.remediation(new Remediation().trustedContent(trustedContent)));
issue -> {
var existing = issue.getRemediation();
if (existing == null) {
existing = new Remediation();
}
existing.trustedContent(trustedContent);
issue.remediation(existing);
});
}

// Backward compat: set deprecated per-dependency recommendation
Expand Down

This file was deleted.

Loading
Loading