From eec460da604b20a8e103e721562143e51f379c8c Mon Sep 17 00:00:00 2001 From: lshindelman Date: Wed, 25 Feb 2026 15:02:50 +0200 Subject: [PATCH 1/3] . --- detect_secrets/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detect_secrets/settings.py b/detect_secrets/settings.py index eb8181b67..b03e5def2 100644 --- a/detect_secrets/settings.py +++ b/detect_secrets/settings.py @@ -20,7 +20,7 @@ # concurrent calls to transient_settings() can corrupt the LRU-cached singletons # (get_settings, get_plugins, get_mapping_from_secret_type_to_class), causing # the secrets scanner to silently produce 0 findings. -_settings_lock = threading.Lock() +_settings_lock = threading.RLock() @lru_cache(maxsize=1) From 07228bb434385dc3ea555472b4e8d98d260ec19d Mon Sep 17 00:00:00 2001 From: lshindelman Date: Wed, 25 Feb 2026 15:05:58 +0200 Subject: [PATCH 2/3] . --- detect_secrets/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/detect_secrets/settings.py b/detect_secrets/settings.py index b03e5def2..182fc308f 100644 --- a/detect_secrets/settings.py +++ b/detect_secrets/settings.py @@ -88,7 +88,6 @@ def default_settings() -> Generator['Settings', None, None]: @contextmanager def transient_settings(config: Dict[str, Any]) -> Generator['Settings', None, None]: """Allows the customizability of non-global settings per invocation. - Protected by _settings_lock to prevent race conditions when multiple threads call this concurrently (e.g., IAC + SECRETS scanners running in parallel via ThreadPoolExecutor on macOS). From 0ab5f4904f321913e2d5a3b1aa0ad7a061921f26 Mon Sep 17 00:00:00 2001 From: lshindelman Date: Wed, 25 Feb 2026 15:10:10 +0200 Subject: [PATCH 3/3] revert _settings_lock ensures only one thread can execute --- detect_secrets/settings.py | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/detect_secrets/settings.py b/detect_secrets/settings.py index 182fc308f..0d89e6f34 100644 --- a/detect_secrets/settings.py +++ b/detect_secrets/settings.py @@ -1,5 +1,4 @@ import contextlib -import threading from contextlib import contextmanager from copy import deepcopy from functools import lru_cache @@ -14,15 +13,6 @@ from .util.importlib import import_file_as_module -# Lock to protect transient_settings from concurrent access. -# On macOS, checkov's ParallelRunner uses ThreadPoolExecutor, -# so all scanners share the same process and global state. Without this lock, -# concurrent calls to transient_settings() can corrupt the LRU-cached singletons -# (get_settings, get_plugins, get_mapping_from_secret_type_to_class), causing -# the secrets scanner to silently produce 0 findings. -_settings_lock = threading.RLock() - - @lru_cache(maxsize=1) def get_settings() -> 'Settings': """ @@ -87,32 +77,21 @@ def default_settings() -> Generator['Settings', None, None]: @contextmanager def transient_settings(config: Dict[str, Any]) -> Generator['Settings', None, None]: - """Allows the customizability of non-global settings per invocation. - Protected by _settings_lock to prevent race conditions when - multiple threads call this concurrently (e.g., IAC + SECRETS scanners - running in parallel via ThreadPoolExecutor on macOS). - """ - with _settings_lock: - original_settings = get_settings().json() + """Allows the customizability of non-global settings per invocation.""" + original_settings = get_settings().json() + cache_bust() + try: + yield configure_settings_from_baseline(config) + finally: cache_bust() - try: - yield configure_settings_from_baseline(config) - finally: - cache_bust() - configure_settings_from_baseline(original_settings) + configure_settings_from_baseline(original_settings) def cache_bust() -> None: get_plugins.cache_clear() get_filters.cache_clear() - - # BCE-56937: Clear the plugin-type mapping cache to prevent stale mappings - # built from empty settings during a race window. - from .core.plugins.util import get_mapping_from_secret_type_to_class - get_mapping_from_secret_type_to_class.cache_clear() - for path in get_settings().filters: # Need to also clear the individual caches (e.g. cached regex patterns). parts = urlparse(path)