diff --git a/README.md b/README.md index 0c5ca0f2b..eecd3a1d2 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,8 @@ The current heuristic searches we implement out of the box include: * **KeywordDetector**: checks to see if certain keywords are being used e.g. `password` or `secret` +* **ArtifactoryDetector**: checks to see if Artifactory credentials are present. + See [detect_secrets/ plugins](https://github.com/Yelp/detect-secrets/tree/master/detect_secrets/plugins) for more details. diff --git a/detect_secrets/core/usage.py b/detect_secrets/core/usage.py index 84b571e60..507c7c3f9 100644 --- a/detect_secrets/core/usage.py +++ b/detect_secrets/core/usage.py @@ -290,6 +290,11 @@ class PluginOptions(object): disable_flag_text='--no-slack-scan', disable_help_text='Disables scanning for Slack tokens.', ), + PluginDescriptor( + classname='ArtifactoryDetector', + disable_flag_text='--no-artifactory-scan', + disable_help_text='Disable scanning for Artifactory credentials', + ), ] def __init__(self, parser): diff --git a/detect_secrets/plugins/artifactory.py b/detect_secrets/plugins/artifactory.py new file mode 100644 index 000000000..512939725 --- /dev/null +++ b/detect_secrets/plugins/artifactory.py @@ -0,0 +1,17 @@ +from __future__ import absolute_import + +import re + +from .base import RegexBasedDetector + + +class ArtifactoryDetector(RegexBasedDetector): + + secret_type = 'Artifactory Credentials' + + blacklist = [ + # artifactory tokens begin with AKC + re.compile(r'(\s|=|"|^)AKC\w{10,}'), # api token + # artifactory encrypted passwords begin with AP6 + re.compile(r'(\s|=|"|^)AP6\w{10,}'), # password + ] diff --git a/detect_secrets/plugins/common/initialize.py b/detect_secrets/plugins/common/initialize.py index 0e2dfb96d..f9ad91b32 100644 --- a/detect_secrets/plugins/common/initialize.py +++ b/detect_secrets/plugins/common/initialize.py @@ -4,6 +4,7 @@ except ImportError: # pragma: no cover from functools32 import lru_cache +from ..artifactory import ArtifactoryDetector # noqa: F401 from ..aws import AWSKeyDetector # noqa: F401 from ..base import BasePlugin from ..basic_auth import BasicAuthDetector # noqa: F401 diff --git a/tests/core/usage_test.py b/tests/core/usage_test.py index 4282be33a..5905d5ba2 100644 --- a/tests/core/usage_test.py +++ b/tests/core/usage_test.py @@ -39,6 +39,7 @@ def test_consolidates_output_basic(self): 'PrivateKeyDetector': {}, 'AWSKeyDetector': {}, 'SlackDetector': {}, + 'ArtifactoryDetector': {}, } assert not hasattr(args, 'no_private_key_scan') diff --git a/tests/main_test.py b/tests/main_test.py index 94b6f9d3c..f4eceab24 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -89,6 +89,7 @@ def test_scan_string_basic( assert main('scan --string'.split()) == 0 assert uncolor(printer_shim.message) == textwrap.dedent(""" AWSKeyDetector : False + ArtifactoryDetector : False Base64HighEntropyString: {} BasicAuthDetector : False HexHighEntropyString : {} @@ -111,6 +112,7 @@ def test_scan_string_cli_overrides_stdin(self): assert main('scan --string 012345'.split()) == 0 assert uncolor(printer_shim.message) == textwrap.dedent(""" AWSKeyDetector : False + ArtifactoryDetector : False Base64HighEntropyString: False (2.585) BasicAuthDetector : False HexHighEntropyString : False (2.121) @@ -232,6 +234,9 @@ def test_old_baseline_ignored_with_update_flag( { "name": "AWSKeyDetector", }, + { + "name": "ArtifactoryDetector", + }, { "base64_limit": 1.5, "name": "Base64HighEntropyString", @@ -267,6 +272,9 @@ def test_old_baseline_ignored_with_update_flag( { "name": "AWSKeyDetector", }, + { + "name": "ArtifactoryDetector", + }, { "name": "BasicAuthDetector", }, @@ -351,6 +359,9 @@ def test_old_baseline_ignored_with_update_flag( { "name": "AWSKeyDetector", }, + { + "name": "ArtifactoryDetector", + }, { "base64_limit": 5.5, "name": "Base64HighEntropyString", @@ -381,6 +392,9 @@ def test_old_baseline_ignored_with_update_flag( { "name": "AWSKeyDetector", }, + { + "name": "ArtifactoryDetector", + }, { "base64_limit": 2.5, "name": "Base64HighEntropyString", diff --git a/tests/plugins/artifactory_test.py b/tests/plugins/artifactory_test.py new file mode 100644 index 000000000..41f6458a1 --- /dev/null +++ b/tests/plugins/artifactory_test.py @@ -0,0 +1,37 @@ +from __future__ import absolute_import + +import pytest + +from detect_secrets.plugins.artifactory import ArtifactoryDetector + + +class TestArtifactoryDetector(object): + + @pytest.mark.parametrize( + 'payload, should_flag', + [ + ('AP6xxxxxxxxxx', True), + ('AKCxxxxxxxxxx', True), + (' AP6xxxxxxxxxx', True), + (' AKCxxxxxxxxxx', True), + ('=AP6xxxxxxxxxx', True), + ('=AKCxxxxxxxxxx', True), + ('\"AP6xxxxxxxxxx\"', True), + ('\"AKCxxxxxxxxxx\"', True), + ('X-JFrog-Art-Api: AKCxxxxxxxxxx', True), + ('X-JFrog-Art-Api: AP6xxxxxxxxxx', True), + ('artifactoryx:_password=AKCxxxxxxxxxx', True), + ('artifactoryx:_password=AP6xxxxxxxxxx', True), + ('testAKCwithinsomeirrelevantstring', False), + ('testAP6withinsomeirrelevantstring', False), + ('X-JFrog-Art-Api: $API_KEY', False), + ('X-JFrog-Art-Api: $PASSWORD', False), + ('artifactory:_password=AP6xxxxxxxx', False), + ('artifactory:_password=AKCxxxxxxxx', False), + ], + ) + def test_analyze_string(self, payload, should_flag): + logic = ArtifactoryDetector() + + output = logic.analyze_string(payload, 1, 'mock_filename') + assert len(output) == int(should_flag) diff --git a/tests/pre_commit_hook_test.py b/tests/pre_commit_hook_test.py index 28bf8d66d..40212bd44 100644 --- a/tests/pre_commit_hook_test.py +++ b/tests/pre_commit_hook_test.py @@ -171,6 +171,9 @@ def test_that_baseline_gets_updated( { 'name': 'AWSKeyDetector', }, + { + 'name': 'ArtifactoryDetector', + }, { 'base64_limit': 4.5, 'name': 'Base64HighEntropyString',