diff --git a/desloppify/languages/python/_security.py b/desloppify/languages/python/_security.py
index 9b623657..eed92009 100644
--- a/desloppify/languages/python/_security.py
+++ b/desloppify/languages/python/_security.py
@@ -5,7 +5,7 @@
import shutil
from desloppify.base.config import load_config
-from desloppify.base.discovery.source import collect_exclude_dirs
+from desloppify.base.discovery.source import collect_exclude_dirs, get_exclusions
from desloppify.languages._framework.base.types import DetectorCoverageStatus, LangSecurityResult
from desloppify.languages.python.detectors.bandit_adapter import detect_with_bandit
from desloppify.languages.python._helpers import scan_root_from_files
@@ -49,7 +49,14 @@ def detect_python_security(files, zone_map) -> LangSecurityResult:
if scan_root is None:
return LangSecurityResult(entries=[], files_scanned=0)
- exclude_dirs = collect_exclude_dirs(scan_root)
+ # Config-level excludes (`desloppify exclude
`) are stored in the project
+ # config, not the runtime context that get_exclusions() reads here. Without
+ # loading them explicitly, bandit rescans excluded directories even though every
+ # other detector honours the exclude.
+ config_excludes = tuple(p for p in (load_config().get("exclude") or ()) if isinstance(p, str))
+ exclude_dirs = collect_exclude_dirs(
+ scan_root, extra_exclusions=tuple(get_exclusions()) + config_excludes
+ )
skip_tests = _load_bandit_skip_tests()
result = detect_with_bandit(
scan_root, zone_map, exclude_dirs=exclude_dirs, skip_tests=skip_tests,
diff --git a/desloppify/tests/detectors/test_external_adapters_exclude_flags.py b/desloppify/tests/detectors/test_external_adapters_exclude_flags.py
index c4b3dc87..dcd3cf45 100644
--- a/desloppify/tests/detectors/test_external_adapters_exclude_flags.py
+++ b/desloppify/tests/detectors/test_external_adapters_exclude_flags.py
@@ -4,6 +4,7 @@
from unittest.mock import MagicMock, patch
+from desloppify.languages.python._security import detect_python_security
from desloppify.languages.python.detectors.ruff_smells import detect_with_ruff_smells
from desloppify.languages.python.detectors.unused import detect_unused
@@ -104,3 +105,30 @@ def _capture_run(cmd, **kwargs):
detect_unused(tmp_path)
assert "--exclude" not in captured_cmd
+
+
+class TestBanditSecurityExcludeFlag:
+ """Regression: config-level excludes (`desloppify exclude `) must reach
+ bandit's --exclude. Previously only the file-discovery detectors honoured them,
+ so the security detector kept reporting findings from excluded directories."""
+
+ def test_security_passes_config_excludes_to_bandit(self, tmp_path):
+ (tmp_path / "app.py").write_text("x = 1\n")
+ captured_cmd: list[str] = []
+
+ def _capture_run(cmd, **kwargs):
+ captured_cmd.extend(cmd)
+ mock_result = MagicMock()
+ mock_result.stdout = ""
+ mock_result.returncode = 0
+ return mock_result
+
+ with patch("subprocess.run", side_effect=_capture_run), patch(
+ "desloppify.languages.python._security.load_config",
+ return_value={"exclude": ["vendored_lib"]},
+ ):
+ detect_python_security([str(tmp_path / "app.py")], zone_map=None)
+
+ assert "--exclude" in captured_cmd
+ exclude_value = captured_cmd[captured_cmd.index("--exclude") + 1]
+ assert "vendored_lib" in exclude_value