Skip to content
Merged
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.11</trustify-da-api.version>
<trustify-da-api.version>2.0.12</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 @@ -545,8 +545,7 @@ private void incrementCounter(PackageItem item, VulnerabilityCounter counter, bo

private boolean hasUpstreamRemediation(Remediation r) {
return (r.getFixedIn() != null && !r.getFixedIn().isEmpty())
|| (r.getVersionRanges() != null && !r.getVersionRanges().isEmpty())
|| (r.getRemediations() != null && !r.getRemediations().isEmpty());
|| (r.getAdvisories() != null && !r.getAdvisories().isEmpty());
}

private boolean hasTrustedContentRemediation(Remediation r) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

import io.github.guacsec.trustifyda.api.PackageRef;
import io.github.guacsec.trustifyda.api.v5.AdvisoryInfo;
import io.github.guacsec.trustifyda.api.v5.AdvisoryRemediation;
import io.github.guacsec.trustifyda.api.v5.Issue;
import io.github.guacsec.trustifyda.api.v5.Remediation;
import io.github.guacsec.trustifyda.api.v5.RemediationCategory;
Expand All @@ -59,7 +60,7 @@
public class TrustifyResponseHandler extends ProviderResponseHandler {

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

@Inject ObjectMapper mapper;

Expand Down Expand Up @@ -141,6 +142,15 @@ private List<Issue> toIssues(JsonNode response) {
setCvssData(issue, vuln, purlStatus);
issuesByCveSource.put(key, issue);
});
issuesByCveSource
.values()
.forEach(
issue -> {
var remediation = issue.getRemediation();
if (remediation != null && remediation.getFixedIn() != null) {
remediation.getFixedIn().sort(VersionComparator.INSTANCE);
}
});
issues.addAll(issuesByCveSource.values());
});

Expand Down Expand Up @@ -181,11 +191,13 @@ private void setCvssData(Issue issue, JsonNode vuln, JsonNode purlStatus) {
issue.setSeverity(SeverityUtils.fromScore(sd.score));
}

var r = new Remediation();
boolean hasRemediation = processVersionRange(purlStatus, r, false);
hasRemediation |= processRemediations(purlStatus, r);

if (hasRemediation) {
var advRem = createAdvisoryRemediation(purlStatus);
if (advRem != null) {
var r = new Remediation();
r.addAdvisoriesItem(advRem);
if (advRem.getFixedIn() != null) {
r.addFixedInItem(advRem.getFixedIn());
}
issue.setRemediation(r);
}
}
Expand All @@ -207,9 +219,11 @@ private void mergeIssueData(Issue existing, JsonNode vuln, JsonNode purlStatus)
}
}

var r = ensureRemediation(existing);
processVersionRange(purlStatus, r, true);
processRemediations(purlStatus, r);
var advRem = createAdvisoryRemediation(purlStatus);
if (advRem != null) {
var r = ensureRemediation(existing);
mergeAdvisoryRemediation(r, advRem);
}
}

private record ScoreData(Float score, String severity) {}
Expand Down Expand Up @@ -240,11 +254,99 @@ private ScoreData extractScoreData(JsonNode vuln, JsonNode purlStatus) {
return new ScoreData(score, severity);
}

