Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions detect_secrets/settings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import contextlib
import threading
from contextlib import contextmanager
from copy import deepcopy
from functools import lru_cache
Expand All @@ -13,6 +14,15 @@
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.Lock()


@lru_cache(maxsize=1)
def get_settings() -> 'Settings':
"""
Expand Down Expand Up @@ -77,21 +87,33 @@ 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."""
original_settings = get_settings().json()
"""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()

cache_bust()
try:
yield configure_settings_from_baseline(config)
finally:
cache_bust()
configure_settings_from_baseline(original_settings)
try:
yield configure_settings_from_baseline(config)
finally:
cache_bust()
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)
Expand Down
Loading