diff --git a/detect_secrets/core/usage.py b/detect_secrets/core/usage.py index c30814a01..efef69fe4 100644 --- a/detect_secrets/core/usage.py +++ b/detect_secrets/core/usage.py @@ -220,6 +220,11 @@ class PluginOptions(object): disable_flag_text='--no-private-key-scan', disable_help_text='Disables scanning for private keys.', ), + PluginDescriptor( + classname='BasicAuthDetector', + disable_flag_text='--no-basic-auth-scan', + disable_help_text='Disables scanning for Basic Auth formatted URIs.', + ), ] def __init__(self, parser): diff --git a/detect_secrets/plugins/basic_auth.py b/detect_secrets/plugins/basic_auth.py new file mode 100644 index 000000000..399fde2e4 --- /dev/null +++ b/detect_secrets/plugins/basic_auth.py @@ -0,0 +1,35 @@ +from __future__ import absolute_import + +import re + +from .base import BasePlugin +from detect_secrets.core.potential_secret import PotentialSecret + + +BASIC_AUTH_REGEX = re.compile( + r'.*?://[^:]+:([^@]+)@', +) + + +class BasicAuthDetector(BasePlugin): + + secret_type = 'Basic Auth Credentials' + + def analyze_string(self, string, line_num, filename): + output = {} + + for result in self.secret_generator(string): + secret = PotentialSecret( + self.secret_type, + filename, + line_num, + result, + ) + output[secret] = secret + + return output + + def secret_generator(self, string): + results = BASIC_AUTH_REGEX.findall(string) + for result in results: + yield result diff --git a/detect_secrets/plugins/core/initialize.py b/detect_secrets/plugins/core/initialize.py index 73e2672ab..ae9ea4c43 100644 --- a/detect_secrets/plugins/core/initialize.py +++ b/detect_secrets/plugins/core/initialize.py @@ -5,6 +5,7 @@ from functools32 import lru_cache from ..base import BasePlugin +from ..basic_auth import BasicAuthDetector # noqa: F401 from ..high_entropy_strings import Base64HighEntropyString # noqa: F401 from ..high_entropy_strings import HexHighEntropyString # noqa: F401 from ..private_key import PrivateKeyDetector # noqa: F401 diff --git a/tests/core/usage_test.py b/tests/core/usage_test.py index 437d8542a..1cf679f2a 100644 --- a/tests/core/usage_test.py +++ b/tests/core/usage_test.py @@ -28,6 +28,7 @@ def test_consolidates_output_basic(self): 'HexHighEntropyString': { 'hex_limit': 3, }, + 'BasicAuthDetector': {}, 'Base64HighEntropyString': { 'base64_limit': 4.5, }, diff --git a/tests/main_test.py b/tests/main_test.py index f31fb9cbc..27e0da729 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -84,6 +84,7 @@ def test_scan_string_basic(self, mock_baseline_initialize): assert main('scan --string'.split()) == 0 assert printer_shim.message == textwrap.dedent(""" Base64HighEntropyString: False (3.459) + BasicAuthDetector : False HexHighEntropyString : True (3.459) PrivateKeyDetector : False """)[1:] @@ -99,6 +100,7 @@ def test_scan_string_cli_overrides_stdin(self): assert main('scan --string 012345'.split()) == 0 assert printer_shim.message == textwrap.dedent(""" Base64HighEntropyString: False (2.585) + BasicAuthDetector : False HexHighEntropyString : False (2.121) PrivateKeyDetector : False """)[1:] diff --git a/tests/plugins/basic_auth_test.py b/tests/plugins/basic_auth_test.py new file mode 100644 index 000000000..dbf3c4496 --- /dev/null +++ b/tests/plugins/basic_auth_test.py @@ -0,0 +1,20 @@ +from __future__ import absolute_import + +import pytest + +from detect_secrets.plugins.basic_auth import BasicAuthDetector + + +class TestBasicAuthDetector(object): + + @pytest.mark.parametrize( + 'payload, should_flag', + [ + ('https://username:password@yelp.com', True,), + ], + ) + def test_analyze_string(self, payload, should_flag): + logic = BasicAuthDetector() + + output = logic.analyze_string(payload, 1, 'mock_filename') + assert len(output) == int(should_flag)