Skip to content

Conversation

@SachinAditya
Copy link
Contributor

@SachinAditya SachinAditya commented Jan 19, 2026

Fixes #8197

Summary

Improve unused suppression detection by tracking individual CVE and vulnerabilityName (GHSA) entries inside a suppression rule.

Previously, only whole suppression blocks were checked for usage. This change allows detecting unused sub-entries so they can be safely removed when failBuildOnUnusedSuppressionRule is enabled.

Changes

  • Track used CVE entries in SuppressionRule
  • Track used vulnerabilityName entries in SuppressionRule
  • Report rules with unused sub-entries in UnusedSuppressionRuleAnalyzer

Motivation

When dependencies are upgraded, some suppressed vulnerabilities no longer apply. Currently these remain hidden because the suppression block is still considered "used". This change makes suppression maintenance more accurate and easier.

Testing

  • Existing CI tests
  • Manual verification with multi-version dependency scenario

@boring-cyborg boring-cyborg bot added the core changes to core label Jan 19, 2026
Added methods to track used CVEs and vulnerability names, and check for unused sub-entries.
@SachinAditya
Copy link
Contributor Author

Hi

I’ve submitted this PR to improve detection of unused suppression rule sub-entries (CVE and vulnerability name tracking).

Whenever you have time, I’d really appreciate your review or any feedback to help improve the implementation.

Thank you for your guidance and support! 🙏

@chadlwilson
Copy link
Collaborator

Please properly fill in the PR description to accurately summarise what you have changed, why, link the issue you are trying to resolve etc, and address the lint/build/test failures.

@SachinAditya
Copy link
Contributor Author

@chadlwilson Thanks for the feedback!

I've updated the PR description with a detailed summary and linked issue #8197.
Please let me know if anything else is needed.

@SachinAditya SachinAditya changed the title Feature/unused suppression subentry feat: detect unused suppression sub-entries ✅ Jan 20, 2026
@chadlwilson chadlwilson self-requested a review January 20, 2026 11:49
@boring-cyborg boring-cyborg bot added the tests test cases label Jan 20, 2026
@SachinAditya
Copy link
Contributor Author

Hi @chadlwilson, I’ve fixed the failing test expectations and now all checks are passing. Thank you for reviewing!

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR enhances the unused suppression detection feature by tracking individual CVE and vulnerabilityName entries within suppression rules, rather than treating entire suppression blocks as atomic units. This allows more precise identification of unused suppression entries when dependencies are upgraded and some vulnerabilities no longer apply.

Changes:

  • Added tracking of used CVE and vulnerabilityName sub-entries in SuppressionRule using HashSet collections
  • Implemented methods to mark sub-entries as used during vulnerability suppression processing
  • Updated UnusedSuppressionRuleAnalyzer to detect and report rules with unused sub-entries
  • Modified tests to reflect the new behavior where rules with unused sub-entries are flagged

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 11 comments.

