diff --git a/bbot/core/config/models.py b/bbot/core/config/models.py index 91cb188cdc..f478299ee9 100644 --- a/bbot/core/config/models.py +++ b/bbot/core/config/models.py @@ -363,6 +363,7 @@ class BBOTConfig(BaseModel): home: Optional[str] = None keep_scans: Optional[int] = None status_frequency: Optional[int] = None + redact_secrets: Optional[bool] = None file_blobs: Optional[bool] = None folder_blobs: Optional[bool] = None max_mem_percent: Optional[int] = None diff --git a/bbot/defaults.yml b/bbot/defaults.yml index 4eed9c9124..117d8e8705 100644 --- a/bbot/defaults.yml +++ b/bbot/defaults.yml @@ -19,6 +19,8 @@ status_frequency: 15 # that scales linearly from 0s at the threshold up to 5s at threshold+5 (capped at 95%). # Last-ditch effort to give the pipeline a chance to drain before OOM. max_mem_percent: 90 +# Redact secrets (API keys, tokens, etc.) in the saved preset.yml +redact_secrets: true # Include the raw data of files (i.e. PDFs, web screenshots) as base64 in the event file_blobs: false # Include the raw data of directories (i.e. git repos) as tar.gz base64 in the event diff --git a/bbot/scanner/preset/preset.py b/bbot/scanner/preset/preset.py index fa83af5431..ff9402e32f 100644 --- a/bbot/scanner/preset/preset.py +++ b/bbot/scanner/preset/preset.py @@ -911,7 +911,7 @@ def to_dict(self, include_target=False, full_config=False, redact_secrets=False) return preset_dict - def to_yaml(self, include_target=False, full_config=False, sort_keys=False): + def to_yaml(self, include_target=False, full_config=False, sort_keys=False, redact_secrets=False): """ Return the preset in the form of a YAML string. @@ -919,6 +919,7 @@ def to_yaml(self, include_target=False, full_config=False, sort_keys=False): include_target (bool, optional): If True, include seeds, target, and blacklist in the dictionary full_config (bool, optional): If True, include the entire config, not just what's changed from the defaults. sort_keys (bool, optional): If True, sort YAML keys alphabetically + redact_secrets (bool, optional): If True, redact secret values from the output Returns: str: The preset in the form of a YAML string @@ -931,7 +932,9 @@ def to_yaml(self, include_target=False, full_config=False, sort_keys=False): modules: - portscan """ - preset_dict = self.to_dict(include_target=include_target, full_config=full_config) + preset_dict = self.to_dict( + include_target=include_target, full_config=full_config, redact_secrets=redact_secrets + ) return yaml.dump(preset_dict, sort_keys=sort_keys) def _is_valid_module(self, module, module_type, name_only=False, raise_error=True): diff --git a/bbot/scanner/scanner.py b/bbot/scanner/scanner.py index 5ac6c1a4d8..84eed2dd82 100644 --- a/bbot/scanner/scanner.py +++ b/bbot/scanner/scanner.py @@ -360,8 +360,12 @@ async def _prep(self): self.dummy_modules.clear() # save scan preset + redact_secrets = self.config.get("redact_secrets", True) with open(self.home / "preset.yml", "w") as f: - f.write(self.preset.to_yaml()) + if redact_secrets: + f.write("# Secrets (API keys, tokens, etc.) have been redacted.\n") + f.write('# To include secrets, set "redact_secrets: false" in your preset or BBOT config.\n\n') + f.write(self.preset.to_yaml(redact_secrets=redact_secrets)) # log scan overview start_msg = f"Scan seeded with {len(self.seeds.event_seeds):,} seed(s)" diff --git a/bbot/test/test_step_1/test_presets.py b/bbot/test/test_step_1/test_presets.py index f5de75bf48..62eb705ef0 100644 --- a/bbot/test/test_step_1/test_presets.py +++ b/bbot/test/test_step_1/test_presets.py @@ -1189,6 +1189,22 @@ async def test_preset_serialization(clean_default_config): assert "seeds" not in preset_dict +def test_preset_yaml_redacts_secrets(): + preset = Preset( + "evilcorp.com", + config={"modules": {"github_org": {"api_key": "ghp_secrettoken123"}}}, + ) + preset.validate() + preset = preset.bake() + + yaml_plain = preset.to_yaml() + assert "ghp_secrettoken123" in yaml_plain + + yaml_redacted = preset.to_yaml(redact_secrets=True) + assert "ghp_secrettoken123" not in yaml_redacted + assert "api_key" not in yaml_redacted + + def test_preset_file_targets(tmp_path): """Test that file paths in preset target/seeds/blacklist are resolved via PresetPath.