66# See https://aboutcode.org for more information about AboutCode FOSS projects.
77#
88
9+ from django .apps import apps
10+
911from vulnerabilities .triage .models import TriageRecord
1012from vulnerabilities .triage .rules import RULE_REGISTRY
1113
@@ -37,10 +39,75 @@ def collect_matches(ruleset, product):
3739 return matched_rules_per_vulnerability_id
3840
3941
42+ def apply_preset_for_vulnerabilities (preset , product , vulnerability_ids ):
43+ """
44+ For each (product_package, vulnerability) pair in the product, create or update
45+ a VulnerabilityAnalysis using preset values.
46+
47+ Skips any analysis already modified by a human (applied_by_preset is null on an
48+ existing record). Only analyses that were auto-created (applied_by_preset is set)
49+ or brand-new are touched.
50+ """
51+ VulnerabilityAnalysis = apps .get_model ("vulnerabilities" , "VulnerabilityAnalysis" )
52+ ProductPackage = apps .get_model ("product_portfolio" , "ProductPackage" )
53+
54+ vulnerability_ids = list (vulnerability_ids )
55+
56+ # One query: exact (product_package_id, vulnerability_id) pairs to process.
57+ # Filtering by __id__in on the M2M restricts the JOIN rows to the matching
58+ # vulnerabilities, so values_list returns only the pairs we want.
59+ pp_vuln_pairs = set (
60+ ProductPackage .objects .filter (
61+ product = product ,
62+ package__affected_by_vulnerabilities__id__in = vulnerability_ids ,
63+ )
64+ .values_list ("id" , "package__affected_by_vulnerabilities__id" )
65+ .distinct ()
66+ )
67+
68+ if not pp_vuln_pairs :
69+ return
70+
71+ pp_ids = {pp_id for pp_id , _ in pp_vuln_pairs }
72+
73+ # One query: all existing analyses for this product_package / vulnerability set
74+ existing_analyses = {
75+ (analysis .product_package_id , analysis .vulnerability_id ): analysis
76+ for analysis in VulnerabilityAnalysis .objects .filter (
77+ product_package_id__in = pp_ids ,
78+ vulnerability_id__in = vulnerability_ids ,
79+ )
80+ }
81+
82+ # One query: product_package instances needed to construct new analyses
83+ product_packages_by_id = {pp .pk : pp for pp in ProductPackage .objects .filter (pk__in = pp_ids )}
84+
85+ for product_package_id , vulnerability_id in pp_vuln_pairs :
86+ existing = existing_analyses .get ((product_package_id , vulnerability_id ))
87+
88+ if existing is not None and existing .applied_by_preset_id is None :
89+ continue # Human-owned analysis -- never overwrite
90+
91+ if existing is None :
92+ product_package = product_packages_by_id [product_package_id ]
93+ analysis = VulnerabilityAnalysis (
94+ product_package = product_package ,
95+ vulnerability_id = vulnerability_id ,
96+ dataspace_id = product .dataspace_id ,
97+ )
98+ else :
99+ analysis = existing
100+
101+ preset .apply_to_analysis (analysis )
102+ analysis .applied_by_preset = preset
103+ analysis .save ()
104+
105+
40106def sync_triage_records (ruleset , product , matched_rules_per_vulnerability_id ):
41107 """
42108 Create or update one TriageRecord per matching vulnerability, then
43109 delete records for vulnerabilities that no longer match any rule in the ruleset.
110+ Applies the ruleset's analysis_preset when configured.
44111 """
45112 for vulnerability_id , matched_rules in matched_rules_per_vulnerability_id .items ():
46113 TriageRecord .objects .update_or_create (
@@ -59,6 +126,13 @@ def sync_triage_records(ruleset, product, matched_rules_per_vulnerability_id):
59126 product = product ,
60127 ).exclude (vulnerability_id__in = matched_rules_per_vulnerability_id .keys ()).delete ()
61128
129+ if ruleset .analysis_preset_id and matched_rules_per_vulnerability_id :
130+ apply_preset_for_vulnerabilities (
131+ preset = ruleset .analysis_preset ,
132+ product = product ,
133+ vulnerability_ids = list (matched_rules_per_vulnerability_id .keys ()),
134+ )
135+
62136
63137def evaluate_ruleset (ruleset , product ):
64138 """Evaluate a TriageRuleset against a product and persist the results."""
0 commit comments