File Description
core/src/main/java/org/owasp/dependencycheck/xml/suppression/SuppressionRule.java Added fields and methods to track which CVE and vulnerabilityName entries are actually used during suppression processing
core/src/main/java/org/owasp/dependencycheck/analyzer/UnusedSuppressionRuleAnalyzer.java Updated detection logic to flag rules with unused sub-entries and improved error messaging
core/src/test/java/org/owasp/dependencycheck/analyzer/UnusedSuppressionRuleAnalyzerTest.java Fixed formatting, corrected comments, and updated test expectations to verify unused sub-entry detection

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (!remove && v.getName() != null) {
for (PropertyType entry : this.vulnerabilityNames) {
if (entry.matches(v.getName())) {
markVulnerabilityNameUsed(entry.getValue());
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The markVulnerabilityNameUsed call has excessive indentation (13 spaces). It should be consistently indented to match the surrounding code at the same nesting level.

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +93
final String message = String.format(
"Suppression Rule had unused entries (or zero matches): %s", rule);
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The message formatting is split across multiple lines with unusual indentation. The second part of the String.format call starts at column 9 instead of being properly aligned. Consider keeping the entire format string on one line or using proper indentation that aligns with the opening parenthesis.

Copilot uses AI. Check for mistakes.
@jeremylong
Copy link
Collaborator

@copilot open a new pull request to apply changes based on the comments in this thread

@SachinAditya
Copy link
Contributor Author

Add tracking for used CVE and vulnerability-name suppression entries

Track which CVE and vulnerability-name sub-entries are actually matched during suppression
processing and expose a helper to detect unused entries.

This helps identify outdated or ineffective suppression rules and improves suppression file hygiene.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

for (Vulnerability v : dependency.getVulnerabilities()) {
boolean remove = false;
for (String entry : this.cve) {
for (String entry : this.cve) {
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

Inconsistent indentation: This line has an extra leading space. It should align with the surrounding code at the same indentation level (16 spaces, not 17).

Suggested change
for (String entry : this.cve) {
for (String entry : this.cve) {

Copilot uses AI. Check for mistakes.
Comment on lines +126 to +131
private final Set<String> usedCves = new HashSet<>();

/**
* Track used vulnerability-name sub-entries.
*/
private final Set<String> usedVulnerabilityNames = new HashSet<>();
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

Thread safety issue: These HashSet fields are being modified without synchronization in a class marked @NotThreadSafe, but the class is accessed by analyzers marked @threadsafe that support parallel processing. Multiple threads can call the process() method concurrently, leading to potential race conditions when adding to usedCves and usedVulnerabilityNames sets.

Consider using thread-safe collections like ConcurrentHashMap.newKeySet() instead of HashSet, or add proper synchronization to the markCveUsed() and markVulnerabilityNameUsed() methods.

Copilot uses AI. Check for mistakes.
Comment on lines +163 to +182
public boolean hasUnusedSubEntries() {
if (!matched) {
return false;
}

for (String c : this.cve) {
if (!usedCves.contains(c)) {
return true;
}
}

for (PropertyType pt : this.vulnerabilityNames) {
String name = pt.getValue();
if (!usedVulnerabilityNames.contains(name)) {
return true;
}
}

return false;
}
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

Incomplete tracking of suppression matches: The hasUnusedSubEntries() method only checks CVE and vulnerabilityName entries, but suppressions can also match based on CWE entries (lines 681-690) and CVSS score thresholds (lines 701-706). When these other suppression types match, they set matched=true but don't track which specific CVE/vulnerabilityName entries were used.

This means if a rule has CVE entries AND a cvssBelow threshold, and only the cvssBelow matches, the CVE entries will incorrectly appear as unused. Consider tracking all suppression types or adjusting the logic to only check for unused sub-entries when the rule uses CVE or vulnerabilityName matching.

Copilot uses AI. Check for mistakes.
if (!remove && v.getName() != null) {
for (PropertyType entry : this.vulnerabilityNames) {
if (entry.matches(v.getName())) {
markVulnerabilityNameUsed(entry.getValue());
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

Inconsistent indentation: This line has extra leading whitespace compared to the surrounding code. The line should align with the 'for' statement above it at line 673.

Copilot uses AI. Check for mistakes.
Comment on lines +91 to +93
if ((!rule.isMatched() || rule.hasUnusedSubEntries()) && !rule.isBase()) {
final String message = String.format(
"Suppression Rule had unused entries (or zero matches): %s", rule);
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

The error message should be more specific and helpful. The current message "Suppression Rule had unused entries (or zero matches)" is ambiguous. When hasUnusedSubEntries() returns true, it means that some CVE or vulnerabilityName entries within a matched rule are unused - not that there are zero matches. Consider separate messages for the two cases: one for completely unused rules (!rule.isMatched()) and another for rules with unused sub-entries (rule.hasUnusedSubEntries()), to make it clearer what action the user should take.

Copilot uses AI. Check for mistakes.
Comment on lines +132 to 136
/**
* Returns whether this suppression rule matched any dependency.
*
* @return the value of matched
* @return true if the rule matched at least one dependency
*/
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

Inconsistent indentation: This JavaDoc comment has extra leading whitespace (8 spaces instead of 4). It should align with the other method JavaDocs in this class.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

@chadlwilson chadlwilson left a comment

Choose a reason for hiding this comment

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

In my opinion the whole approach here needs reworking.

It has a number of problems in both design and functionality; aside from style.

  • no tests
  • mutates collections in threaded code so not threadsafe (as copilot notes)
  • doesn't work correctly if a vulnerability is suppressed by its CPE (regex or prefix)
  • doesn't work correctly if a vulnerability is suppressed by its CWE
  • it doesn't tell you specifically which sub-parts of the rule are unmatched making this useless to use in practice
  • unnecessarily retains and logs via the earlier crude matched status, when that should be unnecessary when tracking the entries deliberately
  • vulnerabilityName tracking is buggy/incomplete because it tracks based on the text of the rule, ignoring the PropertyType whether it is a regex or not.
  • marks and tracks matches against base suppression entries unnecessarily which is confusing for debugging (and wasteful on memory usage)

IMO this really needs some more design thought and a more holistic approach for tracking these which might need some refactoring of the core process(Dependency) method to make it easier to understand - otherwise bugs are likely to be introduced later when/if different suppression styles are supported.

@SachinAditya
Copy link
Contributor Author

Thanks for the detailed feedback — I agree the current approach needs redesign (thread safety, CPE/CWE handling, PropertyType usage, and better reporting).

I’ll rework this with a new design and proper tests rather than patching the current PR.

Appreciate the guidance.

@SachinAditya SachinAditya deleted the feature/unused-suppression-subentry branch January 27, 2026 10:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core changes to core tests test cases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ability to look for unused suppressions more precisely

4 participants