Skip to content

Commit b84459a

Browse files
lirshindalmanlshindelman
andauthored
_settings_lock ensures only one thread can execute transient_settings… (#289)
Co-authored-by: lshindelman <lshindelman@paloaltonetworks.com>
1 parent c43b428 commit b84459a

File tree

1 file changed

+29
-7
lines changed

1 file changed

+29
-7
lines changed

detect_secrets/settings.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import contextlib
2+
import threading
23
from contextlib import contextmanager
34
from copy import deepcopy
45
from functools import lru_cache
@@ -13,6 +14,15 @@
1314
from .util.importlib import import_file_as_module
1415

1516

17+
# Lock to protect transient_settings from concurrent access.
18+
# On macOS, checkov's ParallelRunner uses ThreadPoolExecutor,
19+
# so all scanners share the same process and global state. Without this lock,
20+
# concurrent calls to transient_settings() can corrupt the LRU-cached singletons
21+
# (get_settings, get_plugins, get_mapping_from_secret_type_to_class), causing
22+
# the secrets scanner to silently produce 0 findings.
23+
_settings_lock = threading.Lock()
24+
25+
1626
@lru_cache(maxsize=1)
1727
def get_settings() -> 'Settings':
1828
"""
@@ -77,21 +87,33 @@ def default_settings() -> Generator['Settings', None, None]:
7787

7888
@contextmanager
7989
def transient_settings(config: Dict[str, Any]) -> Generator['Settings', None, None]:
80-
"""Allows the customizability of non-global settings per invocation."""
81-
original_settings = get_settings().json()
90+
"""Allows the customizability of non-global settings per invocation.
91+
92+
Protected by _settings_lock to prevent race conditions when
93+
multiple threads call this concurrently (e.g., IAC + SECRETS scanners
94+
running in parallel via ThreadPoolExecutor on macOS).
95+
"""
96+
with _settings_lock:
97+
original_settings = get_settings().json()
8298

83-
cache_bust()
84-
try:
85-
yield configure_settings_from_baseline(config)
86-
finally:
8799
cache_bust()
88-
configure_settings_from_baseline(original_settings)
100+
try:
101+
yield configure_settings_from_baseline(config)
102+
finally:
103+
cache_bust()
104+
configure_settings_from_baseline(original_settings)
89105

90106

91107
def cache_bust() -> None:
92108
get_plugins.cache_clear()
93109

94110
get_filters.cache_clear()
111+
112+
# BCE-56937: Clear the plugin-type mapping cache to prevent stale mappings
113+
# built from empty settings during a race window.
114+
from .core.plugins.util import get_mapping_from_secret_type_to_class
115+
get_mapping_from_secret_type_to_class.cache_clear()
116+
95117
for path in get_settings().filters:
96118
# Need to also clear the individual caches (e.g. cached regex patterns).
97119
parts = urlparse(path)

0 commit comments

Comments
 (0)