├── .version ├── tests ├── __init__.py ├── resources │ ├── minimal.yaml │ └── multiple.yaml ├── test_hooks_version_check.py ├── test_secrets_filename.py └── test_secrets_filecontent.py ├── .major-version ├── .python-version ├── MANIFEST.in ├── security_git_hooks ├── __init__.py ├── conf.py ├── repository_check.py ├── secrets_filename.py ├── hooks_version_check.py ├── secrets_filecontent.py └── conf.yaml ├── Pipfile ├── .gitignore ├── .pre-commit-hooks.yaml ├── .pre-commit-config.yaml ├── setup.py ├── repository.yaml ├── tox.ini └── README.md /.version: -------------------------------------------------------------------------------- 1 | 1.2.0 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.major-version: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.9.1 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include .version 2 | -------------------------------------------------------------------------------- /security_git_hooks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/resources/minimal.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/hmrc/security-git-hooks 3 | rev: v1.0 4 | hooks: 5 | - id: secrets_filecontent 6 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | tox = "*" 10 | 11 | [requires] 12 | python_version = "3.9.1" 13 | -------------------------------------------------------------------------------- /security_git_hooks/conf.py: -------------------------------------------------------------------------------- 1 | import pkg_resources 2 | import yaml 3 | import re 4 | 5 | CONF_YAML = pkg_resources.resource_string(__name__, "conf.yaml") 6 | 7 | IGNORE_KEYWORD = "LDS ignore" 8 | 9 | 10 | def validate_expressions(ITEM): 11 | for rule in yaml.safe_load(CONF_YAML)[ITEM].values(): 12 | re.compile(rule["pattern"]) 13 | -------------------------------------------------------------------------------- /security_git_hooks/repository_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | 5 | 6 | def repository_check(): 7 | if os.path.exists("./repository.yaml") is True: 8 | return 0 9 | else: 10 | print("No repository.yaml found") 11 | return 1 12 | 13 | 14 | def main(): 15 | repository_check() 16 | 17 | 18 | if __name__ == "__main__": 19 | exit(main()) 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /venv 2 | /.coverage 3 | /.tox 4 | /htmlcov 5 | *.egg-info/ 6 | logs 7 | project/project 8 | project/target 9 | target 10 | lib_managed 11 | tmp 12 | .history 13 | dist 14 | /.idea 15 | /*.iml 16 | /*.ipr 17 | /out 18 | /.idea_modules 19 | /.classpath 20 | /.project 21 | /RUNNING_PID 22 | /.settings 23 | *.iws 24 | node_modules/ 25 | npm-debug.log 26 | yarn-debug.log 27 | yarn-error.log 28 | __pycache__/ 29 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: secrets_filename 2 | name: Check filenames for potential secrets 3 | description: 'Check filenames for potential secrets' 4 | language: python 5 | entry: security-git-hooks-filecontent 6 | 7 | - id: secrets_filecontent 8 | name: Check file content for potential secrets 9 | description: 'Check file content for potential secrets' 10 | language: python 11 | entry: security-git-hooks-filename 12 | 13 | - id: repository_check 14 | name: Check local hooks release against latest 15 | description: 'Check local hooks release against latest' 16 | language: python 17 | entry: security-git-hooks-repository-check 18 | 19 | - id: hooks_version_check 20 | name: Check repository.yaml 21 | description: 'Check repository.yaml' 22 | language: python 23 | entry: security-git-hooks-version-check 24 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/hmrc/security-git-hooks 3 | rev: release/1.9.0 4 | hooks: 5 | - id: secrets_filename 6 | files: '' 7 | language: python 8 | pass_filenames: true 9 | exclude: ".tar$|.gz$|.jar$|.7z$|.rar$|.bz2$|.zip$|.gzip$|.war$|.ear$|.xlsx$|.xls$| 10 | |.docx$|.doc$|.pptx$|.pdf$|.jpg$|.png$|.jpeg$|.tif$|.tiff$|.gif$|.bmp$|.webp$| 11 | |.svg$|.ico$|.psd$|.exe$|.dll$|.dmg$|.de$|.rpm$|.*phantomjs.*|.*chromedriver.*|/.*BrowserStackLocal.*" 12 | - id: secrets_filecontent 13 | files: '' 14 | language: python 15 | pass_filenames: true 16 | exclude: ".tar$|.gz$|.jar$|.7z$|.rar$|.bz2$|.zip$|.gzip$|.war$|.ear$|.xlsx$|.xls$| 17 | |.docx$|.doc$|.pptx$|.pdf$|.jpg$|.png$|.jpeg$|.tif$|.tiff$|.gif$|.bmp$|.webp$|.svg$|.ico$|.psd$|.exe$|.dll$|.dmg$|.de$|.rpm$|kitchen.yml" 18 | - id: hooks_version_check 19 | name: Checking local hooks against latest release 20 | verbose: true 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from codecs import open 3 | import os 4 | 5 | 6 | def read(filename): 7 | return open(os.path.join(os.path.dirname(__file__), filename)).read() 8 | 9 | 10 | setup( 11 | name="security-git-hooks", 12 | author="HRMC Platform Security", 13 | author_email="", 14 | version=read(".version"), 15 | description="Detect secrets prior to commit", 16 | url="https://github.com/hmrc/security-git-hooks/", 17 | long_description=read("README.md"), 18 | entry_points={ 19 | "console_scripts": [ 20 | "security-git-hooks-filecontent = security_git_hooks.secrets_filecontent:main", 21 | "security-git-hooks-filename = security_git_hooks.secrets_filename:main", 22 | "security-git-hooks-version-check = security_git_hooks.hooks_version_check:main", 23 | "security-git-hooks-repository-check = security_git_hooks.hooks_repository_check:main", 24 | ] 25 | }, 26 | packages=find_packages(), 27 | install_requires=["PyYAML", "requests", "setuptools"], 28 | tests_require=["pytest"], 29 | package_data={"": ["conf.yaml"]}, 30 | ) 31 | -------------------------------------------------------------------------------- /repository.yaml: -------------------------------------------------------------------------------- 1 | repoVisibility: public_0C3F0CE3E6E6448FAD341E7BFA50FCD333E06A20CFF05FCACE61154DDBBADF71 2 | leakDetectionExemptions: 3 | - ruleId: 'aws_secret_access_key' 4 | filePath: '/tests/test_secrets_filecontent.py' 5 | text: 'H5xnFhnR3H/o6nrcfoMLR9VfOlfOY17pa/+PchnA' 6 | 7 | - ruleId: 'aws_secret_access_key' 8 | filePath: '/README.md' 9 | text: '1ye+VarkHMg7o6MNjwWIqOYICe03lfA+KPPAmeaY' 10 | 11 | - ruleId: 'cert_1' 12 | filePath: '/tests/test_secrets_filecontent.py' 13 | text: 'PRIVATE KEY' 14 | 15 | - ruleId: 'application_secret' 16 | filePath: '/tests/test_secrets_filecontent.py' 17 | text: 'helloiamasecret' 18 | 19 | - ruleId: 'cookie_deviceId_secret' 20 | filePath: '/tests/test_secrets_filecontent.py' 21 | text: 'helloiamasecret' 22 | 23 | - ruleId: 'sso_encryption_key' 24 | filePath: '/tests/test_secrets_filecontent.py' 25 | text: 'P5xsJ9Nt+quxDKzB4DeLfw==' 26 | 27 | - ruleId: 'play_crypto_secret' 28 | filePath: '/tests/test_secrets_filecontent.py' 29 | text: 'zLiPolptzA5HLnRG9XAF6hQGP4QGQvEd82W27dzsI8HpFQRToMD7m8f78LQ0Ur7k' 30 | 31 | - ruleId: 'play_crypto_secret' 32 | filePath: '/tests/test_secrets_filecontent.py' 33 | text: 'changeme' -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = pre-commit,lint,py39 3 | indexserver = 4 | default = https://artefacts.tax.service.gov.uk/artifactory/api/pypi/pips/simple 5 | 6 | [testenv] 7 | deps = 8 | pytest 9 | pytest-cov 10 | commands = 11 | pytest -v 12 | 13 | [testenv:lint] 14 | deps = 15 | black 16 | flake8 17 | flake8-bugbear 18 | flake8-colors 19 | commands = 20 | black . 21 | flake8 22 | 23 | [testenv:release] 24 | deps = 25 | wheel 26 | twine 27 | version-incrementor 28 | passenv = 29 | TWINE_USERNAME 30 | TWINE_PASSWORD 31 | TWINE_REPOSITORY_URL 32 | GITHUB_API_USER 33 | GITHUB_API_TOKEN 34 | GIT_BRANCH 35 | commands = 36 | prepare-release 37 | python setup.py bdist_wheel sdist 38 | twine upload --skip-existing dist/* 39 | 40 | [testenv:pre-commit] 41 | skip_install = true 42 | deps = pre-commit 43 | commands = pre-commit autoupdate 44 | 45 | 46 | [testenv:black] 47 | deps=black 48 | commands=black . 49 | 50 | [pytest] 51 | junit_family = xunit2 52 | addopts = --cov=security_git_hooks 53 | --cov-fail-under=2 54 | --cov-report=term-missing 55 | --cov-report=html 56 | --junitxml=target/report.xml 57 | 58 | [flake8] 59 | max-complexity = 10 60 | exclude = .git,__pycache__,build,dist,.tox 61 | max-line-length = 120 62 | ignore=D103,D107,W503,D104 63 | 64 | 65 | -------------------------------------------------------------------------------- /tests/resources/multiple.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/hmrc/security-git-hooks 3 | rev: v1.0 4 | hooks: 5 | - id: secrets_filecontent 6 | name: Checking staged files for sensitive content 7 | exclude: ".tar$|.gz$|.jar$|.7z$|.rar$|.bz2$|.zip$|.gzip$|.war$|.ear$|.xlsx$|.xls$| 8 | |.docx$|.doc$|.pptx$|.pdf$|.jpg$|.png$|.jpeg$|.tif$|.tiff$|.gif$|.bmp$|.webp$|.svg$|.ico$|.psd$|.exe$|.dll$|.dmg$|.de$|.rpm$" 9 | - id: secrets_filename 10 | name: Checking staged files for sensitive file types 11 | exclude: ".tar$|.gz$|.jar$|.7z$|.rar$|.bz2$|.zip$|.gzip$|.war$|.ear$|.xlsx$|.xls$| 12 | |.docx$|.doc$|.pptx$|.pdf$|.jpg$|.png$|.jpeg$|.tif$|.tiff$|.gif$|.bmp$|.webp$|.svg$|.ico$|.psd$|.exe$|.dll$|.dmg$|.de$|.rpm$" 13 | - repo: https://github.com/hmrc/some-other-repo 14 | rev: v2.0 15 | hooks: 16 | - id: secrets_filecontent 17 | name: Checking staged files for sensitive content 18 | exclude: ".tar$|.gz$|.jar$|.7z$|.rar$|.bz2$|.zip$|.gzip$|.war$|.ear$|.xlsx$|.xls$| 19 | |.docx$|.doc$|.pptx$|.pdf$|.jpg$|.png$|.jpeg$|.tif$|.tiff$|.gif$|.bmp$|.webp$|.svg$|.ico$|.psd$|.exe$|.dll$|.dmg$|.de$|.rpm$" 20 | - id: secrets_filename 21 | name: Checking staged files for sensitive file types 22 | exclude: ".tar$|.gz$|.jar$|.7z$|.rar$|.bz2$|.zip$|.gzip$|.war$|.ear$|.xlsx$|.xls$| 23 | |.docx$|.doc$|.pptx$|.pdf$|.jpg$|.png$|.jpeg$|.tif$|.tiff$|.gif$|.bmp$|.webp$|.svg$|.ico$|.psd$|.exe$|.dll$|.dmg$|.de$|.rpm$" -------------------------------------------------------------------------------- /security_git_hooks/secrets_filename.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import re 5 | import yaml 6 | 7 | from . import conf 8 | 9 | # import conf 10 | 11 | """Parse the files in a github commit for potentially sensitive filenames, per rules 12 | defined at https://github.com/hmrc/app-config-base/blob/master/leak-detection.conf""" 13 | 14 | patterns = yaml.safe_load(conf.CONF_YAML)["FILE_NAME_REGEXES"] 15 | 16 | 17 | def detect_match_against_filename(files_to_check): 18 | """checks argument against compiled regexes""" 19 | for _, rule in patterns.items(): 20 | if re.search(rule["pattern"], files_to_check): 21 | return rule["pattern"] 22 | 23 | 24 | def main(argv=None): 25 | """Parses filenames and provides outut. 26 | Note that if manually passed a directory as argument, checks are not recursive as Git 27 | adds files to a commit individually.""" 28 | conf.validate_expressions("FILE_NAME_REGEXES") 29 | parser = argparse.ArgumentParser() 30 | parser.add_argument("filenames", nargs="*", help="Files to check") 31 | args = parser.parse_args(argv) 32 | exit_code = 0 33 | for filename in args.filenames: 34 | match = detect_match_against_filename(filename) 35 | if match: 36 | exit_code = 1 37 | print( 38 | "{file} may contain sensitive information due to the file type".format( 39 | file=filename 40 | ) 41 | ) 42 | return exit_code 43 | 44 | 45 | if __name__ == "__main__": 46 | exit(main()) 47 | -------------------------------------------------------------------------------- /tests/test_hooks_version_check.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import json 4 | 5 | # import requests_mock 6 | # import responses 7 | 8 | from security_git_hooks import hooks_version_check 9 | 10 | 11 | my_path = os.path.abspath(os.path.dirname(__file__)) 12 | 13 | minimal = os.path.join(my_path, "resources/minimal.yaml") 14 | malformed = os.path.join(my_path, "resources/malformed.yaml") 15 | multiple = os.path.join(my_path, "resources/multiple.yaml") 16 | outororder = os.path.join(my_path, "resources/outoforder.yaml") 17 | 18 | 19 | @pytest.mark.skip(reason="don't ask") 20 | @pytest.fixture 21 | def mocked_responses(responses): 22 | with responses.RequestsMock() as rsps: 23 | yield rsps 24 | 25 | 26 | @pytest.mark.parametrize( 27 | "test_input, expected", [(minimal, "v1.0"), (multiple, "v1.0")] 28 | ) 29 | def test_local_config(test_input, expected): 30 | assert hooks_version_check.check_release_version_from_config(test_input) == expected 31 | 32 | 33 | """ 34 | def test_url(requests_mock): 35 | requests_mock.get("https://api.github.com/repos/hmrc/security-git-hooks/releases/latest", tag_name = "v1.0") 36 | response = requests.get("https://api.github.com/repos/hmrc/security-git-hooks/releases/latest") 37 | assert response.tag_name == "v1.0" 38 | 39 | """ 40 | 41 | 42 | @pytest.mark.skip(reason="don't ask") 43 | def test_response(mocked_responses): 44 | mocked_responses.add( 45 | mocked_responses.POST, 46 | url="https://api.github.com/repos/hmrc/security-git-hooks/releases/latest", 47 | body=json.dumps({"": {"tag_name": "v1.0.0-beta5"}}), 48 | ) 49 | 50 | response = hooks_version_check.check_release_version_from_remote_repo() 51 | assert "v" in response 52 | -------------------------------------------------------------------------------- /security_git_hooks/hooks_version_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import requests 4 | import yaml 5 | import sys 6 | 7 | 8 | """This hook checks the security-git-hooks release contained in .pre-commit-config.yaml against 9 | the latest release from https://github.com/hmrc/security-git-hooks. It is an information only 10 | hook, and will always pass as to prevent the interruption of workflow, however it will produce 11 | output to advise when a new release is available""" 12 | 13 | 14 | def check_release_version_from_config(pre_commit_config_yaml): 15 | """checks the pre-commit-config.yaml in the current directory and returns the release tag detailed there""" 16 | try: 17 | with open(pre_commit_config_yaml, "r") as file: 18 | config = yaml.safe_load(file) 19 | res = filter(lambda x: "security-git-hooks" in x["repo"], config["repos"]) 20 | return next(res)["rev"] 21 | except Exception: 22 | raise Exception("Local checks failed") 23 | 24 | 25 | def check_release_version_from_remote_repo(): 26 | """checks the GitHub API and returns the latest release tag detailed there""" 27 | try: 28 | req = requests.get( 29 | "https://api.github.com/repos/hmrc/security-git-hooks/releases/latest" 30 | ) 31 | content = req.json() 32 | return content["tag_name"] 33 | except Exception: 34 | raise Exception("Remote checks failed") 35 | 36 | 37 | def main(): 38 | try: 39 | config_version = check_release_version_from_config(".pre-commit-config.yaml") 40 | latest_release = check_release_version_from_remote_repo() 41 | if config_version == latest_release: 42 | print("All HMRC hooks are up to date") 43 | else: 44 | print( 45 | "Your security-git-hooks version is {yours} and latest is {latest}." 46 | ' Please run the following command in this directory: "pre-commit autoupdate"'.format( 47 | yours=config_version, latest=latest_release 48 | ) 49 | ) 50 | 51 | except Exception as e: 52 | print( 53 | "Checking for updates against HMRC hooks failed ({error}). " 54 | "Run 'pre-commit autoupdate' in this directory as a precaution".format( 55 | error=e 56 | ) 57 | ) 58 | 59 | return 0 60 | 61 | 62 | if __name__ == "__main__": 63 | sys.exit(main()) 64 | -------------------------------------------------------------------------------- /security_git_hooks/secrets_filecontent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import re 4 | import yaml 5 | 6 | from . import conf 7 | 8 | # import conf 9 | 10 | PRIVATE_RULES = yaml.safe_load(conf.CONF_YAML)["PRIVATE_RULES"] 11 | 12 | PUBLIC_RULES = yaml.safe_load(conf.CONF_YAML)["PUBLIC_RULES"] 13 | 14 | 15 | def repository_yaml_check(repository_yaml): 16 | """Apply appropriate ruleset per repoVisibility in repository.yaml file""" 17 | """ 18 | - load repository.yaml (open, read, parse) 19 | - access value of repoVisibility 20 | - return rulesets according to value 21 | 22 | """ 23 | 24 | with open(repository_yaml, "r") as file: 25 | yamlfile = yaml.safe_load(file) 26 | if ( 27 | yamlfile["repoVisibility"] 28 | == "public_0C3F0CE3E6E6448FAD341E7BFA50FCD333E06A20CFF05FCACE61154DDBBADF71" 29 | ): 30 | return PUBLIC_RULES 31 | elif ( 32 | yamlfile["repoVisibility"] 33 | == "private_12E5349CFB8BBA30AF464C24760B70343C0EAE9E9BD99156345DD0852C2E0F6F" 34 | ): 35 | return PRIVATE_RULES 36 | else: 37 | print("no repository.yaml data found, searching against all rules") 38 | return PUBLIC_RULES 39 | 40 | 41 | repository_yaml_check("repository.yaml") 42 | 43 | 44 | def detect_secret_in_line(line_to_check, filename): 45 | """compiles regex and checks against line.""" 46 | rules = repository_yaml_check("repository.yaml") 47 | 48 | rules_to_check = { 49 | rule_name: rule 50 | for rule_name, rule in rules.items() 51 | if not is_rule_excluded(filename, rule) 52 | } 53 | 54 | for rule_name, rule in rules_to_check.items(): 55 | if re.search(rule["pattern"], line_to_check): 56 | return rule_name 57 | 58 | 59 | def is_rule_excluded(filename, rule): 60 | for exclusion in rule["exclusions"]: 61 | if re.search(exclusion, filename): 62 | return True 63 | 64 | 65 | def main(argv=None): 66 | conf.validate_expressions("PRIVATE_RULES") 67 | parser = argparse.ArgumentParser() 68 | parser.add_argument("filenames", nargs="*", help="Files to check") 69 | args = parser.parse_args(argv) 70 | exit_code = 0 71 | 72 | for filename in args.filenames: 73 | with open(filename, "r") as f: 74 | flag = False 75 | for i, line in enumerate(f): 76 | if re.search(conf.IGNORE_KEYWORD, line): 77 | flag = True 78 | continue 79 | if flag: 80 | flag = False 81 | continue 82 | rule = detect_secret_in_line(line, filename) 83 | if rule: 84 | print( 85 | "Potentially sensitive string matching rule: {rule} " 86 | "found on line {line_number} of {file}".format( 87 | rule=rule, line_number=i + 1, file=filename 88 | ) 89 | ) 90 | exit_code = 1 91 | return exit_code 92 | 93 | 94 | if __name__ == "__main__": 95 | exit(main()) 96 | -------------------------------------------------------------------------------- /security_git_hooks/conf.yaml: -------------------------------------------------------------------------------- 1 | FILE_NAME_REGEXES: 2 | p12: 3 | pattern: \.p12$ 4 | pfx: 5 | pattern: \.pfx$ 6 | pkcs12: 7 | pattern: \.pkcs12$ 8 | pem: 9 | pattern: \.pem$ 10 | rsa: 11 | pattern: _rsa$ 12 | dsa: 13 | pattern: _dsa$ 14 | ed25519: 15 | pattern: _ed25519$ 16 | ecdsa: 17 | pattern: _ecdsa$ 18 | jks: 19 | pattern: \.jks$ 20 | bash/zsh rc file: 21 | pattern: ^\.?(bash|zsh)?rc$ 22 | bash/zsh profile: 23 | pattern: ^\.?(bash|zsh)_profile$ 24 | bash/zsh aliases file: 25 | pattern: ^\.?(bash|zsh)_aliases$ 26 | credential(s) file: 27 | pattern: ^\.credential(s)?$ 28 | Github Enterprise file: 29 | pattern: ^\.githubenterprise$ 30 | Apple Keychain file: 31 | pattern: ^\.*keychain$ 32 | Keystore/Keyring file: 33 | pattern: ^key(store|ring)$ 34 | terraform state files 1: 35 | pattern: ^\.*.tfstate.*$ 36 | terraform state files 2: 37 | pattern: ^\.*.tfstate$ 38 | 39 | PRIVATE_RULES: 40 | aws_secret_access_key: 41 | pattern: (SECRET|secret|Secret|ACCESS|access|Access|KEY|key|Key)("|')?(:.{0,50})?\s*(:|=>|=|->)\s*("|')?[A-Za-z0-9\/\+=]{40}(?![A-Za-z0-9\/+=]) 42 | exclusions: [] 43 | cert_1: 44 | pattern: -----(BEGIN|END).*?PRIVATE.*?----- 45 | exclusions: [kitchen.yaml$, kitchen.yml$] 46 | application_secret: 47 | pattern: application\.secret\s*(=|:|->)\s*(?!(\s*ENC\[)) 48 | exclusions: [.scala$, conf/application.conf$] 49 | play_crypto_secret: 50 | pattern: play\.crypto\.secret\s*(=|:|->)\s*(?!(\s*ENC\[)) 51 | exclusions: [.scala$, conf/application.conf$] 52 | play_http_secret_key: 53 | pattern: play\.http\.secret\.key\s*(=|:|->)\s*(?!(\s*(ENC\[|"some_secret"))) 54 | exclusions: [ .scala$, conf/application.conf$ ] 55 | cookie_deviceId_secret: 56 | pattern: cookie\.deviceId\.secret\s*(=|:|->)\s*(?!(\s*ENC\[)) 57 | exclusions: [.scala$, conf/application.conf$] 58 | sso_encryption_key: 59 | pattern: sso\.encryption\.key\s*(=|:|->)\s*(?!(\s*ENC\[)) 60 | exclusions: [.scala$, conf/application.conf$] 61 | 62 | PUBLIC_RULES: 63 | ip_adresses: 64 | pattern: \b10\.(?:19|33|39)\.(?:6|1)(?:82|76|[3-4]|[8-9])[6-7]*\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b 65 | exclusions: [] 66 | aws_secret_access_key: 67 | pattern: (SECRET|secret|Secret|ACCESS|access|Access|KEY|key|Key)("|')?(:.{0,50})?\s*(:|=>|=|->)\s*("|')?[A-Za-z0-9\/\+=]{40}(?![A-Za-z0-9\/+=]) 68 | exclusions: [ ] 69 | cert_1: 70 | pattern: -----(BEGIN|END).*?PRIVATE.*?----- 71 | exclusions: [ kitchen.yaml$, kitchen.yml$ ] 72 | application_secret: 73 | pattern: application\.secret\s*(=|:|->)\s*(?!(\s*ENC\[)) 74 | exclusions: [ .scala$, conf/application.conf$ ] 75 | play_crypto_secret: 76 | pattern: play\.crypto\.secret\s*(=|:|->)\s*(?!(\s*ENC\[)) 77 | exclusions: [ .scala$, conf/application.conf$ ] 78 | play_http_secret_key: 79 | pattern: play\.http\.secret\.key\s*(=|:|->)\s*(?!(\s*(ENC\[|"some_secret"))) 80 | exclusions: [ .scala$, conf/application.conf$ ] 81 | cookie_deviceId_secret: 82 | pattern: cookie\.deviceId\.secret\s*(=|:|->)\s*(?!(\s*ENC\[)) 83 | exclusions: [ .scala$, conf/application.conf$ ] 84 | sso_encryption_key: 85 | pattern: sso\.encryption\.key\s*(=|:|->)\s*(?!(\s*ENC\[)) 86 | exclusions: [ .scala$, conf/application.conf$ ] 87 | 88 | REPOSITORY_YAML_CONTENT: 89 | public: "public_0C3F0CE3E6E6448FAD341E7BFA50FCD333E06A20CFF05FCACE61154DDBBADF71" 90 | private: "private_12E5349CFB8BBA30AF464C24760B70343C0EAE9E9BD99156345DD0852C2E0F6F" -------------------------------------------------------------------------------- /tests/test_secrets_filename.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from security_git_hooks import secrets_filename 3 | 4 | """All comments at beginning of test sets correspond to id of leak detection rules per 5 | https://github.com/hmrc/app-config-base/blob/master/leak-detection.conf""" 6 | 7 | 8 | # filename_private_key_1 9 | 10 | 11 | @pytest.mark.parametrize( 12 | "test_input,expected", 13 | [("fake.p12", r"\.p12$"), ("p12.txt", None), ("fake.p12.txt", None)], 14 | ) 15 | def test_private_key_1_p12(test_input, expected): 16 | assert secrets_filename.detect_match_against_filename(test_input) == expected 17 | 18 | 19 | # filename_private_key_2 20 | 21 | 22 | @pytest.mark.parametrize( 23 | "test_input,expected", 24 | [("fake.pfx", r"\.pfx$"), ("pfx.txt", None), ("fake.pfx.txt", None)], 25 | ) 26 | def test_private_key_1_pfx(test_input, expected): 27 | assert secrets_filename.detect_match_against_filename(test_input) == expected 28 | 29 | 30 | # filename_private_key_3 31 | 32 | 33 | @pytest.mark.parametrize( 34 | "test_input,expected", 35 | [("fake.pkcs12", r"\.pkcs12$"), ("pkcs12.txt", None), ("fake.pkcs12.txt", None)], 36 | ) 37 | def test_private_key_3_pkcs12(test_input, expected): 38 | assert secrets_filename.detect_match_against_filename(test_input) == expected 39 | 40 | 41 | # filename_private_key_5 42 | 43 | 44 | @pytest.mark.parametrize( 45 | "test_input,expected", 46 | [("fake.pem", r"\.pem$"), ("pem.txt", None), ("fake.pem.txt", None)], 47 | ) 48 | def test_private_key_5_pem(test_input, expected): 49 | assert secrets_filename.detect_match_against_filename(test_input) == expected 50 | 51 | 52 | # filename_private_key_7 53 | 54 | 55 | @pytest.mark.parametrize( 56 | "test_input,expected", 57 | [("fake_rsa", r"_rsa$"), ("rsa.txt", None), ("fake.rsa.pub", None)], 58 | ) 59 | def test_private_key_7_rsa(test_input, expected): 60 | assert secrets_filename.detect_match_against_filename(test_input) == expected 61 | 62 | 63 | # filename_private_key_8 64 | 65 | 66 | @pytest.mark.parametrize( 67 | "test_input,expected", 68 | [("fake_dsa", r"_dsa$"), ("dsa.txt", None), ("fake.dsa.pub", None)], 69 | ) 70 | def test_private_key_8_dsa(test_input, expected): 71 | assert secrets_filename.detect_match_against_filename(test_input) == expected 72 | 73 | 74 | # filename_private_key_9 75 | 76 | 77 | @pytest.mark.parametrize( 78 | "test_input,expected", 79 | [ 80 | ("fake_ed25519", r"_ed25519$"), 81 | ("_ed25519.txt", None), 82 | ("fake_ed25519.pub", None), 83 | ], 84 | ) 85 | def test_private_key_9_ed25519(test_input, expected): 86 | assert secrets_filename.detect_match_against_filename(test_input) == expected 87 | 88 | 89 | # filename_private_key_10 90 | 91 | 92 | @pytest.mark.parametrize( 93 | "test_input,expected", 94 | [("fake_ecdsa", r"_ecdsa$"), ("_ecdsa.txt", None), ("fake_ecdsa.pub", None)], 95 | ) 96 | def test_private_key_9_ecdsa(test_input, expected): 97 | assert secrets_filename.detect_match_against_filename(test_input) == expected 98 | 99 | 100 | # filename_private_key_11 101 | 102 | 103 | @pytest.mark.parametrize( 104 | "test_input,expected", 105 | [("fake.jks", r"\.jks$"), ("jks.txt", None), ("fake.jks.txt", None)], 106 | ) 107 | def test_private_key_11_jks(test_input, expected): 108 | assert secrets_filename.detect_match_against_filename(test_input) == expected 109 | 110 | 111 | # shell_1 112 | 113 | 114 | @pytest.mark.parametrize( 115 | "test_input,expected", 116 | [ 117 | (".bashrc", r"^\.?(bash|zsh)?rc$"), 118 | (".zshrc", r"^\.?(bash|zsh)?rc$"), 119 | (".bashrc.txt", None), 120 | (".zshrc.txt", None), 121 | ], 122 | ) 123 | def test_shell_1_rc_config___REVIEW(test_input, expected): 124 | assert secrets_filename.detect_match_against_filename(test_input) == expected 125 | 126 | 127 | # shell_2 128 | 129 | 130 | @pytest.mark.parametrize( 131 | "test_input,expected", 132 | [ 133 | (".bash_profile", r"^\.?(bash|zsh)_profile$"), 134 | (".zsh_profile", r"^\.?(bash|zsh)_profile$"), 135 | (".bash_profile.txt", None), 136 | (".zshrc.txt", None), 137 | ], 138 | ) 139 | def test_shell_2_profile_config___REVIEW(test_input, expected): 140 | assert secrets_filename.detect_match_against_filename(test_input) == expected 141 | 142 | 143 | # shell_3 144 | 145 | 146 | @pytest.mark.parametrize( 147 | "test_input,expected", 148 | [ 149 | (".bash_aliases", r"^\.?(bash|zsh)_aliases$"), 150 | (".zsh_aliases", r"^\.?(bash|zsh)_aliases$"), 151 | (".bash_aliases.txt", None), 152 | (".zsh_aliases.txt", None), 153 | ], 154 | ) 155 | def test_shell_3_aliases_config___REVIEW(test_input, expected): 156 | assert secrets_filename.detect_match_against_filename(test_input) == expected 157 | 158 | 159 | # credential_1 160 | 161 | 162 | @pytest.mark.parametrize( 163 | "test_input,expected", 164 | [ 165 | (".credential", r"^\.credential(s)?$"), 166 | (".credentials", r"^\.credential(s)?$"), 167 | ("credentials.txt", None), 168 | ("fake.credential.txt", None), 169 | ], 170 | ) 171 | def test_credential_1_credential_credentials(test_input, expected): 172 | assert secrets_filename.detect_match_against_filename(test_input) == expected 173 | 174 | 175 | # credential_2 176 | 177 | 178 | @pytest.mark.parametrize( 179 | "test_input,expected", 180 | [ 181 | (".githubenterprise", r"^\.githubenterprise$"), 182 | ("githubenterprise.txt", None), 183 | ("fake.githubenterprise.txt", None), 184 | ], 185 | ) 186 | def test_credential_2_githubenterprise(test_input, expected): 187 | assert secrets_filename.detect_match_against_filename(test_input) == expected 188 | 189 | 190 | # credential_3 191 | 192 | 193 | @pytest.mark.parametrize( 194 | "test_input,expected", 195 | [ 196 | (".keychain", r"^\.*keychain$"), 197 | ("keychain.txt", None), 198 | ("fake.keychain.txt", None), 199 | ], 200 | ) 201 | def test_credential_3_keychain__REVIEW(test_input, expected): 202 | assert secrets_filename.detect_match_against_filename(test_input) == expected 203 | 204 | 205 | # credential_4 206 | 207 | 208 | @pytest.mark.parametrize( 209 | "test_input,expected", 210 | [ 211 | ("keystore", r"^key(store|ring)$"), 212 | ("keyring", r"^key(store|ring)$"), 213 | ("keyring.txt", None), 214 | ("keystore.txt", None), 215 | ("fake.keyring.txt", None), 216 | ("fake.keystore.txt", None), 217 | ], 218 | ) 219 | def test_credential_4_keystore_keyring(test_input, expected): 220 | assert secrets_filename.detect_match_against_filename(test_input) == expected 221 | 222 | 223 | def test_main_pos(): 224 | assert secrets_filename.main(["lol.txt"]) == 0 225 | 226 | 227 | def test_main_neg(): 228 | assert secrets_filename.main(["lol.pem"]) != 0 229 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # security-git-hooks 2 | 3 | The purpose of these pre-commit Git hooks is to check file types and content against pre-defined rules in order to identify potentially sensitive information prior to commit. Managing sensitive content before it is committed to GitHub helps maintain our overall security posture, and also helps prevent the Leak Detection Service (LDS) triggering an alert, thus reducing the potential for secrets requiring manual removal from Git histories and for keys which would then require cycling. 4 | 5 | *NOTE:* These hooks do not check for the presence of a `repository.yaml` file and have no relationship with the LDS exemptions defined there. Our pre-commit hooks use inline exclusions. Simply add a comment on the line above the content containing the string `LDS ignore`, and the hook will skip the line. **NB** - This functionality will soon be extended to the LDS itself - see [this ticket](https://jira.tools.tax.service.gov.uk/browse/BDOG-192) for details, however please be mindful that your `repository.yaml` exclusions will need to be maintained for now. 6 | 7 | ## Installation 8 | 9 | `pip3 install pre-commit` 10 | 11 | 12 | ## Getting started 13 | 14 | * Navigate to the root directory of a repository you wish to run hooks in. 15 | * Run the command: `pre-commit install`. 16 | * If there is no `.pre-commit-config.yaml` file present, create one. This is how the pre-commit framework installs and runs selected hooks. This file must be included in any repository utilising the framework. The following content, pasted directly into your `.pre-commit-config.yaml` file, will install all hooks present in this repository. Alternatively, if you have chosen to clone this repository, you can directly copy the `.pre-commit-config.yaml` contained here into whichever directory you're running hooks in. 17 | 18 | ``` 19 | repos: 20 | - repo: https://github.com/hmrc/security-git-hooks 21 | rev: v1.0.0-beta9 22 | hooks: 23 | - id: secrets_filecontent 24 | name: Checking staged files for sensitive content 25 | exclude: ".tar$|.gz$|.jar$|.7z$|.rar$|.bz2$|.zip$|.gzip$|.war$|.ear$|.xlsx$|.xls$| 26 | |.docx$|.doc$|.pptx$|.pdf$|.jpg$|.png$|.jpeg$|.tif$|.tiff$|.gif$|.bmp$|.webp$|.svg$|.ico$|.psd$|.exe$|.dll$|.dmg$|.de$|.rpm$" 27 | - id: secrets_filename 28 | name: Checking staged files for sensitive file types 29 | exclude: ".tar$|.gz$|.jar$|.7z$|.rar$|.bz2$|.zip$|.gzip$|.war$|.ear$|.xlsx$|.xls$| 30 | |.docx$|.doc$|.pptx$|.pdf$|.jpg$|.png$|.jpeg$|.tif$|.tiff$|.gif$|.bmp$|.webp$|.svg$|.ico$|.psd$|.exe$|.dll$|.dmg$|.de$|.rpm$" 31 | - id: hooks_version_check 32 | name: Checking local hooks against latest release 33 | verbose: true 34 | 35 | ``` 36 | 37 | ### Quick test 38 | In order to quickly check if everything is working as expected, test with: 39 | 40 | * Change into your selected repository. 41 | * Create a dummy file to test the file type check: `touch fake.key.pem` 42 | * Create a dummy file to test the file content check: `echo aws_secret_access_key = 1ye+VarkHMg7o6MNjwWIqOYICe03lfA+KPPAmeaY > fake.aws.file` 43 | * Test with: `git add -A && git commit -m 'testing pre-commits'` 44 | 45 | You should see the following output: 46 | ``` 47 | $ git add -A && git commit -m 'testing pre-commits' 48 | Check file content for potential secrets.................................Failed 49 | hookid: secrets_filecontent 50 | 51 | fake.key.pem may contain sensitive information due to the file type 52 | 53 | Check filenames for potential secrets....................................Failed 54 | hookid: secrets_filename 55 | 56 | Potentially sensitive string matching rule: aws_secret_access_key found on line 1 of fake.aws.file 57 | 58 | ``` 59 | 60 | ### Definitions 61 | * `repo` - Points to the repository containing the hook(s). Can be set to `local` for running/testing your own hooks, although additional information (which wouold usually be included in the `.pre-commit-hooks.yaml` is required if this is the case). 62 | 63 | * `hooks` - Declares the `id`, and optionally the local `name` of the hook. There are further options to include `language`, `entrypoint`, and a list of any files to `exclude`. Although this information should not be included generally as it can be found in the `.pre-commit-hooks.yaml` file, the exclusions have been provided as part of the configuration file to provide additional user choice in this case. The default exclusions here are in line with the filetypes excluded by the Leak Detection Service itself, although any changes made to your exclusion list will not affect the Leak Detection Service. 64 | 65 | See [here](https://pre-commit.com/#plugins) for more information on the `.pre-commit-config.yaml` document format. 66 | 67 | *NOTE:* Exclusions must be provided as [Python compliant regex](https://www.debuggex.com/cheatsheet/regex/python) 68 | 69 | ## Usage 70 | 71 | If you wish to run hooks without committing, pre-commit can be used as a general scanning tool with the `pre-commit run --all-files` or `pre-commit run ` commands, providing Git is initialised and pre-commit installed in the relevant repository. 72 | 73 | You can forgo the pre-commit hooks entirely by use of the `--no-verify` flag, although due to the relationship between the `secrets-filename` and `secrets-filecontent` hooks and the Leak Detection Service, an LDS alert will be triggered by the commit for any files not on the exemption list contained within your `repository.yaml` file. 74 | 75 | You can update hooks to point directly at the latest tagged version of a hook by using `pre-commit autoupdate`, or alternatively, `pre-commit autoupdate --bleeding-edge` will point at the latest version of master. 76 | 77 | ### Installing other hooks or writing your own 78 | 79 | The developers of the pre-commit framework have written various hooks, which can be found [here](https://github.com/hmrc/pre-commit-hooks) along with additional information about the framework. If you see a hook you would like to use which is external to the HMRC organisation, please check the license and post in the `#community-security` slack channel so we can mirror it if appropriate. Additionally, if you have written a hook and wish to include it in this repository, please submit a pull request with an update to the `README` and `.pre-commit-config.yaml` files. The decision to host hooks exclusively in our own repository was taken as a security measure, **as the hook mechanism allows for any malicious code to be executed, should it be checked in to the repository which holds the hook**. This is a set recommendation to all users in the HMRC organisation. 80 | 81 | Pre-commit hooks themselves can be written in any language, however for a list of languages currently supported by the framework, please see [here](https://pre-commit.com/#new-hooks) 82 | 83 | ## Hooks in this respository 84 | 85 | `secrets-filename` - Checks against the LDS file extension ruleset as defined [here](https://github.com/hmrc/app-config-base/blob/master/leak-detection.conf#L142) 86 | 87 | `secrets-filecontent` - Checks against the LDS file content ruleset as defined [here](https://github.com/hmrc/app-config-base/blob/master/leak-detection.conf#L92) 88 | 89 | `hooks-version-check` - Checks the tag from your `.pre-commit-config.yaml` file against the latest tagged release in the repository. This is an information only hook, and will provide output but always pass. 90 | 91 | You can test the hooks by cloning this repository and running `tox` in the root directory. 92 | -------------------------------------------------------------------------------- /tests/test_secrets_filecontent.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | 4 | from security_git_hooks import secrets_filecontent 5 | 6 | """All comments at beginning of test sets 7 | correspond to id of leak detection rules per 8 | https://github.com/hmrc/app-config-base/blob/master/leak-detection.conf""" 9 | 10 | my_path = os.path.abspath(os.path.dirname(__file__)) 11 | 12 | minimal = os.path.join(my_path, "resources/minimal.yaml") 13 | 14 | # aws_secret_access_key 15 | 16 | 17 | @pytest.mark.parametrize( 18 | "line, file, expected", 19 | [ 20 | ( 21 | "aws_secret_access_key:H5xnFhnR3H/o6nrcfoMLR9VfOlfOY17pa/+PchnA", 22 | "afile.py", 23 | "aws_secret_access_key", 24 | ), 25 | ( 26 | "aws_secret_access_key : H5xnFhnR3H/o6nrcfoMLR9VfOlfOY17pa/+PchnA", 27 | "afile.txt", 28 | "aws_secret_access_key", 29 | ), 30 | ( 31 | "aws_secret_access_key = H5xnFhnR3H/o6nrcfoMLR9VfOlfOY17pa/+PchnA", 32 | "afile.md", 33 | "aws_secret_access_key", 34 | ), 35 | ( 36 | "aws_secret_access_key=H5xnFhnR3H/o6nrcfoMLR9VfOlfOY17pa/+PchnA", 37 | "afile.py", 38 | "aws_secret_access_key", 39 | ), 40 | ("H5xnFhnR3H/o6nrcfoMLR9VfOlfOY17pa/+PchnA", "afile.py", None), 41 | ( 42 | "aws_secret_access_key = X12345MEuati12345ed+voyz/UeJ560Bu0cgJcaac", 43 | "afile.py", 44 | None, 45 | ), 46 | ( 47 | "aws_secret_access_key = X12345MEuati12345ed+voyz/UeJ560cgJcaac", 48 | "afile.py", 49 | None, 50 | ), 51 | ( 52 | "aws_secret_access_key = H5xnFhnR3H/o6nrcfoMLR9VfOl*OY17pa/+PchnA", 53 | "afile.py", 54 | None, 55 | ), 56 | ], 57 | ) 58 | def test_aws_secret_access_key(line, file, expected): 59 | assert secrets_filecontent.detect_secret_in_line(line, file) == expected 60 | 61 | 62 | # cert_1 63 | 64 | 65 | @pytest.mark.parametrize( 66 | "line, file, expected", 67 | [ 68 | ("-----BEGIN RSA PRIVATE KEY-----", "filename.txt", "cert_1"), 69 | ("-----END RSA PRIVATE KEY-----", "filename.md", "cert_1"), 70 | ("-----BEGIN DSA PRIVATE KEY-----", "filename.py", "cert_1"), 71 | ("-----END DSA PRIVATE KEY-----", "filename.js", "cert_1"), 72 | ("-----BEGIN RSA PRIVATE KEY-----", "kitchen.yml", None), 73 | ("-----BEGIN RSA PRIVATE KEY-----", "kitchen.yaml", None), 74 | ( 75 | "-----BEGIN RSA PRIVATE KEY----- \n keycontent \n -----END RSA PRIVATE KEY-----", 76 | "filename.txt", 77 | "cert_1", 78 | ), 79 | ( 80 | "-----BEGIN DSA PRIVATE KEY----- \n keycontent \n -----END RSA PRIVATE KEY-----", 81 | "filename.txt", 82 | "cert_1", 83 | ), 84 | ("ssh-rsa public key content", "filename.txt", None), 85 | ("ssh-dsa public key content", "filename.txt", None), 86 | ], 87 | ) 88 | def test_cert_1(line, file, expected): 89 | assert secrets_filecontent.detect_secret_in_line(line, file) == expected 90 | 91 | 92 | # application_secret 93 | 94 | 95 | @pytest.mark.parametrize( 96 | "line, file, expected", 97 | [ 98 | ('application.secret:"helloiamasecret"', "filename.txt", "application_secret"), 99 | ('application.secret="helloiamasecret"', "filename.txt", "application_secret"), 100 | ( 101 | 'application.secret : "helloiamasecret"', 102 | "filename.txt", 103 | "application_secret", 104 | ), 105 | ( 106 | 'application.secret = "helloiamasecret"', 107 | "filename.txt", 108 | "application_secret", 109 | ), 110 | ('application.secret = "helloiamasecret"', "filename.scala", None), 111 | ('application.secret = "helloiamasecret"', " /conf/application.conf", None), 112 | ("application secret", "filename.txt", None), 113 | ("application.secret", "filename.txt", None), 114 | ("application_secret", "filename.txt", None), 115 | ], 116 | ) 117 | def test_application_secret(line, file, expected): 118 | assert secrets_filecontent.detect_secret_in_line(line, file) == expected 119 | 120 | 121 | # play_crypto_secret 122 | 123 | 124 | @pytest.mark.parametrize( 125 | "line, file, expected", 126 | [ 127 | ( 128 | 'play.crypto.secret:"zLiPolptzA5HLnRG9XAF6hQGP4QGQvEd82W27dzsI8HpFQRToMD7m8f78LQ0Ur7k"', 129 | "filename.txt", 130 | "play_crypto_secret", 131 | ), 132 | ( 133 | 'play.crypto.secret="zLiPolptzA5HLnRG9XAF6hQGP4QGQvEd82W27dzsI8HpFQRToMD7m8f78LQ0Ur7k"', 134 | "filename.txt", 135 | "play_crypto_secret", 136 | ), 137 | ( 138 | 'play.crypto.secret : "zLiPolptzA5HLnRG9XAF6hQGP4QGQvEd82W27dzsI8HpFQRToMD7m8f78LQ0Ur7k"', 139 | "filename.txt", 140 | "play_crypto_secret", 141 | ), 142 | ( 143 | 'play.crypto.secret = "zLiPolptzA5HLnRG9XAF6hQGP4QGQvEd82W27dzsI8HpFQRToMD7m8f78LQ0Ur7k"', 144 | "filename.txt", 145 | "play_crypto_secret", 146 | ), 147 | ('play.crypto.secret = "changeme"', "filename.txt", "play_crypto_secret"), 148 | ('play.crypto.secret = "changeme"', "filename.scala", None), 149 | ('play.crypto.secret = "changeme"', " /conf/application.conf", None), 150 | ("play.crypto.secret:ENC[GPPencrypted", "filename.txt", None), 151 | ("play.crypto.secret=ENC[GPPencrypted", "filename.txt", None), 152 | ("play.crypto.secret : ENC[GPPencrypted", "filename.txt", None), 153 | ("play.crypto.secret = ENC[GPPencrypted", "filename.txt", None), 154 | ("play crypto secret.", "filename.txt", None), 155 | ], 156 | ) 157 | def test_play_crypto_secret(line, file, expected): 158 | assert secrets_filecontent.detect_secret_in_line(line, file) == expected 159 | 160 | 161 | # cookie_deviceId_secret 162 | 163 | 164 | @pytest.mark.parametrize( 165 | "line, file, expected", 166 | [ 167 | ( 168 | 'cookie.deviceId.secret:"helloiamasecret"', 169 | "filename.txt", 170 | "cookie_deviceId_secret", 171 | ), 172 | ( 173 | 'cookie.deviceId.secret="helloiamasecret"', 174 | "filename.txt", 175 | "cookie_deviceId_secret", 176 | ), 177 | ( 178 | 'cookie.deviceId.secret : "helloiamasecret"', 179 | "filename.txt", 180 | "cookie_deviceId_secret", 181 | ), 182 | ( 183 | 'cookie.deviceId.secret = "helloiamasecret"', 184 | "filename.txt", 185 | "cookie_deviceId_secret", 186 | ), 187 | ("cookie.deviceId.secret:ENC[GPG", "filename.txt", None), 188 | ("cookie.deviceId.secret=ENC[GPG", "filename.txt", None), 189 | ("cookie.deviceId.secret : ENC[GPG", "filename.txt", None), 190 | ("cookie.deviceId.secret = ENC[GPG", "filename.txt", None), 191 | ("cookie deviceId secret.", "filename.txt", None), 192 | ], 193 | ) 194 | def test_cookie_deviceId_secret(line, file, expected): 195 | assert secrets_filecontent.detect_secret_in_line(line, file) == expected 196 | 197 | 198 | # sso_encryption_key 199 | 200 | 201 | @pytest.mark.parametrize( 202 | "line, file, expected", 203 | [ 204 | ( 205 | 'sso.encryption.key:"P5xsJ9Nt+quxDKzB4DeLfw=="', 206 | "filename.txt", 207 | "sso_encryption_key", 208 | ), 209 | ( 210 | "sso.encryption.key=P5xsJ9Nt+quxDKzB4DeLfw==", 211 | "filename.txt", 212 | "sso_encryption_key", 213 | ), 214 | ( 215 | "sso.encryption.key : P5xsJ9Nt+quxDKzB4DeLfw==", 216 | "filename.txt", 217 | "sso_encryption_key", 218 | ), 219 | ( 220 | "sso.encryption.key = P5xsJ9Nt+quxDKzB4DeLfw==", 221 | "filename.txt", 222 | "sso_encryption_key", 223 | ), 224 | ("sso.encryption.key:ENC[GPG", "filename.txt", None), 225 | ("sso.encryption.key=ENC[GPG", "filename.txt", None), 226 | ("sso.encryption.key : ENC[GPG", "filename.txt", None), 227 | ("sso.encryption.key = ENC[GPG", "filename.txt", None), 228 | ("sso encryption key.", "filename.txt", None), 229 | ], 230 | ) 231 | def test_sso_encrpytion_key(line, file, expected): 232 | assert secrets_filecontent.detect_secret_in_line(line, file) == expected 233 | 234 | 235 | def test_main(): 236 | assert secrets_filecontent.main([minimal]) == 0 237 | --------------------------------------------------------------------------------