private boolean processVersionRange(JsonNode purlStatus, Remediation r, boolean dedup) {
private AdvisoryRemediation createAdvisoryRemediation(JsonNode purlStatus) {
var advisoryInfo = buildAdvisoryInfo(purlStatus);
var advRem = new AdvisoryRemediation();
advRem.setAdvisory(advisoryInfo);

var status = JsonUtils.getTextValue(purlStatus, "status");
if (status != null) {
advRem.setStatus(status);
}

var versionRange = purlStatus.get("version_range");
if (versionRange == null || versionRange.isNull()) {
return false;
if (versionRange != null && !versionRange.isNull()) {
advRem.addVersionRangesItem(buildVersionRange(versionRange));
var highVersion = JsonUtils.getTextValue(versionRange, "high_version");
var highInclusive = JsonUtils.getBooleanValue(versionRange, "high_inclusive");
if (highVersion != null && (highInclusive == null || !highInclusive)) {
advRem.setFixedIn(highVersion);
}
}

var remediations = (ArrayNode) purlStatus.get("remediations");
if (remediations != null && !remediations.isEmpty()) {
remediations.forEach(
rem -> {
var info = buildRemediationInfoFromNode(rem, advisoryInfo);
if (info != null) {
advRem.addRemediationsItem(info);
}
});
} else if (advisoryInfo != null) {
var info = new RemediationInfo();
if (advRem.getFixedIn() != null) {
info.category(RemediationCategory.VENDOR_FIX);
}
advRem.addRemediationsItem(info);
}

if (advRem.getVersionRanges() != null
|| advRem.getRemediations() != null
|| advRem.getAdvisory() != null) {
return advRem;
}
return null;
}

private void mergeAdvisoryRemediation(Remediation r, AdvisoryRemediation advRem) {
var existingAdvisories = r.getAdvisories();
if (existingAdvisories != null && advRem.getAdvisory() != null) {
var existingMatch =
existingAdvisories.stream()
.filter(
a ->
a.getAdvisory() != null
&& Objects.equals(a.getAdvisory().getId(), advRem.getAdvisory().getId()))
.findFirst();
if (existingMatch.isPresent()) {
var existing = existingMatch.get();
if (advRem.getVersionRanges() != null) {
for (var vr : advRem.getVersionRanges()) {
if (!isDuplicateVersionRange(existing, vr)) {
existing.addVersionRangesItem(vr);
}
}
}
if (advRem.getRemediations() != null) {
for (var rem : advRem.getRemediations()) {
if (!isDuplicateRemediationInfo(existing.getRemediations(), rem)) {
existing.addRemediationsItem(rem);
}
}
}
if (existing.getFixedIn() == null && advRem.getFixedIn() != null) {
existing.setFixedIn(advRem.getFixedIn());
}
if (advRem.getFixedIn() != null) {
var fixedIn = r.getFixedIn();
if (fixedIn == null || !fixedIn.contains(advRem.getFixedIn())) {
r.addFixedInItem(advRem.getFixedIn());
}
}
return;
}
}
r.addAdvisoriesItem(advRem);
if (advRem.getFixedIn() != null) {
var fixedIn = r.getFixedIn();
if (fixedIn == null || !fixedIn.contains(advRem.getFixedIn())) {
r.addFixedInItem(advRem.getFixedIn());
}
}
}

private VersionRange buildVersionRange(JsonNode versionRange) {
var vr = new VersionRange();
var schemeId = JsonUtils.getTextValue(versionRange, "version_scheme_id");
if (schemeId != null) {
Expand All @@ -259,75 +361,57 @@ private boolean processVersionRange(JsonNode purlStatus, Remediation r, boolean
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) {
if (dedup) {
var fixedIn = r.getFixedIn();
if (fixedIn == null || !fixedIn.contains(highVersion)) {
r.addFixedInItem(highVersion);
}
} else {
r.addFixedInItem(highVersion);
}
}
}
var highInclusive = JsonUtils.getBooleanValue(versionRange, "high_inclusive");
if (highInclusive != null) {
vr.highInclusive(highInclusive);
}
r.addVersionRangesItem(vr);
return true;
return vr;
}

private boolean processRemediations(JsonNode purlStatus, Remediation r) {
var remediations = (ArrayNode) purlStatus.get("remediations");
var advisoryInfo = buildAdvisoryInfo(purlStatus);
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) {
try {
info.url(URI.create(url));
} catch (IllegalArgumentException e) {
LOGGER.infof("Invalid remediation URL: %s", url);
}
}
if (advisoryInfo != null) {
info.advisory(advisoryInfo);
}
if (!isDuplicateRemediation(r, info)) {
r.addRemediationsItem(info);
}
});
return true;
} else {
var info =
buildRemediationInfo(purlStatus, r.getFixedIn() != null && !r.getFixedIn().isEmpty());
if (info != null && !isDuplicateRemediation(r, info)) {
r.addRemediationsItem(info);
return true;
private RemediationInfo buildRemediationInfoFromNode(JsonNode rem, AdvisoryInfo advisoryInfo) {
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) {
try {
info.url(URI.create(url));
} catch (IllegalArgumentException e) {
LOGGER.infof("Invalid remediation URL: %s", url);
}
}
return false;
return info;
}

private boolean isDuplicateVersionRange(AdvisoryRemediation advRem, VersionRange vr) {
var existing = advRem.getVersionRanges();
if (existing == null) {
return false;
}
return existing.stream()
.anyMatch(
e ->
Objects.equals(e.getLowVersion(), vr.getLowVersion())
&& Objects.equals(e.getHighVersion(), vr.getHighVersion())
&& Objects.equals(e.getLowInclusive(), vr.getLowInclusive())
&& Objects.equals(e.getHighInclusive(), vr.getHighInclusive())
&& Objects.equals(e.getVersionSchemeId(), vr.getVersionSchemeId()));
}

private boolean isDuplicateRemediation(Remediation r, RemediationInfo info) {
var existing = r.getRemediations();
private boolean isDuplicateRemediationInfo(List<RemediationInfo> existing, RemediationInfo info) {
if (existing == null) {
return false;
}
Expand All @@ -336,9 +420,7 @@ private boolean isDuplicateRemediation(Remediation r, RemediationInfo info) {
e ->
Objects.equals(e.getCategory(), info.getCategory())
&& Objects.equals(e.getDetails(), info.getDetails())
&& Objects.equals(
e.getAdvisory() != null ? e.getAdvisory().getId() : null,
info.getAdvisory() != null ? info.getAdvisory().getId() : null));
&& Objects.equals(e.getUrl(), info.getUrl()));
}

private Remediation ensureRemediation(Issue issue) {
Expand Down Expand Up @@ -371,18 +453,10 @@ private AdvisoryInfo buildAdvisoryInfo(JsonNode purlStatus) {
} catch (IllegalArgumentException e) {
LOGGER.infof("Invalid advisory URL: %s", identifier);
}
}
return info;
}

