Skip to content

Commit fe30379

Browse files
committed
fix: problog performs more accurate aggregation. Wrappers explanation improved.
Signed-off-by: Carl Flottmann <[email protected]>
1 parent 69c6f1c commit fe30379

File tree

1 file changed

+35
-23
lines changed

1 file changed

+35
-23
lines changed

src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,7 @@ def evaluate_heuristic_results(
145145
what rules were triggered.
146146
"""
147147
facts_list: list[str] = []
148-
triggered_rules = []
149-
# confidence is calculated using the probability of the package being benign, so the negation of the confidence values
150-
# in the problog model. Multiplying these probabilities together on several triggers will further decrease the probability
151-
# of the package being benign. This is then negated after calculation to get the probability of the package being malicious.
152-
# If no rules are triggered, this will simply result in 1.0 - 1.0 = 0.0.
153-
# For example, if a LOW rule and MEDIUM rule are triggered, with confidences 0.4 and 0.7 respectively, this would result in
154-
# the following calculation for confidence in package maliciousness:
155-
# 1 - (1.0 * (1 - 0.4) * (1 - 0.7)) = 0.82
156-
confidence: float = 1.0
148+
triggered_rules: dict[str, JsonType] = {}
157149

158150
for heuristic, result in heuristic_results.items():
159151
if result == HeuristicResult.PASS:
@@ -169,11 +161,11 @@ def evaluate_heuristic_results(
169161
problog_model = PrologString(problog_code)
170162
problog_results: dict[Term, float] = get_evaluatable().create_from(problog_model).evaluate()
171163

172-
for term, conf in problog_results.items():
173-
if conf is not None and conf > 0:
174-
confidence *= 1.0 - conf # decrease the probability of the package being benign
175-
triggered_rules.append(term.args[0])
176-
confidence = round(1.0 - confidence, 2) # 2 decimal places
164+
confidence = problog_results.pop(Term(self.problog_result_access), 0.0)
165+
if confidence > 0: # a rule was triggered
166+
for term, conf in problog_results.items():
167+
if term.args:
168+
triggered_rules[str(term.args[0])] = conf
177169

178170
return confidence, triggered_rules
179171

@@ -336,11 +328,21 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData:
336328
AnomalousVersionAnalyzer,
337329
]
338330

331+
problog_result_access = "result"
332+
339333
malware_rules_problog_model = f"""
340334
% ----- Wrappers ------
341-
% These should be used to logically check for a pass or fail on a heuristic for the rest of the model. They exist since,
342-
% when a heuristic is skipped, it is ommitted from being defined in the ProbLog model, and as such these try_call statements
343-
% are needed to handle referencing an undefined fact.
335+
% When a heuristic is skipped, it is ommitted from the problog model facts definition. This means that references in this
336+
% static model must account for when they are not existent. These wrappers perform this function using the inbuilt try_call
337+
% problog function. It will try to evaluate the provided logic, and return false if it encounters an error, such as the fact
338+
% not being defined. For example, you are expecting A to pass, so we do:
339+
%
340+
% passed(A)
341+
%
342+
% If A was 'true', then this will return true, as A did pass. If A was 'false', then this will return false, as A did not pass.
343+
% If A was not defined, then this will return false, as A did not pass.
344+
% Please use these wrappers throughout the problog model for logic definitions.
345+
344346
passed(H) :- try_call(H).
345347
failed(H) :- try_call(not H).
346348
@@ -358,14 +360,14 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData:
358360
% ----- Suspicious Combinations -----
359361
360362
% Package released recently with little detail, forcing the setup.py to run.
361-
{Confidence.HIGH.value}::result("malware_high_confidence_1") :-
363+
{Confidence.HIGH.value}::trigger(malware_high_confidence_1) :-
362364
quickUndetailed, forceSetup, failed({Heuristics.ONE_RELEASE.value}).
363-
{Confidence.HIGH.value}::result("malware_high_confidence_2") :-
365+
{Confidence.HIGH.value}::trigger(malware_high_confidence_2) :-
364366
quickUndetailed, forceSetup, failed({Heuristics.HIGH_RELEASE_FREQUENCY.value}).
365367
366368
% Package released recently with little detail, with some more refined trust markers introduced: project links,
367369
% multiple different releases, but there is no source code repository matching it and the setup is suspicious.
368-
{Confidence.HIGH.value}::result("malware_high_confidence_3") :-
370+
{Confidence.HIGH.value}::trigger(malware_high_confidence_3) :-
369371
failed({Heuristics.SOURCE_CODE_REPO.value}),
370372
failed({Heuristics.HIGH_RELEASE_FREQUENCY.value}),
371373
passed({Heuristics.UNCHANGED_RELEASE.value}),
@@ -374,21 +376,31 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData:
374376
375377
% Package released recently with little detail, with multiple releases as a trust marker, but frequent and with
376378
% the same code.
377-
{Confidence.MEDIUM.value}::result("malware_medium_confidence_1") :-
379+
{Confidence.MEDIUM.value}::trigger(malware_medium_confidence_1) :-
378380
quickUndetailed,
379381
failed({Heuristics.HIGH_RELEASE_FREQUENCY.value}),
380382
failed({Heuristics.UNCHANGED_RELEASE.value}),
381383
passed({Heuristics.SUSPICIOUS_SETUP.value}).
382384
383385
% Package released recently with little detail and an anomalous version number for a single-release package.
384-
{Confidence.MEDIUM.value}::result("malware_medium_confidence_2") :-
386+
{Confidence.MEDIUM.value}::trigger(malware_medium_confidence_2) :-
385387
quickUndetailed,
386388
failed({Heuristics.ONE_RELEASE.value}),
387389
passed({Heuristics.WHEEL_ABSENCE.value}),
388390
failed({Heuristics.ANOMALOUS_VERSION.value}).
389391
390392
% ----- Evaluation -----
391-
query(result(_)).
393+
394+
% Aggregate result
395+
{problog_result_access} :- trigger(malware_high_confidence_1).
396+
{problog_result_access} :- trigger(malware_high_confidence_2).
397+
{problog_result_access} :- trigger(malware_high_confidence_3).
398+
{problog_result_access} :- trigger(malware_medium_confidence_2).
399+
{problog_result_access} :- trigger(malware_medium_confidence_1).
400+
query({problog_result_access}).
401+
402+
% Explainability
403+
query(trigger(_)).
392404
"""
393405

394406

0 commit comments

Comments
 (0)