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
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,45 @@
import static org.dependencytrack.policy.cel.compat.CelPolicyScriptSourceBuilder.escapeQuotes;

public class VulnerabilityIdCelPolicyScriptSourceBuilder implements CelPolicyScriptSourceBuilder {

@Override
public String apply(final PolicyCondition policyCondition) {
final String scriptSrc = """
vulns.exists(v, v.id == "%s")
""".formatted(escapeQuotes(policyCondition.getValue()));
final String value = policyCondition.getValue();

if (policyCondition.getOperator() == PolicyCondition.Operator.IS) {
return scriptSrc;
} else if (policyCondition.getOperator() == PolicyCondition.Operator.IS_NOT) {
return "!" + scriptSrc;
}
return switch (policyCondition.getOperator()) {
case IS -> """
vulns.exists(v, v.id == "%s")
""".formatted(escapeQuotes(value));
case IS_NOT -> """
!vulns.exists(v, v.id == "%s")
""".formatted(escapeQuotes(value));
case MATCHES -> """
vulns.exists(v, v.id.matches("%s"))
""".formatted(escapeQuotes(toRegex(value)));
case NO_MATCH -> """
!vulns.exists(v, v.id.matches("%s"))
""".formatted(escapeQuotes(toRegex(value)));
default -> null;
};
}

return null;
/**
* Normalizes a condition value into a regular expression, mirroring the behavior of
* {@code org.dependencytrack.policy.Matcher} in Dependency-Track v4. A bare {@code *}
* is treated as a wildcard ({@code .*}), and the pattern is anchored with {@code .*} on
* either end when it is not already anchored, so the match behaves as a substring match.
* This preserves the semantics the {@code MATCHES} / {@code NO_MATCH} operators had for
* vulnerability-id conditions in v4.
*/
static String toRegex(final String conditionString) {
String regex = conditionString.replace("*", ".*").replace("..*", ".*");
if (!regex.startsWith("^") && !regex.startsWith(".*")) {
regex = ".*" + regex;
}
if (!regex.endsWith("$") && !regex.endsWith(".*")) {
regex = regex + ".*";
}
return regex;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,19 @@ private static Object[] parameters() {
// IS_NOT with exact match
new Object[]{PolicyCondition.Operator.IS_NOT, "CVE-123", "CVE-123", false},
// IS with quotes
new Object[]{PolicyCondition.Operator.IS, "\"CVE-123", "\"CVE-123", true}
new Object[]{PolicyCondition.Operator.IS, "\"CVE-123", "\"CVE-123", true},
// MATCHES with wildcard catches malware advisories (MAL-*)
new Object[]{PolicyCondition.Operator.MATCHES, "MAL-*", "MAL-2026-3463", true},
// MATCHES wildcard with no match
new Object[]{PolicyCondition.Operator.MATCHES, "MAL-*", "CVE-2026-1234", false},
// MATCHES with a plain regex (CVE-2024-*)
new Object[]{PolicyCondition.Operator.MATCHES, "CVE-2024-*", "CVE-2024-9999", true},
// MATCHES with a regular expression using alternation and char classes
new Object[]{PolicyCondition.Operator.MATCHES, "^CVE-202[34]-", "CVE-2023-1", true},
// NO_MATCH excludes allowlisted advisories via regex alternation
new Object[]{PolicyCondition.Operator.NO_MATCH, "(MAL-2026-(3463|3432)|MAL-2025-(5870|5869))", "MAL-2026-9999", true},
// NO_MATCH: allowlisted advisory produces no violation
new Object[]{PolicyCondition.Operator.NO_MATCH, "(MAL-2026-(3463|3432)|MAL-2025-(5870|5869))", "MAL-2026-3463", false}
};
}

Expand Down