Skip to content

Commit 968c5f8

Browse files
authored
Merge pull request #258 from awslabs/fix/detect-secrets
Fix/detect secrets
2 parents ccf7124 + 99fef1f commit 968c5f8

15 files changed

Lines changed: 215 additions & 64 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# TEMP
22
/automated_security_helper/identifiers/
33
/fix_*.py
4+
/analysis/
45

56
# ASH Ignores
67
utils/cfn-to-cdk/cfn_to_cdk/

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,18 +91,18 @@ ASH v3 integrates multiple open-source security tools as scanners. Tools like Ba
9191
curl -sSfL https://astral.sh/uv/install.sh | sh
9292

9393
# Create an alias for ASH
94-
alias ash="uvx git+https://github.com/awslabs/automated-security-helper.git@v3.2.4"
94+
alias ash="uvx git+https://github.com/awslabs/automated-security-helper.git@v3.2.5"
9595
```
9696

9797
```powershell
9898
# Install uv on Windows with PowerShell if it isn't installed already
9999
irm https://astral.sh/uv/install.ps1 | iex
100100
101101
# Create a function for ASH
102-
function ash { uvx git+https://github.com/awslabs/automated-security-helper.git@v3.2.4 $args }
102+
function ash { uvx git+https://github.com/awslabs/automated-security-helper.git@v3.2.5 $args }
103103
```
104104

105-
> **Floating tag `v3`**: We also maintain a `v3` floating tag that always points to the latest stable v3.x release. You can use `@v3` instead of `@v3.2.4` to stay up to date automatically. Pin a specific version (e.g., `@v3.2.4`) when you need reproducible builds.
105+
> **Floating tag `v3`**: We also maintain a `v3` floating tag that always points to the latest stable v3.x release. You can use `@v3` instead of `@v3.2.5` to stay up to date automatically. Pin a specific version (e.g., `@v3.2.5`) when you need reproducible builds.
106106
107107
### Other Installation Methods
108108

@@ -122,13 +122,13 @@ ash --help
122122
#### Using `pip`
123123

124124
```bash
125-
pip install git+https://github.com/awslabs/automated-security-helper.git@v3.2.4
125+
pip install git+https://github.com/awslabs/automated-security-helper.git@v3.2.5
126126
```
127127

128128
#### Clone the Repository
129129

130130
```bash
131-
git clone https://github.com/awslabs/automated-security-helper.git --branch v3.2.4
131+
git clone https://github.com/awslabs/automated-security-helper.git --branch v3.2.5
132132
cd automated-security-helper
133133
pip install .
134134
```

automated_security_helper/plugin_modules/ash_builtin/scanners/detect_secrets_scanner.py

Lines changed: 155 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
from importlib.metadata import version
44
import json
5+
import multiprocessing
56
from pathlib import Path
67
import re
8+
import sys
79
from typing import Annotated, Any, Dict, List, Literal
810

911
from pydantic import BaseModel, ConfigDict, Field
@@ -148,6 +150,44 @@ def _process_config_options(self):
148150
self.config.options.baseline_file = Path(baseline_path)
149151
break
150152

153+
# If a baseline file was found, load its plugins_used and filters_used
154+
# into scan_settings so they are applied during scanning.
155+
# SecretsCollection.load_from_baseline() only loads results, not settings,
156+
# so we must propagate the baseline's configuration explicitly.
157+
if self.config.options.baseline_file is not None:
158+
try:
159+
with open(Path(self.config.options.baseline_file).absolute(), "r") as f:
160+
baseline_data = json.load(f)
161+
162+
# Only apply baseline settings if scan_settings was not explicitly
163+
# configured by the user (i.e. still at defaults)
164+
if (
165+
self.config.options.scan_settings.version is None
166+
and len(self.config.options.scan_settings.plugins_used) == 0
167+
):
168+
# Load plugins from baseline
169+
if "plugins_used" in baseline_data:
170+
self.config.options.scan_settings.plugins_used = [
171+
DetectSecretsScanSettingsPluginsUsed(**plugin)
172+
for plugin in baseline_data["plugins_used"]
173+
]
174+
# Load filters from baseline (includes should_exclude_file, etc.)
175+
if "filters_used" in baseline_data:
176+
self.config.options.scan_settings.filters_used = [
177+
DetectSecretsScanSettingsFiltersUsed(**f)
178+
for f in baseline_data["filters_used"]
179+
]
180+
ASH_LOGGER.debug(
181+
f"Loaded settings from baseline: "
182+
f"{len(self.config.options.scan_settings.plugins_used)} plugins, "
183+
f"{len(self.config.options.scan_settings.filters_used)} filters"
184+
)
185+
except (json.JSONDecodeError, OSError) as e:
186+
ASH_LOGGER.warning(
187+
f"Failed to read baseline file settings: {e}. "
188+
f"Falling back to default settings."
189+
)
190+
151191
# If no existing baseline is identified then use all detect-secrets plugins
152192
# This is the same as using the default_settings function provided by detect-secrets
153193
if (
@@ -167,6 +207,77 @@ def _process_config_options(self):
167207

168208
return super()._process_config_options()
169209

210+
@staticmethod
211+
def _get_baseline_exclude_patterns(
212+
scan_settings_dict: Dict[str, Any],
213+
) -> List[re.Pattern]:
214+
"""Extract file exclusion regex patterns from scan settings filters.
215+
216+
Looks for detect_secrets.filters.regex.should_exclude_file entries
217+
in the filters_used configuration and compiles their patterns.
218+
219+
Returns:
220+
List of compiled regex patterns for file exclusion.
221+
"""
222+
patterns = []
223+
for filter_config in scan_settings_dict.get("filters_used", []):
224+
if filter_config.get("path") == (
225+
"detect_secrets.filters.regex.should_exclude_file"
226+
):
227+
raw_patterns = filter_config.get("pattern", [])
228+
if isinstance(raw_patterns, str):
229+
raw_patterns = [raw_patterns]
230+
for p in raw_patterns:
231+
try:
232+
patterns.append(re.compile(p))
233+
except re.error as e:
234+
ASH_LOGGER.warning(
235+
f"Invalid exclude pattern '{p}' in baseline: {e}"
236+
)
237+
return patterns
238+
239+
@staticmethod
240+
def _apply_file_exclusions(
241+
files: List[str],
242+
exclude_patterns: List[re.Pattern],
243+
) -> List[str]:
244+
"""Filter out files matching any of the exclude patterns.
245+
246+
Args:
247+
files: List of file paths to filter.
248+
exclude_patterns: Compiled regex patterns to match against.
249+
250+
Returns:
251+
Filtered list of file paths.
252+
"""
253+
if not exclude_patterns:
254+
return files
255+
return [
256+
f
257+
for f in files
258+
if not any(pattern.search(f) for pattern in exclude_patterns)
259+
]
260+
261+
@staticmethod
262+
def _ensure_fork_multiprocessing() -> None:
263+
"""Ensure multiprocessing uses 'fork' start method on Linux.
264+
265+
detect-secrets' scan_files() uses multiprocessing.Pool internally.
266+
On macOS with Python 3.13+, the default start method is 'spawn',
267+
which causes recursive process creation (fork bomb) when called
268+
outside of 'if __name__ == "__main__"' guard.
269+
270+
On Linux containers (CodeBuild, Docker), 'fork' is the default and
271+
works correctly, but we set it explicitly to be safe in case the
272+
default changes in future Python versions.
273+
"""
274+
if sys.platform == "linux":
275+
try:
276+
multiprocessing.set_start_method("fork", force=True)
277+
except RuntimeError:
278+
# Already set — this is fine
279+
pass
280+
170281
def scan(
171282
self,
172283
target: Path,
@@ -266,7 +377,6 @@ def scan(
266377
self._secrets_collection = SecretsCollection.load_from_baseline(
267378
baseline=json.load(f),
268379
)
269-
f.close()
270380

271381
self._secrets_collection.root = Path(target).absolute()
272382
# Find all files to scan from the scan set
@@ -283,6 +393,43 @@ def scan(
283393
if Path(item).name not in [*KNOWN_LOCKFILE_NAMES]
284394
and "/.ash/" not in str(item)
285395
]
396+
397+
# Build the scan_settings dict for transient_settings, ensuring
398+
# filters_used from the baseline are included so detect-secrets
399+
# can apply should_exclude_file and other filters during scanning.
400+
scan_settings_dict = self.config.options.scan_settings.model_dump(
401+
exclude_defaults=True, exclude_none=True, exclude_unset=True
402+
)
403+
# model_dump with exclude_defaults drops empty lists, but we need
404+
# filters_used to be present if the baseline defined any filters,
405+
# so that transient_settings -> configure_settings_from_baseline
406+
# actually configures them.
407+
if (
408+
len(self.config.options.scan_settings.filters_used) > 0
409+
and "filters_used" not in scan_settings_dict
410+
):
411+
scan_settings_dict["filters_used"] = [
412+
f.model_dump(exclude_none=True)
413+
for f in self.config.options.scan_settings.filters_used
414+
]
415+
416+
# Apply exclude file patterns from baseline filters to the scan set
417+
# BEFORE passing files to detect-secrets. This prevents unnecessary
418+
# file I/O and entropy calculations on excluded files, which is
419+
# critical for performance on large repos (e.g. 400+ JSON test files).
420+
exclude_patterns = self._get_baseline_exclude_patterns(scan_settings_dict)
421+
if exclude_patterns:
422+
pre_filter_count = len(scannable)
423+
scannable = self._apply_file_exclusions(scannable, exclude_patterns)
424+
excluded_count = pre_filter_count - len(scannable)
425+
if excluded_count > 0:
426+
self._plugin_log(
427+
f"Excluded {excluded_count} files from detect-secrets scan "
428+
f"based on baseline exclude patterns",
429+
level=15,
430+
target_type=target_type,
431+
)
432+
286433
if len(scannable) == 0:
287434
message = f"There were no scannable files found in target '{target}'"
288435
self._plugin_log(
@@ -302,11 +449,13 @@ def scan(
302449
level=15,
303450
target_type=target_type,
304451
)
305-
with transient_settings(
306-
self.config.options.scan_settings.model_dump(
307-
exclude_defaults=True, exclude_none=True, exclude_unset=True
308-
)
309-
) as settings:
452+
453+
# Ensure multiprocessing uses 'fork' start method to avoid spawn-related
454+
# issues in containerized environments (CodeBuild, Docker) where 'spawn'
455+
# can cause recursive process creation and significant overhead.
456+
self._ensure_fork_multiprocessing()
457+
458+
with transient_settings(scan_settings_dict) as settings:
310459
ASH_LOGGER.debug(f"Settings: {settings}")
311460
self._secrets_collection.scan_files(*scannable)
312461

automated_security_helper/schemas/AshAggregatedResults.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21199,7 +21199,7 @@
2119921199
"supportedTaxonomies": [],
2120021200
"taxa": [],
2120121201
"translationMetadata": null,
21202-
"version": "3.2.4"
21202+
"version": "3.2.5"
2120321203
},
2120421204
"extensions": [],
2120521205
"properties": null

docs/content/docs/advanced-usage.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ print(f"Found {results.summary_stats.total_findings} findings")
216216

217217
## CI/CD Integration
218218

219-
> **Tip**: The examples below use pinned versions (`@v3.2.4`) for reproducibility. You can also use the `v3` floating tag (`@v3`) to always get the latest stable v3.x release, though pinned versions are recommended for CI/CD.
219+
> **Tip**: The examples below use pinned versions (`@v3.2.5`) for reproducibility. You can also use the `v3` floating tag (`@v3`) to always get the latest stable v3.x release, though pinned versions are recommended for CI/CD.
220220
221221
### GitHub Actions
222222

@@ -239,7 +239,7 @@ jobs:
239239
with:
240240
python-version: '3.10'
241241
- name: Install ASH
242-
run: pip install git+https://github.com/awslabs/automated-security-helper.git@v3.2.4
242+
run: pip install git+https://github.com/awslabs/automated-security-helper.git@v3.2.5
243243
- name: Run ASH scan
244244
run: ash --mode local
245245
- name: Upload scan results
@@ -255,7 +255,7 @@ jobs:
255255
ash-scan:
256256
image: python:3.10
257257
script:
258-
- pip install git+https://github.com/awslabs/automated-security-helper.git@v3.2.4
258+
- pip install git+https://github.com/awslabs/automated-security-helper.git@v3.2.5
259259
- ash --mode local
260260
artifacts:
261261
paths:

docs/content/docs/installation-guide.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ ASH v3 uses UV's tool isolation system to automatically manage most scanner depe
3333
curl -sSf https://astral.sh/uv/install.sh | sh
3434

3535
# Create an alias for ASH
36-
alias ash="uvx git+https://github.com/awslabs/automated-security-helper.git@v3.2.4"
36+
alias ash="uvx git+https://github.com/awslabs/automated-security-helper.git@v3.2.5"
3737

3838
# Use as normal
3939
ash --help
@@ -45,22 +45,22 @@ ash --help
4545
irm https://astral.sh/uv/install.ps1 | iex
4646
4747
# Create a function for ASH
48-
function ash { uvx git+https://github.com/awslabs/automated-security-helper.git@v3.2.4 $args }
48+
function ash { uvx git+https://github.com/awslabs/automated-security-helper.git@v3.2.5 $args }
4949
5050
# Use as normal
5151
ash --help
5252
```
5353

5454
!!! tip "Floating tag `v3`"
55-
We also maintain a `v3` floating tag that always points to the latest stable v3.x release. You can use `@v3` instead of a specific version to stay up to date automatically. Pin a specific version (e.g., `@v3.2.4`) when you need reproducible builds, such as in CI/CD pipelines.
55+
We also maintain a `v3` floating tag that always points to the latest stable v3.x release. You can use `@v3` instead of a specific version to stay up to date automatically. Pin a specific version (e.g., `@v3.2.5`) when you need reproducible builds, such as in CI/CD pipelines.
5656

5757
#### 2. Using `pipx`
5858

5959
[`pipx`](https://pypa.github.io/pipx/) installs packages in isolated environments and makes their entry points available globally.
6060

6161
```bash
6262
# Works on Windows, macOS, and Linux
63-
pipx install git+https://github.com/awslabs/automated-security-helper.git@v3.2.4
63+
pipx install git+https://github.com/awslabs/automated-security-helper.git@v3.2.5
6464

6565
# Use as normal
6666
ash --help
@@ -72,7 +72,7 @@ Standard Python package installation:
7272

7373
```bash
7474
# Works on Windows, macOS, and Linux
75-
pip install git+https://github.com/awslabs/automated-security-helper.git@v3.2.4
75+
pip install git+https://github.com/awslabs/automated-security-helper.git@v3.2.5
7676

7777
# Use as normal
7878
ash --help
@@ -84,7 +84,7 @@ For development or if you want to modify ASH:
8484

8585
```bash
8686
# Works on Windows, macOS, and Linux
87-
git clone https://github.com/awslabs/automated-security-helper.git --branch v3.2.4
87+
git clone https://github.com/awslabs/automated-security-helper.git --branch v3.2.5
8888
cd automated-security-helper
8989
pip install .
9090

@@ -134,7 +134,7 @@ To upgrade ASH to the latest version:
134134
### If installed with `uvx`
135135
```bash
136136
# Your alias will use the latest version when specified
137-
alias ash="uvx git+https://github.com/awslabs/automated-security-helper.git@v3.2.4"
137+
alias ash="uvx git+https://github.com/awslabs/automated-security-helper.git@v3.2.5"
138138
```
139139

140140
### If installed with `pipx`
@@ -144,7 +144,7 @@ pipx upgrade automated-security-helper
144144

145145
### If installed with `pip`
146146
```bash
147-
pip install --upgrade git+https://github.com/awslabs/automated-security-helper.git@v3.2.4
147+
pip install --upgrade git+https://github.com/awslabs/automated-security-helper.git@v3.2.5
148148
```
149149

150150
### If installed from repository

docs/content/docs/migration-guide.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ export PATH="${PATH}:/path/to/automated-security-helper"
4848

4949
```bash
5050
# Option 1: Using uvx (recommended) -- add to shell profile
51-
alias ash="uvx git+https://github.com/awslabs/automated-security-helper.git@v3.2.4"
51+
alias ash="uvx git+https://github.com/awslabs/automated-security-helper.git@v3.2.5"
5252

5353
# Option 2: Using pipx
54-
pipx install git+https://github.com/awslabs/automated-security-helper.git@v3.2.4
54+
pipx install git+https://github.com/awslabs/automated-security-helper.git@v3.2.5
5555

5656
# Option 3: Using pip
57-
pip install git+https://github.com/awslabs/automated-security-helper.git@v3.2.4
57+
pip install git+https://github.com/awslabs/automated-security-helper.git@v3.2.5
5858
```
5959

6060
> **Tip**: You can also use the `v3` floating tag (`@v3`) instead of a specific version to always get the latest stable v3.x release. Pin a specific version for CI/CD or reproducible environments.

0 commit comments

Comments
 (0)