private RemediationInfo buildRemediationInfo(JsonNode purlStatus, boolean hasFixedVersions) {
var advisoryInfo = buildAdvisoryInfo(purlStatus);
if (advisoryInfo == null) {
return null;
}
var info = new RemediationInfo().advisory(advisoryInfo);
if (hasFixedVersions) {
info.category(RemediationCategory.VENDOR_FIX);
} else if (identifier != null && identifier.startsWith("GHSA-")) {
info.url(URI.create("https://github.com/advisories/" + identifier));
} else if (documentId.startsWith("GHSA-")) {
info.url(URI.create("https://github.com/advisories/" + documentId));
}
return info;
}
Expand All @@ -392,14 +466,20 @@ private String getSource(JsonNode purlStatus) {
if (advisory == null) {
return DEFAULT_SOURCE;
}
var issuer = advisory.get("issuer");
if (issuer == null || issuer.isNull()) {
return DEFAULT_SOURCE;
var labels = advisory.get("labels");
if (labels != null && !labels.isNull()) {
var importer = JsonUtils.getTextValue(labels, "importer");
if (importer != null && !importer.isBlank()) {
return importer;
}
}
var name = JsonUtils.getTextValue(issuer, "name");
if (name == null || name.isBlank()) {
return DEFAULT_SOURCE;
var issuer = advisory.get("issuer");
if (issuer != null && !issuer.isNull()) {
var name = JsonUtils.getTextValue(issuer, "name");
if (name != null && !name.isBlank()) {
return name;
}
}
return name;
return DEFAULT_SOURCE;
}
}
Loading
Loading