diff --git a/secator/runners/_base.py b/secator/runners/_base.py index 38454a0df..9c24f91e7 100644 --- a/secator/runners/_base.py +++ b/secator/runners/_base.py @@ -139,6 +139,8 @@ def __init__(self, config, inputs=[], results=[], run_opts={}, hooks={}, validat # Runner toggles self.enable_duplicate_check = self.run_opts.get('enable_duplicate_check', self.enable_duplicate_check) + if self.has_parent and 'enable_duplicate_check' not in self.run_opts: # disable duplicate check on children runners + self.enable_duplicate_check = False self.enable_profiles = self.run_opts.get('enable_profiles', True) self.enable_reports = self.run_opts.get('enable_reports', not self.sync) and not self.dry_run and not self.no_process and not self.no_poll # noqa: E501 self.enable_hooks = self.run_opts.get('enable_hooks', True) and not self.dry_run and not self.no_process # noqa: E501 diff --git a/tests/unit/test_runners.py b/tests/unit/test_runners.py index 314f8d134..16f6be2d3 100644 --- a/tests/unit/test_runners.py +++ b/tests/unit/test_runners.py @@ -918,3 +918,36 @@ def test_self_errors_includes_chunk_errors(self): cmd.unique_name = 'nmap' cmd.results.append(chunk_error) self.assertEqual(cmd.self_errors, [chunk_error]) + + +class TestDuplicateCheckGating(unittest.TestCase): + """Duplicate check must run only on the outermost runner. + + A nested runner (has_parent=True) — a task inside a workflow, or a workflow inside + a scan — must NOT run its own dedup pass; the outermost runner does it once over its + fully-aggregated results. See Runner.__init__ in secator/runners/_base.py. + """ + + def test_outermost_runner_dedups(self): + """An outermost runner (has_parent defaults to False) keeps the duplicate check on.""" + cmd = MyCommand(TARGETS) + self.assertFalse(cmd.has_parent) + self.assertTrue(cmd.enable_duplicate_check) + + def test_nested_runner_does_not_dedup(self): + """A nested runner (has_parent=True) has its duplicate check disabled by default.""" + cmd = MyCommand(TARGETS, has_parent=True) + self.assertTrue(cmd.has_parent) + self.assertFalse(cmd.enable_duplicate_check) + + def test_nested_runner_respects_explicit_enable_override(self): + """An explicit enable_duplicate_check=True override wins even when nested.""" + cmd = MyCommand(TARGETS, has_parent=True, enable_duplicate_check=True) + self.assertTrue(cmd.has_parent) + self.assertTrue(cmd.enable_duplicate_check) + + def test_outermost_runner_respects_explicit_disable_override(self): + """An explicit enable_duplicate_check=False override wins on an outermost runner.""" + cmd = MyCommand(TARGETS, enable_duplicate_check=False) + self.assertFalse(cmd.has_parent) + self.assertFalse(cmd.enable_duplicate_check)