├── .gitattributes ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── test.yml ├── .pre-commit-hooks.yaml ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── Makefile ├── NOTICE.txt ├── README.rst ├── git-secrets ├── git-secrets.1 ├── install.ps1 └── test ├── bats ├── LICENSE ├── bin │ └── bats └── libexec │ ├── bats │ ├── bats-exec-suite │ ├── bats-exec-test │ ├── bats-format-tap-stream │ └── bats-preprocess ├── commit-msg.bats ├── git-secrets.bats ├── pre-commit.bats ├── prepare-commit-msg.bats └── test_helper.bash /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Force the bash scripts to be checked out with LF line endings. 5 | git-secrets text eol=lf 6 | git-secrets.1 text eol=lf 7 | test/bats/bin/* text eol=lf 8 | test/bats/libexec/* text eol=lf 9 | *.bats text eol=lf 10 | *.bash text eol=lf -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "test" 3 | 4 | on: 5 | push: 6 | branches: 7 | - 'master' 8 | pull_request: {} 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: make test 16 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: git-secrets 2 | name: Git Secrets 3 | description: git-secrets scans commits, commit messages, and --no-ff merges to prevent adding secrets into your git repositories. 4 | entry: 'git-secrets --pre_commit_hook' 5 | language: script 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: bash 2 | 3 | before_install: 4 | - git config --global user.email "you@example.com" 5 | - git config --global user.name "Your Name" 6 | 7 | script: 8 | - make test 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 1.3.0 - 2019-02-10 4 | 5 | * Empty provider output is now excluded 6 | (https://github.com/awslabs/git-secrets/issues/34) 7 | * Spaces are now supported in git exec path, making more Windows 8 | paths execute properly. 9 | * Patterns with newlines and carriage returns are now loaded properly. 10 | * Patterns that contain only "\n" are now ignored. 11 | * Various Bash 4 fixes (https://github.com/awslabs/git-secrets/issues/66). 12 | * Make IAM key scanning much more targeted. 13 | 14 | ## 1.2.1 - 2016-06-27 15 | 16 | * Fixed an issue where secret provider commands were causing "command not 17 | found" errors due to a previously set IFS variable. 18 | https://github.com/awslabs/git-secrets/pull/30 19 | 20 | ## 1.2.0 - 2016-05-23 21 | 22 | * Fixed an issue where spaces files with spaces in their names were not being 23 | properly scanned in the pre-commit hook. 24 | * Now ignoring empty lines and comments (e.g., `#`) in the .gitallowed file. 25 | * Fixed an issue where numbers were being compared to strings causing failures 26 | on some platforms. 27 | 28 | ## 1.1.0 - 2016-04-06 29 | 30 | * Bug fix: the pre-commit hook previously only scanned the working directory 31 | rather than staged files. This release updates the pre-commit hook to instead 32 | scan staged files so that git-secrets will detect violations if the working 33 | directory drifts from the staging directory. 34 | * Added the `--scan-history` subcommand so that you can scan your entire 35 | git history for violations. 36 | * Added the ability to filter false positives by using a .gitallowed file. 37 | * Added support for `--cached`, `--no-index`, and `--untracked` to the `--scan` 38 | subcommand. 39 | 40 | ## 1.0.1 - 2016-01-11 41 | 42 | * Now works correctly with filenames in a repository that contain spaces when 43 | executing `git secrets --scan` with no provided filename (via `git grep`). 44 | * Now works with git repositories with hundreds of thousands of files when 45 | using `git secrets --scan` with no provided filename (via `git grep`). 46 | 47 | ## 1.0.0 - 2015-12-10 48 | 49 | * Initial release of ``git-secrets``. 50 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/awslabs/git-secrets/issues), or [recently closed](https://github.com/awslabs/git-secrets/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/git-secrets/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/awslabs/git-secrets/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | Note: Other license terms may apply to certain, identified software files 205 | contained within or distributed with the accompanying software if such terms 206 | are included in the directory containing the accompanying software. Such other 207 | license terms will then apply in lieu of the terms of the software license 208 | above. 209 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX ?= /usr/local 2 | MANPREFIX ?= "${PREFIX}/share/man/man1" 3 | 4 | help: 5 | @echo "Please use \`make ' where is one of" 6 | @echo " test to perform unit tests." 7 | @echo " man to build the man file from README.rst" 8 | @echo " install to install. Use PREFIX and MANPREFIX to customize." 9 | 10 | # We use bats for testing: https://github.com/sstephenson/bats 11 | test: 12 | LANG=C test/bats/bin/bats test/ 13 | 14 | # The man page is completely derived from README.rst. Edits to 15 | # README.rst require a rebuild of the man page. 16 | man: 17 | rst2man.py README.rst > git-secrets.1 18 | 19 | install: 20 | @mkdir -p ${DESTDIR}${MANPREFIX} 21 | @mkdir -p ${DESTDIR}${PREFIX}/bin 22 | @cp -f git-secrets ${DESTDIR}${PREFIX}/bin 23 | @cp -f git-secrets.1 ${DESTDIR}${MANPREFIX} 24 | 25 | .PHONY: help test man 26 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | git-secrets 2 | Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | bats 5 | This product bundles bats, which is available under a "MIT" license. 6 | For details, see test/bats. 7 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | git-secrets 3 | =========== 4 | 5 | ------------------------------------------------------------------------------------------- 6 | Prevents you from committing passwords and other sensitive information to a git repository. 7 | ------------------------------------------------------------------------------------------- 8 | 9 | .. contents:: :depth: 2 10 | 11 | Synopsis 12 | -------- 13 | 14 | :: 15 | 16 | git secrets --scan [-r|--recursive] [--cached] [--no-index] [--untracked] [...] 17 | git secrets --scan-history 18 | git secrets --install [-f|--force] [] 19 | git secrets --list [--global] 20 | git secrets --add [-a|--allowed] [-l|--literal] [--global] 21 | git secrets --add-provider [--global] [arguments...] 22 | git secrets --register-aws [--global] 23 | git secrets --aws-provider [] 24 | 25 | 26 | Description 27 | ----------- 28 | 29 | ``git-secrets`` scans commits, commit messages, and ``--no-ff`` merges to 30 | prevent adding secrets into your git repositories. If a commit, 31 | commit message, or any commit in a ``--no-ff`` merge history matches one of 32 | your configured prohibited regular expression patterns, then the commit is 33 | rejected. 34 | 35 | 36 | Installing git-secrets 37 | ---------------------- 38 | 39 | ``git-secrets`` must be placed somewhere in your PATH so that it is picked up 40 | by ``git`` when running ``git secrets``. 41 | 42 | \*nix (Linux/macOS) 43 | ~~~~~~~~~~~~~~~~~~~ 44 | 45 | You can use the ``install`` target of the provided Makefile to install ``git secrets`` and the man page. 46 | You can customize the install path using the PREFIX and MANPREFIX variables. 47 | 48 | :: 49 | 50 | make install 51 | 52 | Windows 53 | ~~~~~~~ 54 | 55 | Run the provided ``install.ps1`` powershell script. This will copy the needed files 56 | to an installation directory (``%USERPROFILE%/.git-secrets`` by default) and add 57 | the directory to the current user ``PATH``. 58 | 59 | :: 60 | 61 | PS > ./install.ps1 62 | 63 | Homebrew (for macOS users) 64 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 65 | 66 | :: 67 | 68 | brew install git-secrets 69 | 70 | .. warning:: 71 | 72 | **You're not done yet! You MUST install the git hooks for every repo that 73 | you wish to use with** ``git secrets --install``. 74 | 75 | Here's a quick example of how to ensure a git repository is scanned for secrets 76 | on each commit:: 77 | 78 | cd /path/to/my/repo 79 | git secrets --install 80 | git secrets --register-aws 81 | 82 | 83 | Advanced configuration 84 | ---------------------- 85 | 86 | Add a configuration template if you want to add hooks to all repositories you 87 | initialize or clone in the future. 88 | 89 | :: 90 | 91 | git secrets --register-aws --global 92 | 93 | 94 | Add hooks to all your local repositories. 95 | 96 | :: 97 | 98 | git secrets --install ~/.git-templates/git-secrets 99 | git config --global init.templateDir ~/.git-templates/git-secrets 100 | 101 | 102 | Add custom providers to scan for security credentials. 103 | 104 | :: 105 | 106 | git secrets --add-provider -- cat /path/to/secret/file/patterns 107 | 108 | 109 | Before making public a repository 110 | --------------------------------- 111 | 112 | With git-secrets is also possible to scan a repository including all revisions: 113 | 114 | :: 115 | 116 | git secrets --scan-history 117 | 118 | 119 | Options 120 | ------- 121 | 122 | Operation Modes 123 | ~~~~~~~~~~~~~~~ 124 | 125 | Each of these options must appear first on the command line. 126 | 127 | ``--install`` 128 | Installs git hooks for a repository. Once the hooks are installed for a git 129 | repository, commits and non-fast-forward merges for that repository will be prevented 130 | from committing secrets. 131 | 132 | ``--scan`` 133 | Scans one or more files for secrets. When a file contains a secret, the 134 | matched text from the file being scanned will be written to stdout and the 135 | script will exit with a non-zero status. Each matched line will be written with 136 | the name of the file that matched, a colon, the line number that matched, 137 | a colon, and then the line of text that matched. If no files are provided, 138 | all files returned by ``git ls-files`` are scanned. 139 | 140 | ``--scan-history`` 141 | Scans repository including all revisions. When a file contains a secret, the 142 | matched text from the file being scanned will be written to stdout and the 143 | script will exit with a non-zero status. Each matched line will be written with 144 | the name of the file that matched, a colon, the line number that matched, 145 | a colon, and then the line of text that matched. 146 | 147 | ``--list`` 148 | Lists the ``git-secrets`` configuration for the current repo or in the global 149 | git config. 150 | 151 | ``--add`` 152 | Adds a prohibited or allowed pattern. 153 | 154 | ``--add-provider`` 155 | Registers a secret provider. Secret providers are executables that when 156 | invoked output prohibited patterns that ``git-secrets`` should treat as 157 | prohibited. 158 | 159 | ``--register-aws`` 160 | Adds common AWS patterns to the git config and ensures that keys present 161 | in ``~/.aws/credentials`` are not found in any commit. The following 162 | checks are added: 163 | 164 | - AWS Access Key IDs via ``(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}`` 165 | - AWS Secret Access Key assignments via ":" or "=" surrounded by optional 166 | quotes 167 | - AWS account ID assignments via ":" or "=" surrounded by optional quotes 168 | - Allowed patterns for example AWS keys (``AKIAIOSFODNN7EXAMPLE`` and 169 | ``wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY``) 170 | - Known credentials from ``~/.aws/credentials`` 171 | 172 | .. note:: 173 | 174 | While the patterns registered by this command should catch most 175 | instances of AWS credentials, these patterns are **not** guaranteed to 176 | catch them **all**. ``git-secrets`` should be used as an extra means of 177 | insurance -- you still need to do your due diligence to ensure that you 178 | do not commit credentials to a repository. 179 | 180 | ``--aws-provider`` 181 | Secret provider that outputs credentials found in an INI file. You can 182 | optionally provide the path to an INI file. 183 | 184 | 185 | Options for ``--install`` 186 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 187 | 188 | ``-f, --force`` 189 | Overwrites existing hooks if present. 190 | 191 | ```` 192 | When provided, installs git hooks to the given directory. The current 193 | directory is assumed if ```` is not provided. 194 | 195 | If the provided ```` is not in a git repository, the 196 | directory will be created and hooks will be placed in 197 | ``/hooks``. This can be useful for creating git template 198 | directories using with ``git init --template ``. 199 | 200 | You can run ``git init`` on a repository that has already been initialized. 201 | From the `git init documentation `_: 202 | 203 | From the git documentation: Running ``git init`` in an existing repository 204 | is safe. It will not overwrite things that are already there. The 205 | primary reason for rerunning ``git init`` is to pick up newly added 206 | templates (or to move the repository to another place if 207 | ``--separate-git-dir`` is given). 208 | 209 | The following git hooks are installed: 210 | 211 | 1. ``pre-commit``: Used to check if any of the files changed in the commit 212 | use prohibited patterns. 213 | 2. ``commit-msg``: Used to determine if a commit message contains a 214 | prohibited patterns. 215 | 3. ``prepare-commit-msg``: Used to determine if a merge commit will 216 | introduce a history that contains a prohibited pattern at any point. 217 | Please note that this hook is only invoked for non fast-forward merges. 218 | 219 | .. note:: 220 | 221 | Git only allows a single script to be executed per hook. If the 222 | repository contains Debian-style subdirectories like ``pre-commit.d`` 223 | and ``commit-msg.d``, then the git hooks will be installed into these 224 | directories, which assumes that you've configured the corresponding 225 | hooks to execute all of the scripts found in these directories. If 226 | these git subdirectories are not present, then the git hooks will be 227 | installed to the git repo's ``.git/hooks`` directory. 228 | 229 | 230 | Examples 231 | ^^^^^^^^ 232 | 233 | Install git hooks to the current directory:: 234 | 235 | cd /path/to/my/repository 236 | git secrets --install 237 | 238 | Install git hooks to a repository other than the current directory:: 239 | 240 | git secrets --install /path/to/my/repository 241 | 242 | Create a git template that has ``git-secrets`` installed, and then copy that 243 | template into a git repository:: 244 | 245 | git secrets --install ~/.git-templates/git-secrets 246 | git init --template ~/.git-templates/git-secrets 247 | 248 | Overwrite existing hooks if present:: 249 | 250 | git secrets --install -f 251 | 252 | 253 | Options for ``--scan`` 254 | ~~~~~~~~~~~~~~~~~~~~~~ 255 | 256 | ``-r, --recursive`` 257 | Scans the given files recursively. If a directory is encountered, the 258 | directory will be scanned. If ``-r`` is not provided, directories will be 259 | ignored. 260 | 261 | ``-r`` cannot be used alongside ``--cached``, ``--no-index``, or 262 | ``--untracked``. 263 | 264 | ``--cached`` 265 | Searches blobs registered in the index file. 266 | 267 | ``--no-index`` 268 | Searches files in the current directory that is not managed by git. 269 | 270 | ``--untracked`` 271 | In addition to searching in the tracked files in the working tree, 272 | ``--scan`` also in untracked files. 273 | 274 | ``...`` 275 | The path to one or more files on disk to scan for secrets. 276 | 277 | If no files are provided, all files returned by ``git ls-files`` are 278 | scanned. 279 | 280 | 281 | Examples 282 | ^^^^^^^^ 283 | 284 | Scan all files in the repo:: 285 | 286 | git secrets --scan 287 | 288 | Scans a single file for secrets:: 289 | 290 | git secrets --scan /path/to/file 291 | 292 | Scans a directory recursively for secrets:: 293 | 294 | git secrets --scan -r /path/to/directory 295 | 296 | Scans multiple files for secrets:: 297 | 298 | git secrets --scan /path/to/file /path/to/other/file 299 | 300 | You can scan by globbing:: 301 | 302 | git secrets --scan /path/to/directory/* 303 | 304 | Scan from stdin:: 305 | 306 | echo 'hello!' | git secrets --scan - 307 | 308 | 309 | Options for ``--list`` 310 | ~~~~~~~~~~~~~~~~~~~~~~ 311 | 312 | ``--global`` 313 | Lists only git-secrets configuration in the global git config. 314 | 315 | 316 | Options for ``--add`` 317 | ~~~~~~~~~~~~~~~~~~~~~ 318 | 319 | ``--global`` 320 | Adds patterns to the global git config 321 | 322 | ``-l, --literal`` 323 | Escapes special regular expression characters in the provided pattern so 324 | that the pattern is searched for literally. 325 | 326 | ``-a, --allowed`` 327 | Mark the pattern as allowed instead of prohibited. Allowed patterns are 328 | used to filter out false positives. 329 | 330 | ```` 331 | The regex pattern to search. 332 | 333 | 334 | Examples 335 | ^^^^^^^^ 336 | 337 | Adds a prohibited pattern to the current repo:: 338 | 339 | git secrets --add '[A-Z0-9]{20}' 340 | 341 | Adds a prohibited pattern to the global git config:: 342 | 343 | git secrets --add --global '[A-Z0-9]{20}' 344 | 345 | Adds a string that is scanned for literally (``+`` is escaped):: 346 | 347 | git secrets --add --literal 'foo+bar' 348 | 349 | Add an allowed pattern:: 350 | 351 | git secrets --add -a 'allowed pattern' 352 | 353 | 354 | Options for ``--register-aws`` 355 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 356 | 357 | ``--global`` 358 | Adds AWS specific configuration variables to the global git config. 359 | 360 | 361 | Options for ``--aws-provider`` 362 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 363 | 364 | ``[]`` 365 | If provided, specifies the custom path to an INI file to scan. If not 366 | provided, ``~/.aws/credentials`` is assumed. 367 | 368 | 369 | Options for ``--add-provider`` 370 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 371 | 372 | ``--global`` 373 | Adds the provider to the global git config. 374 | 375 | ```` 376 | Provider command to invoke. When invoked the command is expected to write 377 | prohibited patterns separated by new lines to stdout. Any extra arguments 378 | provided are passed on to the command. 379 | 380 | 381 | Examples 382 | ^^^^^^^^ 383 | 384 | Registers a secret provider with arguments:: 385 | 386 | git secrets --add-provider -- git secrets --aws-provider 387 | 388 | Cats secrets out of a file:: 389 | 390 | git secrets --add-provider -- cat /path/to/secret/file/patterns 391 | 392 | 393 | Defining prohibited patterns 394 | ---------------------------- 395 | 396 | ``egrep``-compatible regular expressions are used to determine if a commit or 397 | commit message contains any prohibited patterns. These regular expressions are 398 | defined using the ``git config`` command. It is important to note that 399 | different systems use different versions of egrep. For example, when running on 400 | macOS, you will use a different version of ``egrep`` than when running on something 401 | like Ubuntu (BSD vs GNU). 402 | 403 | You can add prohibited regular expression patterns to your git config using 404 | ``git secrets --add ``. 405 | 406 | 407 | Ignoring false positives 408 | ------------------------ 409 | 410 | Sometimes a regular expression might match false positives. For example, git 411 | commit SHAs look a lot like AWS access keys. You can specify many different 412 | regular expression patterns as false positives using the following command: 413 | 414 | :: 415 | 416 | git secrets --add --allowed 'my regex pattern' 417 | 418 | You can also add regular expressions patterns to filter false positives to a 419 | ``.gitallowed`` file located in the repository's root directory. Lines starting 420 | with ``#`` are skipped (comment line) and empty lines are also skipped. 421 | 422 | First, git-secrets will extract all lines from a file that contain a prohibited 423 | match. Included in the matched results will be the full path to the name of 424 | the file that was matched, followed by ':', followed by the line number that was 425 | matched, followed by the entire line from the file that was matched by a secret 426 | pattern. Then, if you've defined allowed regular expressions, git-secrets will 427 | check to see if all of the matched lines match at least one of your registered 428 | allowed regular expressions. If all of the lines that were flagged as secret 429 | are canceled out by an allowed match, then the subject text does not contain 430 | any secrets. If any of the matched lines are not matched by an allowed regular 431 | expression, then git-secrets will fail the commit/merge/message. 432 | 433 | .. important:: 434 | 435 | Just as it is a bad practice to add prohibited patterns that are too 436 | greedy, it is also a bad practice to add allowed patterns that are too 437 | forgiving. Be sure to test out your patterns using ad-hoc calls to 438 | ``git secrets --scan $filename`` to ensure they are working as intended. 439 | 440 | 441 | Secret providers 442 | ---------------- 443 | 444 | Sometimes you want to check for an exact pattern match against a set of known 445 | secrets. For example, you might want to ensure that no credentials present in 446 | ``~/.aws/credentials`` ever show up in a commit. In these cases, it's better to 447 | leave these secrets in one location rather than spread them out across git 448 | repositories in git configs. You can use "secret providers" to fetch these 449 | types of credentials. A secret provider is an executable that when invoked 450 | outputs prohibited patterns separated by new lines. 451 | 452 | You can add secret providers using the ``--add-provider`` command:: 453 | 454 | git secrets --add-provider -- git secrets --aws-provider 455 | 456 | Notice the use of ``--``. This ensures that any arguments associated with the 457 | provider are passed to the provider each time it is invoked when scanning 458 | for secrets. 459 | 460 | 461 | Example walkthrough 462 | ------------------- 463 | 464 | Let's take a look at an example. Given the following subject text (stored in 465 | ``/tmp/example``):: 466 | 467 | This is a test! 468 | password=ex@mplepassword 469 | password=****** 470 | More test... 471 | 472 | And the following registered patterns: 473 | 474 | :: 475 | 476 | git secrets --add 'password\s*=\s*.+' 477 | git secrets --add --allowed --literal 'ex@mplepassword' 478 | 479 | Running ``git secrets --scan /tmp/example``, the result will 480 | result in the following error output:: 481 | 482 | /tmp/example:3:password=****** 483 | 484 | [ERROR] Matched prohibited pattern 485 | 486 | Possible mitigations: 487 | - Mark false positives as allowed using: git config --add secrets.allowed ... 488 | - List your configured patterns: git config --get-all secrets.patterns 489 | - List your configured allowed patterns: git config --get-all secrets.allowed 490 | - Use --no-verify if this is a one-time false positive 491 | 492 | Breaking this down, the prohibited pattern value of ``password\s*=\s*.+`` will 493 | match the following lines:: 494 | 495 | /tmp/example:2:password=ex@mplepassword 496 | /tmp/example:3:password=****** 497 | 498 | ...But the first match will be filtered out due to the fact that it matches the 499 | allowed regular expression of ``ex@mplepassword``. Because there is still a 500 | remaining line that did not match, it is considered a secret. 501 | 502 | Because that matching lines are placed on lines that start with the filename 503 | and line number (e.g., ``/tmp/example:3:...``), you can create allowed 504 | patterns that take filenames and line numbers into account in the regular 505 | expression. For example, you could whitelist an entire file using something 506 | like:: 507 | 508 | git secrets --add --allowed '/tmp/example:.*' 509 | git secrets --scan /tmp/example && echo $? 510 | # Outputs: 0 511 | 512 | Alternatively, you could allow a specific line number of a file if that 513 | line is unlikely to change using something like the following: 514 | 515 | :: 516 | 517 | git secrets --add --allowed '/tmp/example:3:.*' 518 | git secrets --scan /tmp/example && echo $? 519 | # Outputs: 0 520 | 521 | Keep this in mind when creating allowed patterns to ensure that your allowed 522 | patterns are not inadvertently matched due to the fact that the filename is 523 | included in the subject text that allowed patterns are matched against. 524 | 525 | 526 | Skipping validation 527 | ------------------- 528 | 529 | Use the ``--no-verify`` option in the event of a false positive match in a 530 | commit, merge, or commit message. This will skip the execution of the 531 | git hook and allow you to make the commit or merge. 532 | 533 | 534 | About 535 | ------ 536 | 537 | - Author: `Michael Dowling `_ 538 | - Issue tracker: This project's source code and issue tracker can be found at 539 | `https://github.com/awslabs/git-secrets `_ 540 | - Special thanks to Adrian Vatchinsky and Ari Juels of Cornell University for 541 | providing suggestions and feedback. 542 | 543 | Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 544 | -------------------------------------------------------------------------------- /git-secrets: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | 15 | NONGIT_OK=1 OPTIONS_SPEC="\ 16 | git secrets --scan [-r|--recursive] [--cached] [--no-index] [--untracked] [...] 17 | git secrets --scan-history 18 | git secrets --install [-f|--force] [] 19 | git secrets --list [--global] 20 | git secrets --add [-a|--allowed] [-l|--literal] [--global] 21 | git secrets --add-provider [--global] [arguments...] 22 | git secrets --register-aws [--global] 23 | git secrets --aws-provider [] 24 | -- 25 | scan Scans for prohibited patterns 26 | scan-history Scans repo for prohibited patterns 27 | install Installs git hooks for Git repository or Git template directory 28 | list Lists secret patterns 29 | add Adds a prohibited or allowed pattern, ensuring to de-dupe with existing patterns 30 | add-provider Adds a secret provider that when called outputs secret patterns on new lines 31 | aws-provider Secret provider that outputs credentials found in an ini file 32 | register-aws Adds common AWS patterns to the git config and scans for ~/.aws/credentials 33 | r,recursive --scan scans directories recursively 34 | cached --scan scans searches blobs registered in the index file 35 | no-index --scan searches files in the current directory that is not managed by Git 36 | untracked In addition to searching in the tracked files in the working tree, --scan also in untracked files 37 | f,force --install overwrites hooks if the hook already exists 38 | l,literal --add and --add-allowed patterns are escaped so that they are literal 39 | a,allowed --add adds an allowed pattern instead of a prohibited pattern 40 | global Uses the --global git config 41 | commit_msg_hook* commit-msg hook (internal only) 42 | pre_commit_hook* pre-commit hook (internal only) 43 | prepare_commit_msg_hook* prepare-commit-msg hook (internal only)" 44 | 45 | # Include the git setup script. This parses and normalized CLI arguments. 46 | . "$(git --exec-path)"/git-sh-setup 47 | 48 | load_patterns() { 49 | git config --get-all secrets.patterns 50 | # Execute each provider and use their output to build up patterns 51 | git config --get-all secrets.providers | while read -r cmd; do 52 | # Only split words on '\n\t ' and strip "\r" from the output to account 53 | # for carriage returns being added on Windows systems. Note that this 54 | # trimming is done before the test to ensure that the string is not empty. 55 | local result="$(export IFS=$'\n\t '; $cmd | tr -d $'\r')" 56 | # Do not add empty lines from providers as that would match everything. 57 | if [ -n "${result}" ]; then 58 | echo "${result}" 59 | fi 60 | done 61 | } 62 | 63 | load_allowed() { 64 | git config --get-all secrets.allowed 65 | local gitallowed="$(git rev-parse --show-toplevel)/.gitallowed" 66 | if [ -e "$gitallowed" ]; then 67 | cat $gitallowed | awk 'NF && $1!~/^#/' 68 | fi 69 | } 70 | 71 | # load patterns and combine them with | 72 | load_combined_patterns() { 73 | local patterns=$(load_patterns) 74 | local combined_patterns='' 75 | for pattern in $patterns; do 76 | combined_patterns=${combined_patterns}${pattern}"|" 77 | done 78 | combined_patterns=${combined_patterns%?} 79 | echo $combined_patterns 80 | } 81 | 82 | # Scans files or a repo using patterns. 83 | scan() { 84 | local files=("${@}") options="" 85 | [ "${SCAN_CACHED}" == 1 ] && options+="--cached" 86 | [ "${SCAN_UNTRACKED}" == 1 ] && options+=" --untracked" 87 | [ "${SCAN_NO_INDEX}" == 1 ] && options+=" --no-index" 88 | # Scan using git-grep if there are no files or if git options are applied. 89 | if [ ${#files[@]} -eq 0 ] || [ ! -z "${options}" ]; then 90 | output=$(git_grep $options "${files[@]}") 91 | else 92 | output=$(regular_grep "${files[@]}") 93 | fi 94 | process_output $? "${output}" 95 | } 96 | 97 | # Scans through history using patterns 98 | scan_history() { 99 | # git log does not support multiple patterns, so we need to combine them 100 | local combined_patterns=$(load_combined_patterns) 101 | [ -z "${combined_patterns}" ] && return 0 102 | # Looks for differences matching the patterns, reduces the number of revisions to scan 103 | local to_scan=$(git log --all -G"${combined_patterns}" --pretty=%H) 104 | # Scan through revisions with findings to normalize output 105 | output=$(GREP_OPTIONS= LC_ALL=C git grep -nwHEI "${combined_patterns}" $to_scan) 106 | process_output $? "${output}" 107 | } 108 | 109 | # Performs a git-grep, taking into account patterns and options. 110 | # Note: this function returns 1 on success, 0 on error. 111 | git_grep() { 112 | local options="$1"; shift 113 | local files=("${@}") combined_patterns=$(load_combined_patterns) 114 | 115 | [ -z "${combined_patterns}" ] && return 1 116 | GREP_OPTIONS= LC_ALL=C git grep -nwHEI ${options} "${combined_patterns}" -- "${files[@]}" 117 | } 118 | 119 | # Performs a regular grep, taking into account patterns and recursion. 120 | # Note: this function returns 1 on success, 0 on error. 121 | regular_grep() { 122 | local files=("${@}") patterns=$(load_patterns) action='skip' 123 | [ -z "${patterns}" ] && return 1 124 | [ ${RECURSIVE} -eq 1 ] && action="recurse" 125 | GREP_OPTIONS= LC_ALL=C grep -d "${action}" -nwHEI "${patterns}" "${files[@]}" 126 | } 127 | 128 | # Process the given status ($1) and output variables ($2). 129 | # Takes into account allowed patterns, and if a bad match is found, 130 | # prints an error message and exits 1. 131 | process_output() { 132 | local status="$1" output="$2" 133 | local allowed=$(load_allowed) 134 | case "$status" in 135 | 0) 136 | [ -z "${allowed}" ] && echo "${output}" >&2 && return 1 137 | # Determine with a negative grep if the found matches are allowed 138 | echo "${output}" | GREP_OPTIONS= LC_ALL=C grep -Ev "${allowed}" >&2 \ 139 | && return 1 || return 0 140 | ;; 141 | 1) return 0 ;; 142 | *) exit $status 143 | esac 144 | } 145 | 146 | # Calls the given scanning function at $1, shifts, and passes to it $@. 147 | # Exit 0 if success, otherwise exit 1 with error message. 148 | scan_with_fn_or_die() { 149 | local fn="$1"; shift 150 | $fn "$@" && exit 0 151 | echo >&2 152 | echo "[ERROR] Matched one or more prohibited patterns" >&2 153 | echo >&2 154 | echo "Possible mitigations:" >&2 155 | echo "- Mark false positives as allowed using: git config --add secrets.allowed ..." >&2 156 | echo "- Mark false positives as allowed by adding regular expressions to .gitallowed at repository's root directory" >&2 157 | echo "- List your configured patterns: git config --get-all secrets.patterns" >&2 158 | echo "- List your configured allowed patterns: git config --get-all secrets.allowed" >&2 159 | echo "- List your configured allowed patterns in .gitallowed at repository's root directory" >&2 160 | echo "- Use --no-verify if this is a one-time false positive" >&2 161 | exit 1 162 | } 163 | 164 | # Scans a commit message, passed in the path to a file. 165 | commit_msg_hook() { 166 | scan_with_fn_or_die "scan" "$1" 167 | } 168 | 169 | # Scans all files that are about to be committed. 170 | pre_commit_hook() { 171 | SCAN_CACHED=1 172 | local files=() file found_match=0 rev="4b825dc642cb6eb9a060e54bf8d69288fbee4904" 173 | # Diff against HEAD if this is not the first commit in the repo. 174 | git rev-parse --verify HEAD >/dev/null 2>&1 && rev="HEAD" 175 | # Filter out deleted files using --diff-filter 176 | while IFS= read -r file; do 177 | [ -n "$file" ] && files+=("$file") 178 | done <<< "$(git diff-index --diff-filter 'ACMU' --name-only --cached $rev --)" 179 | scan_with_fn_or_die "scan" "${files[@]}" 180 | } 181 | 182 | # Determines if merging in a commit will introduce tainted history. 183 | prepare_commit_msg_hook() { 184 | case "$2,$3" in 185 | merge,) 186 | local git_head=$(env | grep GITHEAD) # e.g. GITHEAD_=release/1.43 187 | local sha="${git_head##*=}" # Get just the SHA 188 | local branch=$(git symbolic-ref HEAD) # e.g. refs/heads/master 189 | local dest="${branch#refs/heads/}" # cut out "refs/heads" 190 | git log "${dest}".."${sha}" -p | scan_with_fn_or_die "scan" - 191 | ;; 192 | esac 193 | } 194 | 195 | install_hook() { 196 | local path="$1" hook="$2" cmd="$3" dest 197 | # Determines the appropriate path for a hook to be installed 198 | if [ -d "${path}/hooks/${hook}.d" ]; then 199 | dest="${path}/hooks/${hook}.d/git-secrets" 200 | else 201 | dest="${path}/hooks/${hook}" 202 | fi 203 | [ -f "${dest}" ] && [ "${FORCE}" -ne 1 ] \ 204 | && die "${dest} already exists. Use -f to force" 205 | echo "#!/usr/bin/env bash" > "${dest}" 206 | echo "git secrets --${cmd} -- \"\$@\"" >> "${dest}" 207 | chmod +x "${dest}" 208 | [ -t 1 ] && command -v tput &> /dev/null && echo -n "$(tput setaf 2)✓$(tput sgr 0) " 209 | echo "Installed ${hook} hook to ${dest}" 210 | } 211 | 212 | install_all_hooks() { 213 | install_hook "$1" "commit-msg" "commit_msg_hook" 214 | install_hook "$1" "pre-commit" "pre_commit_hook" 215 | install_hook "$1" "prepare-commit-msg" "prepare_commit_msg_hook" 216 | } 217 | 218 | # Adds a git config pattern, ensuring to de-dupe 219 | add_config() { 220 | local key="$1"; shift 221 | local value="$@" 222 | if [ ${LITERAL} -eq 1 ]; then 223 | value=$(sed 's/[\.|$(){}?+*^]/\\&/g' <<< "${value}") 224 | fi 225 | if [ ${GLOBAL} -eq 1 ]; then 226 | git config --global --get-all $key | grep -Fq "${value}" && return 1 227 | git config --global --add "${key}" "${value}" 228 | else 229 | git config --get-all $key | grep -Fq "${value}" && return 1 230 | git config --add "${key}" "${value}" 231 | fi 232 | } 233 | 234 | register_aws() { 235 | # Reusable regex patterns 236 | local aws="(AWS|aws|Aws)?_?" quote="(\"|')" connect="\s*(:|=>|=)\s*" 237 | local opt_quote="${quote}?" 238 | add_config 'secrets.providers' 'git secrets --aws-provider' 239 | add_config 'secrets.patterns' '(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}' 240 | add_config 'secrets.patterns' "${opt_quote}${aws}(SECRET|secret|Secret)?_?(ACCESS|access|Access)?_?(KEY|key|Key)${opt_quote}${connect}${opt_quote}[A-Za-z0-9/\+=]{40}${opt_quote}" 241 | add_config 'secrets.patterns' "${opt_quote}${aws}(ACCOUNT|account|Account)_?(ID|id|Id)?${opt_quote}${connect}${opt_quote}[0-9]{4}\-?[0-9]{4}\-?[0-9]{4}${opt_quote}" 242 | add_config 'secrets.allowed' 'AKIAIOSFODNN7EXAMPLE' 243 | add_config 'secrets.allowed' "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" 244 | 245 | if [[ $? == 0 ]]; then 246 | echo 'OK' 247 | fi 248 | 249 | exit $? 250 | } 251 | 252 | aws_provider() { 253 | local fi="$1" 254 | [ -z "$fi" ] && fi=~/.aws/credentials 255 | # Find keys and ensure that special characters are escaped. 256 | if [ -f $fi ]; then 257 | awk -F "=" '/aws_access_key_id|aws_secret_access_key/ {print $2}' $fi \ 258 | | tr -d ' "' \ 259 | | sed 's/[]\.|$(){}?+*^]/\\&/g' 260 | fi 261 | } 262 | 263 | # Ensures that the command is what was expected for an option. 264 | assert_option_for_command() { 265 | local expected_command="$1" 266 | local option_name="$2" 267 | if [ "${COMMAND}" != "${expected_command}" ]; then 268 | die "${option_name} can only be supplied with the ${expected_command} subcommand" 269 | fi 270 | } 271 | 272 | declare COMMAND="$1" FORCE=0 RECURSIVE=0 LITERAL=0 GLOBAL=0 ALLOWED=0 273 | declare SCAN_CACHED=0 SCAN_NO_INDEX=0 SCAN_UNTRACKED=0 274 | 275 | # Shift off the command name 276 | shift 1 277 | while [ "$#" -ne 0 ]; do 278 | case "$1" in 279 | -f) 280 | assert_option_for_command "--install" "-f|--force" 281 | FORCE=1 282 | ;; 283 | -r) 284 | assert_option_for_command "--scan" "-r|--recursive" 285 | RECURSIVE=1 286 | ;; 287 | -a) 288 | assert_option_for_command "--add" "-a|--allowed" 289 | ALLOWED=1 290 | ;; 291 | -l) 292 | assert_option_for_command "--add" "-l|--literal" 293 | LITERAL=1 294 | ;; 295 | --cached) 296 | assert_option_for_command "--scan" "--cached" 297 | SCAN_CACHED=1 298 | ;; 299 | --no-index) 300 | assert_option_for_command "--scan" "--no-index" 301 | SCAN_NO_INDEX=1 302 | ;; 303 | --untracked) 304 | assert_option_for_command "--scan" "--untracked" 305 | SCAN_UNTRACKED=1 306 | ;; 307 | --global) GLOBAL=1 ;; 308 | --) shift; break ;; 309 | esac 310 | shift 311 | done 312 | 313 | # Ensure that recursive is not applied with mutually exclusive options. 314 | if [ ${RECURSIVE} -eq 1 ]; then 315 | if [ ${SCAN_CACHED} -eq 1 ] \ 316 | || [ ${SCAN_NO_INDEX} -eq 1 ] \ 317 | || [ ${SCAN_UNTRACKED} -eq 1 ]; 318 | then 319 | die "-r|--recursive cannot be supplied with --cached, --no-index, or --untracked" 320 | fi 321 | fi 322 | 323 | case "${COMMAND}" in 324 | -h|--help|--) "$0" -h; exit 0 ;; 325 | --add-provider) add_config "secrets.providers" "$@" ;; 326 | --register-aws) register_aws ;; 327 | --aws-provider) aws_provider "$1" ;; 328 | --commit_msg_hook|--pre_commit_hook|--prepare_commit_msg_hook) 329 | ${COMMAND:2} "$@" 330 | ;; 331 | --add) 332 | if [ ${ALLOWED} -eq 1 ]; then 333 | add_config "secrets.allowed" "$1" 334 | else 335 | add_config "secrets.patterns" "$1" 336 | fi 337 | ;; 338 | --scan) scan_with_fn_or_die "scan" "$@" ;; 339 | --scan-history) scan_with_fn_or_die "scan_history" "$@" ;; 340 | --list) 341 | if [ ${GLOBAL} -eq 1 ]; then 342 | git config --global --get-regex secrets.* 343 | else 344 | git config --get-regex secrets.* 345 | fi 346 | ;; 347 | --install) 348 | DIRECTORY="$1" 349 | if [ -z "${DIRECTORY}" ]; then 350 | DIRECTORY=$(git rev-parse --git-dir) || die "Not in a Git repository" 351 | elif [ -d "${DIRECTORY}"/.git ]; then 352 | DIRECTORY="${DIRECTORY}/.git" 353 | fi 354 | mkdir -p "${DIRECTORY}/hooks" || die "Could not create dir: ${DIRECTORY}" 355 | install_all_hooks "${DIRECTORY}" 356 | ;; 357 | *) echo "Unknown option: ${COMMAND}" && "$0" -h ;; 358 | esac 359 | -------------------------------------------------------------------------------- /git-secrets.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | .TH GIT-SECRETS "" "" "" 4 | .SH NAME 5 | git-secrets \- Prevents you from committing passwords and other sensitive information to a git repository. 6 | . 7 | .nr rst2man-indent-level 0 8 | . 9 | .de1 rstReportMargin 10 | \\$1 \\n[an-margin] 11 | level \\n[rst2man-indent-level] 12 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 13 | - 14 | \\n[rst2man-indent0] 15 | \\n[rst2man-indent1] 16 | \\n[rst2man-indent2] 17 | .. 18 | .de1 INDENT 19 | .\" .rstReportMargin pre: 20 | . RS \\$1 21 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 22 | . nr rst2man-indent-level +1 23 | .\" .rstReportMargin post: 24 | .. 25 | .de UNINDENT 26 | . RE 27 | .\" indent \\n[an-margin] 28 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 29 | .nr rst2man-indent-level -1 30 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 31 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 32 | .. 33 | .SS Contents 34 | .INDENT 0.0 35 | .IP \(bu 2 36 | \fI\%Synopsis\fP 37 | .IP \(bu 2 38 | \fI\%Description\fP 39 | .IP \(bu 2 40 | \fI\%Installing git\-secrets\fP 41 | .INDENT 2.0 42 | .IP \(bu 2 43 | \fI\%*nix (Linux/macOS)\fP 44 | .IP \(bu 2 45 | \fI\%Windows\fP 46 | .IP \(bu 2 47 | \fI\%Homebrew (for macOS users)\fP 48 | .UNINDENT 49 | .IP \(bu 2 50 | \fI\%Advanced configuration\fP 51 | .IP \(bu 2 52 | \fI\%Before making public a repository\fP 53 | .IP \(bu 2 54 | \fI\%Options\fP 55 | .INDENT 2.0 56 | .IP \(bu 2 57 | \fI\%Operation Modes\fP 58 | .IP \(bu 2 59 | \fI\%Options for \fB\-\-install\fP\fP 60 | .IP \(bu 2 61 | \fI\%Options for \fB\-\-scan\fP\fP 62 | .IP \(bu 2 63 | \fI\%Options for \fB\-\-list\fP\fP 64 | .IP \(bu 2 65 | \fI\%Options for \fB\-\-add\fP\fP 66 | .IP \(bu 2 67 | \fI\%Options for \fB\-\-register\-aws\fP\fP 68 | .IP \(bu 2 69 | \fI\%Options for \fB\-\-aws\-provider\fP\fP 70 | .IP \(bu 2 71 | \fI\%Options for \fB\-\-add\-provider\fP\fP 72 | .UNINDENT 73 | .IP \(bu 2 74 | \fI\%Defining prohibited patterns\fP 75 | .IP \(bu 2 76 | \fI\%Ignoring false positives\fP 77 | .IP \(bu 2 78 | \fI\%Secret providers\fP 79 | .IP \(bu 2 80 | \fI\%Example walkthrough\fP 81 | .IP \(bu 2 82 | \fI\%Skipping validation\fP 83 | .IP \(bu 2 84 | \fI\%About\fP 85 | .UNINDENT 86 | .SH SYNOPSIS 87 | .INDENT 0.0 88 | .INDENT 3.5 89 | .sp 90 | .nf 91 | .ft C 92 | git secrets \-\-scan [\-r|\-\-recursive] [\-\-cached] [\-\-no\-index] [\-\-untracked] [...] 93 | git secrets \-\-scan\-history 94 | git secrets \-\-install [\-f|\-\-force] [] 95 | git secrets \-\-list [\-\-global] 96 | git secrets \-\-add [\-a|\-\-allowed] [\-l|\-\-literal] [\-\-global] 97 | git secrets \-\-add\-provider [\-\-global] [arguments...] 98 | git secrets \-\-register\-aws [\-\-global] 99 | git secrets \-\-aws\-provider [] 100 | .ft P 101 | .fi 102 | .UNINDENT 103 | .UNINDENT 104 | .SH DESCRIPTION 105 | .sp 106 | \fBgit\-secrets\fP scans commits, commit messages, and \fB\-\-no\-ff\fP merges to 107 | prevent adding secrets into your git repositories. If a commit, 108 | commit message, or any commit in a \fB\-\-no\-ff\fP merge history matches one of 109 | your configured prohibited regular expression patterns, then the commit is 110 | rejected. 111 | .SH INSTALLING GIT-SECRETS 112 | .sp 113 | \fBgit\-secrets\fP must be placed somewhere in your PATH so that it is picked up 114 | by \fBgit\fP when running \fBgit secrets\fP\&. 115 | .SS *nix (Linux/macOS) 116 | .IP "System Message: WARNING/2 (README.rst:, line 43)" 117 | Title underline too short. 118 | .INDENT 0.0 119 | .INDENT 3.5 120 | .sp 121 | .nf 122 | .ft C 123 | \e*nix (Linux/macOS) 124 | ~~~~~~~~~~~~~~~~~ 125 | .ft P 126 | .fi 127 | .UNINDENT 128 | .UNINDENT 129 | .sp 130 | You can use the \fBinstall\fP target of the provided Makefile to install \fBgit secrets\fP and the man page. 131 | You can customize the install path using the PREFIX and MANPREFIX variables. 132 | .INDENT 0.0 133 | .INDENT 3.5 134 | .sp 135 | .nf 136 | .ft C 137 | make install 138 | .ft P 139 | .fi 140 | .UNINDENT 141 | .UNINDENT 142 | .SS Windows 143 | .sp 144 | Run the provided \fBinstall.ps1\fP powershell script. This will copy the needed files 145 | to an installation directory (\fB%USERPROFILE%/.git\-secrets\fP by default) and add 146 | the directory to the current user \fBPATH\fP\&. 147 | .INDENT 0.0 148 | .INDENT 3.5 149 | .sp 150 | .nf 151 | .ft C 152 | PS > ./install.ps1 153 | .ft P 154 | .fi 155 | .UNINDENT 156 | .UNINDENT 157 | .SS Homebrew (for macOS users) 158 | .INDENT 0.0 159 | .INDENT 3.5 160 | .sp 161 | .nf 162 | .ft C 163 | brew install git\-secrets 164 | .ft P 165 | .fi 166 | .UNINDENT 167 | .UNINDENT 168 | .sp 169 | \fBWARNING:\fP 170 | .INDENT 0.0 171 | .INDENT 3.5 172 | You\(aqre not done yet! You MUST install the git hooks for every repo that 173 | you wish to use with \fBgit secrets \-\-install\fP\&. 174 | .UNINDENT 175 | .UNINDENT 176 | .sp 177 | Here\(aqs a quick example of how to ensure a git repository is scanned for secrets 178 | on each commit: 179 | .INDENT 0.0 180 | .INDENT 3.5 181 | .sp 182 | .nf 183 | .ft C 184 | cd /path/to/my/repo 185 | git secrets \-\-install 186 | git secrets \-\-register\-aws 187 | .ft P 188 | .fi 189 | .UNINDENT 190 | .UNINDENT 191 | .SH ADVANCED CONFIGURATION 192 | .sp 193 | Add a configuration template if you want to add hooks to all repositories you 194 | initialize or clone in the future. 195 | .INDENT 0.0 196 | .INDENT 3.5 197 | .sp 198 | .nf 199 | .ft C 200 | git secrets \-\-register\-aws \-\-global 201 | .ft P 202 | .fi 203 | .UNINDENT 204 | .UNINDENT 205 | .sp 206 | Add hooks to all your local repositories. 207 | .INDENT 0.0 208 | .INDENT 3.5 209 | .sp 210 | .nf 211 | .ft C 212 | git secrets \-\-install ~/.git\-templates/git\-secrets 213 | git config \-\-global init.templateDir ~/.git\-templates/git\-secrets 214 | .ft P 215 | .fi 216 | .UNINDENT 217 | .UNINDENT 218 | .sp 219 | Add custom providers to scan for security credentials. 220 | .INDENT 0.0 221 | .INDENT 3.5 222 | .sp 223 | .nf 224 | .ft C 225 | git secrets \-\-add\-provider \-\- cat /path/to/secret/file/patterns 226 | .ft P 227 | .fi 228 | .UNINDENT 229 | .UNINDENT 230 | .SH BEFORE MAKING PUBLIC A REPOSITORY 231 | .sp 232 | With git\-secrets is also possible to scan a repository including all revisions: 233 | .INDENT 0.0 234 | .INDENT 3.5 235 | .sp 236 | .nf 237 | .ft C 238 | git secrets \-\-scan\-history 239 | .ft P 240 | .fi 241 | .UNINDENT 242 | .UNINDENT 243 | .SH OPTIONS 244 | .SS Operation Modes 245 | .sp 246 | Each of these options must appear first on the command line. 247 | .INDENT 0.0 248 | .TP 249 | .B \fB\-\-install\fP 250 | Installs git hooks for a repository. Once the hooks are installed for a git 251 | repository, commits and non\-fast\-forward merges for that repository will be prevented 252 | from committing secrets. 253 | .TP 254 | .B \fB\-\-scan\fP 255 | Scans one or more files for secrets. When a file contains a secret, the 256 | matched text from the file being scanned will be written to stdout and the 257 | script will exit with a non\-zero status. Each matched line will be written with 258 | the name of the file that matched, a colon, the line number that matched, 259 | a colon, and then the line of text that matched. If no files are provided, 260 | all files returned by \fBgit ls\-files\fP are scanned. 261 | .TP 262 | .B \fB\-\-scan\-history\fP 263 | Scans repository including all revisions. When a file contains a secret, the 264 | matched text from the file being scanned will be written to stdout and the 265 | script will exit with a non\-zero status. Each matched line will be written with 266 | the name of the file that matched, a colon, the line number that matched, 267 | a colon, and then the line of text that matched. 268 | .TP 269 | .B \fB\-\-list\fP 270 | Lists the \fBgit\-secrets\fP configuration for the current repo or in the global 271 | git config. 272 | .TP 273 | .B \fB\-\-add\fP 274 | Adds a prohibited or allowed pattern. 275 | .TP 276 | .B \fB\-\-add\-provider\fP 277 | Registers a secret provider. Secret providers are executables that when 278 | invoked output prohibited patterns that \fBgit\-secrets\fP should treat as 279 | prohibited. 280 | .TP 281 | .B \fB\-\-register\-aws\fP 282 | Adds common AWS patterns to the git config and ensures that keys present 283 | in \fB~/.aws/credentials\fP are not found in any commit. The following 284 | checks are added: 285 | .INDENT 7.0 286 | .IP \(bu 2 287 | AWS Access Key IDs via \fB(A3T[A\-Z0\-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A\-Z0\-9]{16}\fP 288 | .IP \(bu 2 289 | AWS Secret Access Key assignments via ":" or "=" surrounded by optional 290 | quotes 291 | .IP \(bu 2 292 | AWS account ID assignments via ":" or "=" surrounded by optional quotes 293 | .IP \(bu 2 294 | Allowed patterns for example AWS keys (\fBAKIAIOSFODNN7EXAMPLE\fP and 295 | \fBwJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\fP) 296 | .IP \(bu 2 297 | Known credentials from \fB~/.aws/credentials\fP 298 | .UNINDENT 299 | .sp 300 | \fBNOTE:\fP 301 | .INDENT 7.0 302 | .INDENT 3.5 303 | While the patterns registered by this command should catch most 304 | instances of AWS credentials, these patterns are \fBnot\fP guaranteed to 305 | catch them \fBall\fP\&. \fBgit\-secrets\fP should be used as an extra means of 306 | insurance \-\- you still need to do your due diligence to ensure that you 307 | do not commit credentials to a repository. 308 | .UNINDENT 309 | .UNINDENT 310 | .TP 311 | .B \fB\-\-aws\-provider\fP 312 | Secret provider that outputs credentials found in an INI file. You can 313 | optionally provide the path to an INI file. 314 | .UNINDENT 315 | .SS Options for \fB\-\-install\fP 316 | .INDENT 0.0 317 | .TP 318 | .B \fB\-f, \-\-force\fP 319 | Overwrites existing hooks if present. 320 | .TP 321 | .B \fB\fP 322 | When provided, installs git hooks to the given directory. The current 323 | directory is assumed if \fB\fP is not provided. 324 | .sp 325 | If the provided \fB\fP is not in a git repository, the 326 | directory will be created and hooks will be placed in 327 | \fB/hooks\fP\&. This can be useful for creating git template 328 | directories using with \fBgit init \-\-template \fP\&. 329 | .sp 330 | You can run \fBgit init\fP on a repository that has already been initialized. 331 | From the \fI\%git init documentation\fP: 332 | .INDENT 7.0 333 | .INDENT 3.5 334 | From the git documentation: Running \fBgit init\fP in an existing repository 335 | is safe. It will not overwrite things that are already there. The 336 | primary reason for rerunning \fBgit init\fP is to pick up newly added 337 | templates (or to move the repository to another place if 338 | \fB\-\-separate\-git\-dir\fP is given). 339 | .UNINDENT 340 | .UNINDENT 341 | .sp 342 | The following git hooks are installed: 343 | .INDENT 7.0 344 | .IP 1. 3 345 | \fBpre\-commit\fP: Used to check if any of the files changed in the commit 346 | use prohibited patterns. 347 | .IP 2. 3 348 | \fBcommit\-msg\fP: Used to determine if a commit message contains a 349 | prohibited patterns. 350 | .IP 3. 3 351 | \fBprepare\-commit\-msg\fP: Used to determine if a merge commit will 352 | introduce a history that contains a prohibited pattern at any point. 353 | Please note that this hook is only invoked for non fast\-forward merges. 354 | .UNINDENT 355 | .sp 356 | \fBNOTE:\fP 357 | .INDENT 7.0 358 | .INDENT 3.5 359 | Git only allows a single script to be executed per hook. If the 360 | repository contains Debian\-style subdirectories like \fBpre\-commit.d\fP 361 | and \fBcommit\-msg.d\fP, then the git hooks will be installed into these 362 | directories, which assumes that you\(aqve configured the corresponding 363 | hooks to execute all of the scripts found in these directories. If 364 | these git subdirectories are not present, then the git hooks will be 365 | installed to the git repo\(aqs \fB\&.git/hooks\fP directory. 366 | .UNINDENT 367 | .UNINDENT 368 | .UNINDENT 369 | .SS Examples 370 | .sp 371 | Install git hooks to the current directory: 372 | .INDENT 0.0 373 | .INDENT 3.5 374 | .sp 375 | .nf 376 | .ft C 377 | cd /path/to/my/repository 378 | git secrets \-\-install 379 | .ft P 380 | .fi 381 | .UNINDENT 382 | .UNINDENT 383 | .sp 384 | Install git hooks to a repository other than the current directory: 385 | .INDENT 0.0 386 | .INDENT 3.5 387 | .sp 388 | .nf 389 | .ft C 390 | git secrets \-\-install /path/to/my/repository 391 | .ft P 392 | .fi 393 | .UNINDENT 394 | .UNINDENT 395 | .sp 396 | Create a git template that has \fBgit\-secrets\fP installed, and then copy that 397 | template into a git repository: 398 | .INDENT 0.0 399 | .INDENT 3.5 400 | .sp 401 | .nf 402 | .ft C 403 | git secrets \-\-install ~/.git\-templates/git\-secrets 404 | git init \-\-template ~/.git\-templates/git\-secrets 405 | .ft P 406 | .fi 407 | .UNINDENT 408 | .UNINDENT 409 | .sp 410 | Overwrite existing hooks if present: 411 | .INDENT 0.0 412 | .INDENT 3.5 413 | .sp 414 | .nf 415 | .ft C 416 | git secrets \-\-install \-f 417 | .ft P 418 | .fi 419 | .UNINDENT 420 | .UNINDENT 421 | .SS Options for \fB\-\-scan\fP 422 | .INDENT 0.0 423 | .TP 424 | .B \fB\-r, \-\-recursive\fP 425 | Scans the given files recursively. If a directory is encountered, the 426 | directory will be scanned. If \fB\-r\fP is not provided, directories will be 427 | ignored. 428 | .sp 429 | \fB\-r\fP cannot be used alongside \fB\-\-cached\fP, \fB\-\-no\-index\fP, or 430 | \fB\-\-untracked\fP\&. 431 | .TP 432 | .B \fB\-\-cached\fP 433 | Searches blobs registered in the index file. 434 | .TP 435 | .B \fB\-\-no\-index\fP 436 | Searches files in the current directory that is not managed by git. 437 | .TP 438 | .B \fB\-\-untracked\fP 439 | In addition to searching in the tracked files in the working tree, 440 | \fB\-\-scan\fP also in untracked files. 441 | .TP 442 | .B \fB...\fP 443 | The path to one or more files on disk to scan for secrets. 444 | .sp 445 | If no files are provided, all files returned by \fBgit ls\-files\fP are 446 | scanned. 447 | .UNINDENT 448 | .SS Examples 449 | .sp 450 | Scan all files in the repo: 451 | .INDENT 0.0 452 | .INDENT 3.5 453 | .sp 454 | .nf 455 | .ft C 456 | git secrets \-\-scan 457 | .ft P 458 | .fi 459 | .UNINDENT 460 | .UNINDENT 461 | .sp 462 | Scans a single file for secrets: 463 | .INDENT 0.0 464 | .INDENT 3.5 465 | .sp 466 | .nf 467 | .ft C 468 | git secrets \-\-scan /path/to/file 469 | .ft P 470 | .fi 471 | .UNINDENT 472 | .UNINDENT 473 | .sp 474 | Scans a directory recursively for secrets: 475 | .INDENT 0.0 476 | .INDENT 3.5 477 | .sp 478 | .nf 479 | .ft C 480 | git secrets \-\-scan \-r /path/to/directory 481 | .ft P 482 | .fi 483 | .UNINDENT 484 | .UNINDENT 485 | .sp 486 | Scans multiple files for secrets: 487 | .INDENT 0.0 488 | .INDENT 3.5 489 | .sp 490 | .nf 491 | .ft C 492 | git secrets \-\-scan /path/to/file /path/to/other/file 493 | .ft P 494 | .fi 495 | .UNINDENT 496 | .UNINDENT 497 | .sp 498 | You can scan by globbing: 499 | .INDENT 0.0 500 | .INDENT 3.5 501 | .sp 502 | .nf 503 | .ft C 504 | git secrets \-\-scan /path/to/directory/* 505 | .ft P 506 | .fi 507 | .UNINDENT 508 | .UNINDENT 509 | .sp 510 | Scan from stdin: 511 | .INDENT 0.0 512 | .INDENT 3.5 513 | .sp 514 | .nf 515 | .ft C 516 | echo \(aqhello!\(aq | git secrets \-\-scan \- 517 | .ft P 518 | .fi 519 | .UNINDENT 520 | .UNINDENT 521 | .SS Options for \fB\-\-list\fP 522 | .INDENT 0.0 523 | .TP 524 | .B \fB\-\-global\fP 525 | Lists only git\-secrets configuration in the global git config. 526 | .UNINDENT 527 | .SS Options for \fB\-\-add\fP 528 | .INDENT 0.0 529 | .TP 530 | .B \fB\-\-global\fP 531 | Adds patterns to the global git config 532 | .TP 533 | .B \fB\-l, \-\-literal\fP 534 | Escapes special regular expression characters in the provided pattern so 535 | that the pattern is searched for literally. 536 | .TP 537 | .B \fB\-a, \-\-allowed\fP 538 | Mark the pattern as allowed instead of prohibited. Allowed patterns are 539 | used to filter out false positives. 540 | .TP 541 | .B \fB\fP 542 | The regex pattern to search. 543 | .UNINDENT 544 | .SS Examples 545 | .sp 546 | Adds a prohibited pattern to the current repo: 547 | .INDENT 0.0 548 | .INDENT 3.5 549 | .sp 550 | .nf 551 | .ft C 552 | git secrets \-\-add \(aq[A\-Z0\-9]{20}\(aq 553 | .ft P 554 | .fi 555 | .UNINDENT 556 | .UNINDENT 557 | .sp 558 | Adds a prohibited pattern to the global git config: 559 | .INDENT 0.0 560 | .INDENT 3.5 561 | .sp 562 | .nf 563 | .ft C 564 | git secrets \-\-add \-\-global \(aq[A\-Z0\-9]{20}\(aq 565 | .ft P 566 | .fi 567 | .UNINDENT 568 | .UNINDENT 569 | .sp 570 | Adds a string that is scanned for literally (\fB+\fP is escaped): 571 | .INDENT 0.0 572 | .INDENT 3.5 573 | .sp 574 | .nf 575 | .ft C 576 | git secrets \-\-add \-\-literal \(aqfoo+bar\(aq 577 | .ft P 578 | .fi 579 | .UNINDENT 580 | .UNINDENT 581 | .sp 582 | Add an allowed pattern: 583 | .INDENT 0.0 584 | .INDENT 3.5 585 | .sp 586 | .nf 587 | .ft C 588 | git secrets \-\-add \-a \(aqallowed pattern\(aq 589 | .ft P 590 | .fi 591 | .UNINDENT 592 | .UNINDENT 593 | .SS Options for \fB\-\-register\-aws\fP 594 | .INDENT 0.0 595 | .TP 596 | .B \fB\-\-global\fP 597 | Adds AWS specific configuration variables to the global git config. 598 | .UNINDENT 599 | .SS Options for \fB\-\-aws\-provider\fP 600 | .INDENT 0.0 601 | .TP 602 | .B \fB[]\fP 603 | If provided, specifies the custom path to an INI file to scan. If not 604 | provided, \fB~/.aws/credentials\fP is assumed. 605 | .UNINDENT 606 | .SS Options for \fB\-\-add\-provider\fP 607 | .INDENT 0.0 608 | .TP 609 | .B \fB\-\-global\fP 610 | Adds the provider to the global git config. 611 | .TP 612 | .B \fB\fP 613 | Provider command to invoke. When invoked the command is expected to write 614 | prohibited patterns separated by new lines to stdout. Any extra arguments 615 | provided are passed on to the command. 616 | .UNINDENT 617 | .SS Examples 618 | .sp 619 | Registers a secret provider with arguments: 620 | .INDENT 0.0 621 | .INDENT 3.5 622 | .sp 623 | .nf 624 | .ft C 625 | git secrets \-\-add\-provider \-\- git secrets \-\-aws\-provider 626 | .ft P 627 | .fi 628 | .UNINDENT 629 | .UNINDENT 630 | .sp 631 | Cats secrets out of a file: 632 | .INDENT 0.0 633 | .INDENT 3.5 634 | .sp 635 | .nf 636 | .ft C 637 | git secrets \-\-add\-provider \-\- cat /path/to/secret/file/patterns 638 | .ft P 639 | .fi 640 | .UNINDENT 641 | .UNINDENT 642 | .SH DEFINING PROHIBITED PATTERNS 643 | .sp 644 | \fBegrep\fP\-compatible regular expressions are used to determine if a commit or 645 | commit message contains any prohibited patterns. These regular expressions are 646 | defined using the \fBgit config\fP command. It is important to note that 647 | different systems use different versions of egrep. For example, when running on 648 | macOS, you will use a different version of \fBegrep\fP than when running on something 649 | like Ubuntu (BSD vs GNU). 650 | .sp 651 | You can add prohibited regular expression patterns to your git config using 652 | \fBgit secrets \-\-add \fP\&. 653 | .SH IGNORING FALSE POSITIVES 654 | .sp 655 | Sometimes a regular expression might match false positives. For example, git 656 | commit SHAs look a lot like AWS access keys. You can specify many different 657 | regular expression patterns as false positives using the following command: 658 | .INDENT 0.0 659 | .INDENT 3.5 660 | .sp 661 | .nf 662 | .ft C 663 | git secrets \-\-add \-\-allowed \(aqmy regex pattern\(aq 664 | .ft P 665 | .fi 666 | .UNINDENT 667 | .UNINDENT 668 | .sp 669 | You can also add regular expressions patterns to filter false positives to a 670 | \fB\&.gitallowed\fP file located in the repository\(aqs root directory. Lines starting 671 | with \fB#\fP are skipped (comment line) and empty lines are also skipped. 672 | .sp 673 | First, git\-secrets will extract all lines from a file that contain a prohibited 674 | match. Included in the matched results will be the full path to the name of 675 | the file that was matched, followed by \(aq:\(aq, followed by the line number that was 676 | matched, followed by the entire line from the file that was matched by a secret 677 | pattern. Then, if you\(aqve defined allowed regular expressions, git\-secrets will 678 | check to see if all of the matched lines match at least one of your registered 679 | allowed regular expressions. If all of the lines that were flagged as secret 680 | are canceled out by an allowed match, then the subject text does not contain 681 | any secrets. If any of the matched lines are not matched by an allowed regular 682 | expression, then git\-secrets will fail the commit/merge/message. 683 | .sp 684 | \fBIMPORTANT:\fP 685 | .INDENT 0.0 686 | .INDENT 3.5 687 | Just as it is a bad practice to add prohibited patterns that are too 688 | greedy, it is also a bad practice to add allowed patterns that are too 689 | forgiving. Be sure to test out your patterns using ad\-hoc calls to 690 | \fBgit secrets \-\-scan $filename\fP to ensure they are working as intended. 691 | .UNINDENT 692 | .UNINDENT 693 | .SH SECRET PROVIDERS 694 | .sp 695 | Sometimes you want to check for an exact pattern match against a set of known 696 | secrets. For example, you might want to ensure that no credentials present in 697 | \fB~/.aws/credentials\fP ever show up in a commit. In these cases, it\(aqs better to 698 | leave these secrets in one location rather than spread them out across git 699 | repositories in git configs. You can use "secret providers" to fetch these 700 | types of credentials. A secret provider is an executable that when invoked 701 | outputs prohibited patterns separated by new lines. 702 | .sp 703 | You can add secret providers using the \fB\-\-add\-provider\fP command: 704 | .INDENT 0.0 705 | .INDENT 3.5 706 | .sp 707 | .nf 708 | .ft C 709 | git secrets \-\-add\-provider \-\- git secrets \-\-aws\-provider 710 | .ft P 711 | .fi 712 | .UNINDENT 713 | .UNINDENT 714 | .sp 715 | Notice the use of \fB\-\-\fP\&. This ensures that any arguments associated with the 716 | provider are passed to the provider each time it is invoked when scanning 717 | for secrets. 718 | .SH EXAMPLE WALKTHROUGH 719 | .sp 720 | Let\(aqs take a look at an example. Given the following subject text (stored in 721 | \fB/tmp/example\fP): 722 | .INDENT 0.0 723 | .INDENT 3.5 724 | .sp 725 | .nf 726 | .ft C 727 | This is a test! 728 | password=ex@mplepassword 729 | password=****** 730 | More test... 731 | .ft P 732 | .fi 733 | .UNINDENT 734 | .UNINDENT 735 | .sp 736 | And the following registered patterns: 737 | .INDENT 0.0 738 | .INDENT 3.5 739 | .sp 740 | .nf 741 | .ft C 742 | git secrets \-\-add \(aqpassword\es*=\es*.+\(aq 743 | git secrets \-\-add \-\-allowed \-\-literal \(aqex@mplepassword\(aq 744 | .ft P 745 | .fi 746 | .UNINDENT 747 | .UNINDENT 748 | .sp 749 | Running \fBgit secrets \-\-scan /tmp/example\fP, the result will 750 | result in the following error output: 751 | .INDENT 0.0 752 | .INDENT 3.5 753 | .sp 754 | .nf 755 | .ft C 756 | /tmp/example:3:password=****** 757 | 758 | [ERROR] Matched prohibited pattern 759 | 760 | Possible mitigations: 761 | \- Mark false positives as allowed using: git config \-\-add secrets.allowed ... 762 | \- List your configured patterns: git config \-\-get\-all secrets.patterns 763 | \- List your configured allowed patterns: git config \-\-get\-all secrets.allowed 764 | \- Use \-\-no\-verify if this is a one\-time false positive 765 | .ft P 766 | .fi 767 | .UNINDENT 768 | .UNINDENT 769 | .sp 770 | Breaking this down, the prohibited pattern value of \fBpassword\es*=\es*.+\fP will 771 | match the following lines: 772 | .INDENT 0.0 773 | .INDENT 3.5 774 | .sp 775 | .nf 776 | .ft C 777 | /tmp/example:2:password=ex@mplepassword 778 | /tmp/example:3:password=****** 779 | .ft P 780 | .fi 781 | .UNINDENT 782 | .UNINDENT 783 | .sp 784 | \&...But the first match will be filtered out due to the fact that it matches the 785 | allowed regular expression of \fBex@mplepassword\fP\&. Because there is still a 786 | remaining line that did not match, it is considered a secret. 787 | .sp 788 | Because that matching lines are placed on lines that start with the filename 789 | and line number (e.g., \fB/tmp/example:3:...\fP), you can create allowed 790 | patterns that take filenames and line numbers into account in the regular 791 | expression. For example, you could whitelist an entire file using something 792 | like: 793 | .INDENT 0.0 794 | .INDENT 3.5 795 | .sp 796 | .nf 797 | .ft C 798 | git secrets \-\-add \-\-allowed \(aq/tmp/example:.*\(aq 799 | git secrets \-\-scan /tmp/example && echo $? 800 | # Outputs: 0 801 | .ft P 802 | .fi 803 | .UNINDENT 804 | .UNINDENT 805 | .sp 806 | Alternatively, you could allow a specific line number of a file if that 807 | line is unlikely to change using something like the following: 808 | .INDENT 0.0 809 | .INDENT 3.5 810 | .sp 811 | .nf 812 | .ft C 813 | git secrets \-\-add \-\-allowed \(aq/tmp/example:3:.*\(aq 814 | git secrets \-\-scan /tmp/example && echo $? 815 | # Outputs: 0 816 | .ft P 817 | .fi 818 | .UNINDENT 819 | .UNINDENT 820 | .sp 821 | Keep this in mind when creating allowed patterns to ensure that your allowed 822 | patterns are not inadvertently matched due to the fact that the filename is 823 | included in the subject text that allowed patterns are matched against. 824 | .SH SKIPPING VALIDATION 825 | .sp 826 | Use the \fB\-\-no\-verify\fP option in the event of a false positive match in a 827 | commit, merge, or commit message. This will skip the execution of the 828 | git hook and allow you to make the commit or merge. 829 | .SH ABOUT 830 | .INDENT 0.0 831 | .IP \(bu 2 832 | Author: \fI\%Michael Dowling\fP 833 | .IP \(bu 2 834 | Issue tracker: This project\(aqs source code and issue tracker can be found at 835 | \fI\%https://github.com/awslabs/git\-secrets\fP 836 | .IP \(bu 2 837 | Special thanks to Adrian Vatchinsky and Ari Juels of Cornell University for 838 | providing suggestions and feedback. 839 | .UNINDENT 840 | .sp 841 | Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 842 | .\" Generated by docutils manpage writer. 843 | . 844 | -------------------------------------------------------------------------------- /install.ps1: -------------------------------------------------------------------------------- 1 | Param([string]$InstallationDirectory = $($Env:USERPROFILE + "\.git-secrets")) 2 | 3 | Write-Host "Checking to see if installation directory already exists..." 4 | if (-not (Test-Path $InstallationDirectory)) 5 | { 6 | Write-Host "Creating installation directory." 7 | New-Item -ItemType Directory -Path $InstallationDirectory | Out-Null 8 | } 9 | else 10 | { 11 | Write-Host "Installation directory already exists." 12 | } 13 | 14 | Write-Host "Copying files." 15 | Copy-Item ./git-secrets -Destination $InstallationDirectory -Force 16 | Copy-Item ./git-secrets.1 -Destination $InstallationDirectory -Force 17 | 18 | Write-Host "Checking if directory already exists in Path..." 19 | $currentPath = [Environment]::GetEnvironmentVariable("PATH", "User") 20 | if ($currentPath -notlike "*$InstallationDirectory*") 21 | { 22 | Write-Host "Adding to path." 23 | $newPath = $currentPath 24 | if(-not ($newPath.EndsWith(";"))) 25 | { 26 | $newPath = $newPath + ";" 27 | } 28 | $newPath = $newPath + $InstallationDirectory 29 | [Environment]::SetEnvironmentVariable("PATH", $newPath, "User") 30 | } 31 | else 32 | { 33 | Write-Host "Already in Path." 34 | } 35 | 36 | # Adding to Session 37 | Write-Host "Adding to user session." 38 | $currentSessionPath = $Env:Path 39 | if ($currentSessionPath -notlike "*$InstallationDirectory*") 40 | { 41 | if(-not ($currentSessionPath.EndsWith(";"))) 42 | { 43 | $currentSessionPath = $currentSessionPath + ";" 44 | } 45 | $Env:Path = $currentSessionPath + $InstallationDirectory 46 | } 47 | 48 | Write-Host "Done." -------------------------------------------------------------------------------- /test/bats/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Sam Stephenson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/bats/bin/bats: -------------------------------------------------------------------------------- 1 | ../libexec/bats -------------------------------------------------------------------------------- /test/bats/libexec/bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | version() { 5 | echo "Bats 0.4.0" 6 | } 7 | 8 | usage() { 9 | version 10 | echo "Usage: bats [-c] [-p | -t] [ ...]" 11 | } 12 | 13 | help() { 14 | usage 15 | echo 16 | echo " is the path to a Bats test file, or the path to a directory" 17 | echo " containing Bats test files." 18 | echo 19 | echo " -c, --count Count the number of test cases without running any tests" 20 | echo " -h, --help Display this help message" 21 | echo " -p, --pretty Show results in pretty format (default for terminals)" 22 | echo " -t, --tap Show results in TAP format" 23 | echo " -v, --version Display the version number" 24 | echo 25 | echo " For more information, see https://github.com/sstephenson/bats" 26 | echo 27 | } 28 | 29 | resolve_link() { 30 | $(type -p greadlink readlink | head -1) "$1" 31 | } 32 | 33 | abs_dirname() { 34 | local cwd="$(pwd)" 35 | local path="$1" 36 | 37 | while [ -n "$path" ]; do 38 | cd "${path%/*}" 39 | local name="${path##*/}" 40 | path="$(resolve_link "$name" || true)" 41 | done 42 | 43 | pwd 44 | cd "$cwd" 45 | } 46 | 47 | expand_path() { 48 | { cd "$(dirname "$1")" 2>/dev/null 49 | local dirname="$PWD" 50 | cd "$OLDPWD" 51 | echo "$dirname/$(basename "$1")" 52 | } || echo "$1" 53 | } 54 | 55 | BATS_LIBEXEC="$(abs_dirname "$0")" 56 | export BATS_PREFIX="$(abs_dirname "$BATS_LIBEXEC")" 57 | export BATS_CWD="$(abs_dirname .)" 58 | export PATH="$BATS_LIBEXEC:$PATH" 59 | 60 | options=() 61 | arguments=() 62 | for arg in "$@"; do 63 | if [ "${arg:0:1}" = "-" ]; then 64 | if [ "${arg:1:1}" = "-" ]; then 65 | options[${#options[*]}]="${arg:2}" 66 | else 67 | index=1 68 | while option="${arg:$index:1}"; do 69 | [ -n "$option" ] || break 70 | options[${#options[*]}]="$option" 71 | let index+=1 72 | done 73 | fi 74 | else 75 | arguments[${#arguments[*]}]="$arg" 76 | fi 77 | done 78 | 79 | unset count_flag pretty 80 | [ -t 0 ] && [ -t 1 ] && pretty="1" 81 | [ -n "$CI" ] && pretty="" 82 | 83 | for option in "${options[@]}"; do 84 | case "$option" in 85 | "h" | "help" ) 86 | help 87 | exit 0 88 | ;; 89 | "v" | "version" ) 90 | version 91 | exit 0 92 | ;; 93 | "c" | "count" ) 94 | count_flag="-c" 95 | ;; 96 | "t" | "tap" ) 97 | pretty="" 98 | ;; 99 | "p" | "pretty" ) 100 | pretty="1" 101 | ;; 102 | * ) 103 | usage >&2 104 | exit 1 105 | ;; 106 | esac 107 | done 108 | 109 | if [ "${#arguments[@]}" -eq 0 ]; then 110 | usage >&2 111 | exit 1 112 | fi 113 | 114 | filenames=() 115 | for filename in "${arguments[@]}"; do 116 | if [ -d "$filename" ]; then 117 | shopt -s nullglob 118 | for suite_filename in "$(expand_path "$filename")"/*.bats; do 119 | filenames["${#filenames[@]}"]="$suite_filename" 120 | done 121 | shopt -u nullglob 122 | else 123 | filenames["${#filenames[@]}"]="$(expand_path "$filename")" 124 | fi 125 | done 126 | 127 | if [ "${#filenames[@]}" -eq 1 ]; then 128 | command="bats-exec-test" 129 | else 130 | command="bats-exec-suite" 131 | fi 132 | 133 | if [ -n "$pretty" ]; then 134 | extended_syntax_flag="-x" 135 | formatter="bats-format-tap-stream" 136 | else 137 | extended_syntax_flag="" 138 | formatter="cat" 139 | fi 140 | 141 | set -o pipefail execfail 142 | exec "$command" $count_flag $extended_syntax_flag "${filenames[@]}" | "$formatter" 143 | -------------------------------------------------------------------------------- /test/bats/libexec/bats-exec-suite: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | count_only_flag="" 5 | if [ "$1" = "-c" ]; then 6 | count_only_flag=1 7 | shift 8 | fi 9 | 10 | extended_syntax_flag="" 11 | if [ "$1" = "-x" ]; then 12 | extended_syntax_flag="-x" 13 | shift 14 | fi 15 | 16 | trap "kill 0; exit 1" int 17 | 18 | count=0 19 | for filename in "$@"; do 20 | let count+="$(bats-exec-test -c "$filename")" 21 | done 22 | 23 | if [ -n "$count_only_flag" ]; then 24 | echo "$count" 25 | exit 26 | fi 27 | 28 | echo "1..$count" 29 | status=0 30 | offset=0 31 | for filename in "$@"; do 32 | index=0 33 | { 34 | IFS= read -r # 1..n 35 | while IFS= read -r line; do 36 | case "$line" in 37 | "begin "* ) 38 | let index+=1 39 | echo "${line/ $index / $(($offset + $index)) }" 40 | ;; 41 | "ok "* | "not ok "* ) 42 | [ -n "$extended_syntax_flag" ] || let index+=1 43 | echo "${line/ $index / $(($offset + $index)) }" 44 | [ "${line:0:6}" != "not ok" ] || status=1 45 | ;; 46 | * ) 47 | echo "$line" 48 | ;; 49 | esac 50 | done 51 | } < <( bats-exec-test $extended_syntax_flag "$filename" ) 52 | offset=$(($offset + $index)) 53 | done 54 | 55 | exit "$status" 56 | -------------------------------------------------------------------------------- /test/bats/libexec/bats-exec-test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | set -E 4 | set -T 5 | 6 | BATS_COUNT_ONLY="" 7 | if [ "$1" = "-c" ]; then 8 | BATS_COUNT_ONLY=1 9 | shift 10 | fi 11 | 12 | BATS_EXTENDED_SYNTAX="" 13 | if [ "$1" = "-x" ]; then 14 | BATS_EXTENDED_SYNTAX="$1" 15 | shift 16 | fi 17 | 18 | BATS_TEST_FILENAME="$1" 19 | if [ -z "$BATS_TEST_FILENAME" ]; then 20 | echo "usage: bats-exec " >&2 21 | exit 1 22 | elif [ ! -f "$BATS_TEST_FILENAME" ]; then 23 | echo "bats: $BATS_TEST_FILENAME does not exist" >&2 24 | exit 1 25 | else 26 | shift 27 | fi 28 | 29 | BATS_TEST_DIRNAME="$(dirname "$BATS_TEST_FILENAME")" 30 | BATS_TEST_NAMES=() 31 | 32 | load() { 33 | local name="$1" 34 | local filename 35 | 36 | if [ "${name:0:1}" = "/" ]; then 37 | filename="${name}" 38 | else 39 | filename="$BATS_TEST_DIRNAME/${name}.bash" 40 | fi 41 | 42 | [ -f "$filename" ] || { 43 | echo "bats: $filename does not exist" >&2 44 | exit 1 45 | } 46 | 47 | source "${filename}" 48 | } 49 | 50 | run() { 51 | local e E T oldIFS 52 | [[ ! "$-" =~ e ]] || e=1 53 | [[ ! "$-" =~ E ]] || E=1 54 | [[ ! "$-" =~ T ]] || T=1 55 | set +e 56 | set +E 57 | set +T 58 | output="$("$@" 2>&1)" 59 | status="$?" 60 | oldIFS=$IFS 61 | IFS=$'\n' lines=($output) 62 | [ -z "$e" ] || set -e 63 | [ -z "$E" ] || set -E 64 | [ -z "$T" ] || set -T 65 | IFS=$oldIFS 66 | } 67 | 68 | setup() { 69 | true 70 | } 71 | 72 | teardown() { 73 | true 74 | } 75 | 76 | skip() { 77 | BATS_TEST_SKIPPED=${1:-1} 78 | BATS_TEST_COMPLETED=1 79 | exit 0 80 | } 81 | 82 | bats_test_begin() { 83 | BATS_TEST_DESCRIPTION="$1" 84 | if [ -n "$BATS_EXTENDED_SYNTAX" ]; then 85 | echo "begin $BATS_TEST_NUMBER $BATS_TEST_DESCRIPTION" >&3 86 | fi 87 | setup 88 | } 89 | 90 | bats_test_function() { 91 | local test_name="$1" 92 | BATS_TEST_NAMES["${#BATS_TEST_NAMES[@]}"]="$test_name" 93 | } 94 | 95 | bats_capture_stack_trace() { 96 | BATS_PREVIOUS_STACK_TRACE=( "${BATS_CURRENT_STACK_TRACE[@]}" ) 97 | BATS_CURRENT_STACK_TRACE=() 98 | 99 | local test_pattern=" $BATS_TEST_NAME $BATS_TEST_SOURCE" 100 | local setup_pattern=" setup $BATS_TEST_SOURCE" 101 | local teardown_pattern=" teardown $BATS_TEST_SOURCE" 102 | 103 | local frame 104 | local index=1 105 | 106 | while frame="$(caller "$index")"; do 107 | BATS_CURRENT_STACK_TRACE["${#BATS_CURRENT_STACK_TRACE[@]}"]="$frame" 108 | if [[ "$frame" = *"$test_pattern" || \ 109 | "$frame" = *"$setup_pattern" || \ 110 | "$frame" = *"$teardown_pattern" ]]; then 111 | break 112 | else 113 | let index+=1 114 | fi 115 | done 116 | 117 | BATS_SOURCE="$(bats_frame_filename "${BATS_CURRENT_STACK_TRACE[0]}")" 118 | BATS_LINENO="$(bats_frame_lineno "${BATS_CURRENT_STACK_TRACE[0]}")" 119 | } 120 | 121 | bats_print_stack_trace() { 122 | local frame 123 | local index=1 124 | local count="${#@}" 125 | 126 | for frame in "$@"; do 127 | local filename="$(bats_trim_filename "$(bats_frame_filename "$frame")")" 128 | local lineno="$(bats_frame_lineno "$frame")" 129 | 130 | if [ $index -eq 1 ]; then 131 | echo -n "# (" 132 | else 133 | echo -n "# " 134 | fi 135 | 136 | local fn="$(bats_frame_function "$frame")" 137 | if [ "$fn" != "$BATS_TEST_NAME" ]; then 138 | echo -n "from function \`$fn' " 139 | fi 140 | 141 | if [ $index -eq $count ]; then 142 | echo "in test file $filename, line $lineno)" 143 | else 144 | echo "in file $filename, line $lineno," 145 | fi 146 | 147 | let index+=1 148 | done 149 | } 150 | 151 | bats_print_failed_command() { 152 | local frame="$1" 153 | local status="$2" 154 | local filename="$(bats_frame_filename "$frame")" 155 | local lineno="$(bats_frame_lineno "$frame")" 156 | 157 | local failed_line="$(bats_extract_line "$filename" "$lineno")" 158 | local failed_command="$(bats_strip_string "$failed_line")" 159 | echo -n "# \`${failed_command}' " 160 | 161 | if [ $status -eq 1 ]; then 162 | echo "failed" 163 | else 164 | echo "failed with status $status" 165 | fi 166 | } 167 | 168 | bats_frame_lineno() { 169 | local frame="$1" 170 | local lineno="${frame%% *}" 171 | echo "$lineno" 172 | } 173 | 174 | bats_frame_function() { 175 | local frame="$1" 176 | local rest="${frame#* }" 177 | local fn="${rest%% *}" 178 | echo "$fn" 179 | } 180 | 181 | bats_frame_filename() { 182 | local frame="$1" 183 | local rest="${frame#* }" 184 | local filename="${rest#* }" 185 | 186 | if [ "$filename" = "$BATS_TEST_SOURCE" ]; then 187 | echo "$BATS_TEST_FILENAME" 188 | else 189 | echo "$filename" 190 | fi 191 | } 192 | 193 | bats_extract_line() { 194 | local filename="$1" 195 | local lineno="$2" 196 | sed -n "${lineno}p" "$filename" 197 | } 198 | 199 | bats_strip_string() { 200 | local string="$1" 201 | printf "%s" "$string" | sed -e "s/^[ "$'\t'"]*//" -e "s/[ "$'\t'"]*$//" 202 | } 203 | 204 | bats_trim_filename() { 205 | local filename="$1" 206 | local length="${#BATS_CWD}" 207 | 208 | if [ "${filename:0:length+1}" = "${BATS_CWD}/" ]; then 209 | echo "${filename:length+1}" 210 | else 211 | echo "$filename" 212 | fi 213 | } 214 | 215 | bats_debug_trap() { 216 | if [ "$BASH_SOURCE" != "$1" ]; then 217 | bats_capture_stack_trace 218 | fi 219 | } 220 | 221 | bats_error_trap() { 222 | BATS_ERROR_STATUS="$?" 223 | BATS_ERROR_STACK_TRACE=( "${BATS_PREVIOUS_STACK_TRACE[@]}" ) 224 | trap - debug 225 | } 226 | 227 | bats_teardown_trap() { 228 | trap "bats_exit_trap" exit 229 | local status=0 230 | teardown >>"$BATS_OUT" 2>&1 || status="$?" 231 | 232 | if [ $status -eq 0 ]; then 233 | BATS_TEARDOWN_COMPLETED=1 234 | elif [ -n "$BATS_TEST_COMPLETED" ]; then 235 | BATS_ERROR_STATUS="$status" 236 | BATS_ERROR_STACK_TRACE=( "${BATS_CURRENT_STACK_TRACE[@]}" ) 237 | fi 238 | 239 | bats_exit_trap 240 | } 241 | 242 | bats_exit_trap() { 243 | local status 244 | local skipped 245 | trap - err exit 246 | 247 | skipped="" 248 | if [ -n "$BATS_TEST_SKIPPED" ]; then 249 | skipped=" # skip" 250 | if [ "1" != "$BATS_TEST_SKIPPED" ]; then 251 | skipped+=" ($BATS_TEST_SKIPPED)" 252 | fi 253 | fi 254 | 255 | if [ -z "$BATS_TEST_COMPLETED" ] || [ -z "$BATS_TEARDOWN_COMPLETED" ]; then 256 | echo "not ok $BATS_TEST_NUMBER $BATS_TEST_DESCRIPTION" >&3 257 | bats_print_stack_trace "${BATS_ERROR_STACK_TRACE[@]}" >&3 258 | bats_print_failed_command "${BATS_ERROR_STACK_TRACE[${#BATS_ERROR_STACK_TRACE[@]}-1]}" "$BATS_ERROR_STATUS" >&3 259 | sed -e "s/^/# /" < "$BATS_OUT" >&3 260 | status=1 261 | else 262 | echo "ok ${BATS_TEST_NUMBER}${skipped} ${BATS_TEST_DESCRIPTION}" >&3 263 | status=0 264 | fi 265 | 266 | rm -f "$BATS_OUT" 267 | exit "$status" 268 | } 269 | 270 | bats_perform_tests() { 271 | echo "1..$#" 272 | test_number=1 273 | status=0 274 | for test_name in "$@"; do 275 | "$0" $BATS_EXTENDED_SYNTAX "$BATS_TEST_FILENAME" "$test_name" "$test_number" || status=1 276 | let test_number+=1 277 | done 278 | exit "$status" 279 | } 280 | 281 | bats_perform_test() { 282 | BATS_TEST_NAME="$1" 283 | if [ "$(type -t "$BATS_TEST_NAME" || true)" = "function" ]; then 284 | BATS_TEST_NUMBER="$2" 285 | if [ -z "$BATS_TEST_NUMBER" ]; then 286 | echo "1..1" 287 | BATS_TEST_NUMBER="1" 288 | fi 289 | 290 | BATS_TEST_COMPLETED="" 291 | BATS_TEARDOWN_COMPLETED="" 292 | trap "bats_debug_trap \"\$BASH_SOURCE\"" debug 293 | trap "bats_error_trap" err 294 | trap "bats_teardown_trap" exit 295 | "$BATS_TEST_NAME" >>"$BATS_OUT" 2>&1 296 | BATS_TEST_COMPLETED=1 297 | 298 | else 299 | echo "bats: unknown test name \`$BATS_TEST_NAME'" >&2 300 | exit 1 301 | fi 302 | } 303 | 304 | if [ -z "$TMPDIR" ]; then 305 | BATS_TMPDIR="/tmp" 306 | else 307 | BATS_TMPDIR="${TMPDIR%/}" 308 | fi 309 | 310 | BATS_TMPNAME="$BATS_TMPDIR/bats.$$" 311 | BATS_PARENT_TMPNAME="$BATS_TMPDIR/bats.$PPID" 312 | BATS_OUT="${BATS_TMPNAME}.out" 313 | 314 | bats_preprocess_source() { 315 | BATS_TEST_SOURCE="${BATS_TMPNAME}.src" 316 | { tr -d '\r' < "$BATS_TEST_FILENAME"; echo; } | bats-preprocess > "$BATS_TEST_SOURCE" 317 | trap "bats_cleanup_preprocessed_source" err exit 318 | trap "bats_cleanup_preprocessed_source; exit 1" int 319 | } 320 | 321 | bats_cleanup_preprocessed_source() { 322 | rm -f "$BATS_TEST_SOURCE" 323 | } 324 | 325 | bats_evaluate_preprocessed_source() { 326 | if [ -z "$BATS_TEST_SOURCE" ]; then 327 | BATS_TEST_SOURCE="${BATS_PARENT_TMPNAME}.src" 328 | fi 329 | source "$BATS_TEST_SOURCE" 330 | } 331 | 332 | exec 3<&1 333 | 334 | if [ "$#" -eq 0 ]; then 335 | bats_preprocess_source 336 | bats_evaluate_preprocessed_source 337 | 338 | if [ -n "$BATS_COUNT_ONLY" ]; then 339 | echo "${#BATS_TEST_NAMES[@]}" 340 | else 341 | bats_perform_tests "${BATS_TEST_NAMES[@]}" 342 | fi 343 | else 344 | bats_evaluate_preprocessed_source 345 | bats_perform_test "$@" 346 | fi 347 | -------------------------------------------------------------------------------- /test/bats/libexec/bats-format-tap-stream: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # Just stream the TAP output (sans extended syntax) if tput is missing 5 | command -v tput >/dev/null || exec grep -v "^begin " 6 | 7 | header_pattern='[0-9]+\.\.[0-9]+' 8 | IFS= read -r header 9 | 10 | if [[ "$header" =~ $header_pattern ]]; then 11 | count="${header:3}" 12 | index=0 13 | failures=0 14 | skipped=0 15 | name="" 16 | count_column_width=$(( ${#count} * 2 + 2 )) 17 | else 18 | # If the first line isn't a TAP plan, print it and pass the rest through 19 | printf "%s\n" "$header" 20 | exec cat 21 | fi 22 | 23 | update_screen_width() { 24 | screen_width="$(tput cols)" 25 | count_column_left=$(( $screen_width - $count_column_width )) 26 | } 27 | 28 | trap update_screen_width WINCH 29 | update_screen_width 30 | 31 | begin() { 32 | go_to_column 0 33 | printf_with_truncation $(( $count_column_left - 1 )) " %s" "$name" 34 | clear_to_end_of_line 35 | go_to_column $count_column_left 36 | printf "%${#count}s/${count}" "$index" 37 | go_to_column 1 38 | } 39 | 40 | pass() { 41 | go_to_column 0 42 | printf " ✓ %s" "$name" 43 | advance 44 | } 45 | 46 | skip() { 47 | local reason="$1" 48 | [ -z "$reason" ] || reason=": $reason" 49 | go_to_column 0 50 | printf " - %s (skipped%s)" "$name" "$reason" 51 | advance 52 | } 53 | 54 | fail() { 55 | go_to_column 0 56 | set_color 1 bold 57 | printf " ✗ %s" "$name" 58 | advance 59 | } 60 | 61 | log() { 62 | set_color 1 63 | printf " %s\n" "$1" 64 | clear_color 65 | } 66 | 67 | summary() { 68 | printf "\n%d test%s" "$count" "$(plural "$count")" 69 | 70 | printf ", %d failure%s" "$failures" "$(plural "$failures")" 71 | 72 | if [ "$skipped" -gt 0 ]; then 73 | printf ", %d skipped" "$skipped" 74 | fi 75 | 76 | printf "\n" 77 | } 78 | 79 | printf_with_truncation() { 80 | local width="$1" 81 | shift 82 | local string="$(printf "$@")" 83 | 84 | if [ "${#string}" -gt "$width" ]; then 85 | printf "%s..." "${string:0:$(( $width - 4 ))}" 86 | else 87 | printf "%s" "$string" 88 | fi 89 | } 90 | 91 | go_to_column() { 92 | local column="$1" 93 | printf "\x1B[%dG" $(( $column + 1 )) 94 | } 95 | 96 | clear_to_end_of_line() { 97 | printf "\x1B[K" 98 | } 99 | 100 | advance() { 101 | clear_to_end_of_line 102 | echo 103 | clear_color 104 | } 105 | 106 | set_color() { 107 | local color="$1" 108 | local weight="$2" 109 | printf "\x1B[%d;%dm" $(( 30 + $color )) "$( [ "$weight" = "bold" ] && echo 1 || echo 22 )" 110 | } 111 | 112 | clear_color() { 113 | printf "\x1B[0m" 114 | } 115 | 116 | plural() { 117 | [ "$1" -eq 1 ] || echo "s" 118 | } 119 | 120 | _buffer="" 121 | 122 | buffer() { 123 | _buffer="${_buffer}$("$@")" 124 | } 125 | 126 | flush() { 127 | printf "%s" "$_buffer" 128 | _buffer="" 129 | } 130 | 131 | finish() { 132 | flush 133 | printf "\n" 134 | } 135 | 136 | trap finish EXIT 137 | 138 | while IFS= read -r line; do 139 | case "$line" in 140 | "begin "* ) 141 | let index+=1 142 | name="${line#* $index }" 143 | buffer begin 144 | flush 145 | ;; 146 | "ok "* ) 147 | skip_expr="ok $index # skip (\(([^)]*)\))?" 148 | if [[ "$line" =~ $skip_expr ]]; then 149 | let skipped+=1 150 | buffer skip "${BASH_REMATCH[2]}" 151 | else 152 | buffer pass 153 | fi 154 | ;; 155 | "not ok "* ) 156 | let failures+=1 157 | buffer fail 158 | ;; 159 | "# "* ) 160 | buffer log "${line:2}" 161 | ;; 162 | esac 163 | done 164 | 165 | buffer summary 166 | -------------------------------------------------------------------------------- /test/bats/libexec/bats-preprocess: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | encode_name() { 5 | local name="$1" 6 | local result="test_" 7 | 8 | if [[ ! "$name" =~ [^[:alnum:]\ _-] ]]; then 9 | name="${name//_/-5f}" 10 | name="${name//-/-2d}" 11 | name="${name// /_}" 12 | result+="$name" 13 | else 14 | local length="${#name}" 15 | local char i 16 | 17 | for ((i=0; i "$BATS_TMPDIR/test.txt" 29 | repo_run git-secrets --scan "$BATS_TMPDIR/test.txt" 30 | [ $status -eq 0 ] 31 | } 32 | 33 | @test "Scans all files when no file provided" { 34 | setup_bad_repo 35 | repo_run git-secrets --scan 36 | [ $status -eq 1 ] 37 | } 38 | 39 | @test "Scans all files including history" { 40 | setup_bad_repo 41 | repo_run git-secrets --scan-history 42 | [ $status -eq 1 ] 43 | } 44 | 45 | @test "Scans all files when no file provided with secret in history" { 46 | setup_bad_repo_history 47 | repo_run git-secrets --scan 48 | [ $status -eq 0 ] 49 | } 50 | 51 | @test "Scans all files including history with secret in history" { 52 | setup_bad_repo_history 53 | repo_run git-secrets --scan-history 54 | [ $status -eq 1 ] 55 | } 56 | 57 | @test "Scans history with secrets distributed among branches in history" { 58 | cd $TEST_REPO 59 | echo '@todo' > $TEST_REPO/history_failure.txt 60 | git add -A 61 | git commit -m "Testing history" 62 | echo 'todo' > $TEST_REPO/history_failure.txt 63 | git add -A 64 | git commit -m "Testing history" 65 | git checkout -b testbranch 66 | echo '@todo' > $TEST_REPO/history_failure.txt 67 | git add -A 68 | git commit -m "Testing history" 69 | git checkout master 70 | cd - 71 | repo_run git-secrets --scan-history 72 | [ $status -eq 1 ] 73 | } 74 | 75 | @test "Scans recursively" { 76 | setup_bad_repo 77 | mkdir -p $TEST_REPO/foo/bar/baz 78 | echo '@todo more stuff' > $TEST_REPO/foo/bar/baz/data.txt 79 | repo_run git-secrets --scan -r $TEST_REPO/foo 80 | [ $status -eq 1 ] 81 | } 82 | 83 | @test "Scans recursively only if -r is given" { 84 | setup_bad_repo 85 | mkdir -p $TEST_REPO/foo/bar/baz 86 | echo '@todo more stuff' > $TEST_REPO/foo/bar/baz/data.txt 87 | repo_run git-secrets --scan $TEST_REPO/foo 88 | [ $status -eq 0 ] 89 | } 90 | 91 | @test "Excludes allowed patterns from failures" { 92 | git config --add secrets.patterns 'foo="baz{1,5}"' 93 | git config --add secrets.allowed 'foo="bazzz"' 94 | echo 'foo="bazzz" is ok because 3 "z"s' > "$BATS_TMPDIR/test.txt" 95 | repo_run git-secrets --scan "$BATS_TMPDIR/test.txt" 96 | [ $status -eq 0 ] 97 | echo 'This is NOT: ok foo="bazzzz"' > "$BATS_TMPDIR/test.txt" 98 | repo_run git-secrets --scan "$BATS_TMPDIR/test.txt" 99 | [ $status -eq 1 ] 100 | } 101 | 102 | @test "Prohibited matches exits 1" { 103 | file="$TEST_REPO/test.txt" 104 | echo '@todo stuff' > $file 105 | echo 'this is forbidden right?' >> $file 106 | repo_run git-secrets --scan $file 107 | [ $status -eq 1 ] 108 | [ "${lines[0]}" == "$file:1:@todo stuff" ] 109 | [ "${lines[1]}" == "$file:2:this is forbidden right?" ] 110 | } 111 | 112 | @test "Only matches on word boundaries" { 113 | file="$TEST_REPO/test.txt" 114 | # Note that the following does not match as it is not a word. 115 | echo 'mesa Jar Jar Binks' > $file 116 | # The following do match because they are in word boundaries. 117 | echo 'foo.me' >> $file 118 | echo '"me"' >> $file 119 | repo_run git-secrets --scan $file 120 | [ $status -eq 1 ] 121 | [ "${lines[0]}" == "$file:2:foo.me" ] 122 | [ "${lines[1]}" == "$file:3:\"me\"" ] 123 | } 124 | 125 | @test "Can scan from stdin using -" { 126 | echo "foo" | "${BATS_TEST_DIRNAME}/../git-secrets" --scan - 127 | echo "me" | "${BATS_TEST_DIRNAME}/../git-secrets" --scan - && exit 1 || true 128 | } 129 | 130 | @test "installs hooks for repo" { 131 | setup_bad_repo 132 | repo_run git-secrets --install $TEST_REPO 133 | [ -f $TEST_REPO/.git/hooks/pre-commit ] 134 | [ -f $TEST_REPO/.git/hooks/prepare-commit-msg ] 135 | [ -f $TEST_REPO/.git/hooks/commit-msg ] 136 | } 137 | 138 | @test "fails if hook exists and no -f" { 139 | repo_run git-secrets --install $TEST_REPO 140 | repo_run git-secrets --install $TEST_REPO 141 | [ $status -eq 1 ] 142 | } 143 | 144 | @test "Overwrites hooks if -f is given" { 145 | repo_run git-secrets --install $TEST_REPO 146 | repo_run git-secrets --install -f $TEST_REPO 147 | [ $status -eq 0 ] 148 | } 149 | 150 | @test "installs hooks for repo with Debian style directories" { 151 | setup_bad_repo 152 | mkdir $TEST_REPO/.git/hooks/pre-commit.d 153 | mkdir $TEST_REPO/.git/hooks/prepare-commit-msg.d 154 | mkdir $TEST_REPO/.git/hooks/commit-msg.d 155 | run git-secrets --install $TEST_REPO 156 | [ -f $TEST_REPO/.git/hooks/pre-commit.d/git-secrets ] 157 | [ -f $TEST_REPO/.git/hooks/prepare-commit-msg.d/git-secrets ] 158 | [ -f $TEST_REPO/.git/hooks/commit-msg.d/git-secrets ] 159 | } 160 | 161 | @test "installs hooks to template directory" { 162 | setup_bad_repo 163 | run git-secrets --install $TEMPLATE_DIR 164 | [ $status -eq 0 ] 165 | run git init --template $TEMPLATE_DIR 166 | [ $status -eq 0 ] 167 | [ -f "${TEST_REPO}/.git/hooks/pre-commit" ] 168 | [ -f "${TEST_REPO}/.git/hooks/prepare-commit-msg" ] 169 | [ -f "${TEST_REPO}/.git/hooks/commit-msg" ] 170 | } 171 | 172 | @test "Scans using keys from credentials file" { 173 | echo 'aws_access_key_id = abc123' > $BATS_TMPDIR/test.ini 174 | echo 'aws_secret_access_key=foobaz' >> $BATS_TMPDIR/test.ini 175 | echo 'aws_access_key_id = "Bernard"' >> $BATS_TMPDIR/test.ini 176 | echo 'aws_secret_access_key= "Laverne"' >> $BATS_TMPDIR/test.ini 177 | echo 'aws_access_key_id= Hoagie+man' >> $BATS_TMPDIR/test.ini 178 | cd $TEST_REPO 179 | run git secrets --aws-provider $BATS_TMPDIR/test.ini 180 | [ $status -eq 0 ] 181 | echo "$output" | grep -F "foobaz" 182 | echo "$output" | grep -F "abc123" 183 | echo "$output" | grep -F "Bernard" 184 | echo "$output" | grep -F "Laverne" 185 | echo "$output" | grep -F 'Hoagie\+man' 186 | run git secrets --add-provider -- git secrets --aws-provider $BATS_TMPDIR/test.ini 187 | [ $status -eq 0 ] 188 | echo '(foobaz) test' > $TEST_REPO/bad_file 189 | echo "abc123 test" >> $TEST_REPO/bad_file 190 | echo 'Bernard test' >> $TEST_REPO/bad_file 191 | echo 'Laverne test' >> $TEST_REPO/bad_file 192 | echo 'Hoagie+man test' >> $TEST_REPO/bad_file 193 | repo_run git-secrets --scan $TEST_REPO/bad_file 194 | [ $status -eq 1 ] 195 | echo "$output" | grep "foobaz" 196 | echo "$output" | grep "abc123" 197 | echo "$output" | grep "Bernard" 198 | echo "$output" | grep "Laverne" 199 | echo "$output" | grep -F 'Hoagie+man' 200 | } 201 | 202 | @test "Lists secrets for a repo" { 203 | repo_run git-secrets --list 204 | [ $status -eq 0 ] 205 | echo "$output" | grep -F 'secrets.patterns @todo' 206 | echo "$output" | grep -F 'secrets.patterns forbidden|me' 207 | } 208 | 209 | @test "Adds secrets to a repo and de-dedupes" { 210 | repo_run git-secrets --add 'testing+123' 211 | [ $status -eq 0 ] 212 | repo_run git-secrets --add 'testing+123' 213 | [ $status -eq 1 ] 214 | repo_run git-secrets --add --literal 'testing+abc' 215 | [ $status -eq 0 ] 216 | repo_run git-secrets --add -l 'testing+abc' 217 | [ $status -eq 1 ] 218 | repo_run git-secrets --list 219 | echo "$output" | grep -F 'secrets.patterns @todo' 220 | echo "$output" | grep -F 'secrets.patterns forbidden|me' 221 | echo "$output" | grep -F 'secrets.patterns testing+123' 222 | echo "$output" | grep -F 'secrets.patterns testing\+abc' 223 | } 224 | 225 | @test "Adds allowed patterns to a repo and de-dedupes" { 226 | repo_run git-secrets --add -a 'testing+123' 227 | [ $status -eq 0 ] 228 | repo_run git-secrets --add --allowed 'testing+123' 229 | [ $status -eq 1 ] 230 | repo_run git-secrets --add -a -l 'testing+abc' 231 | [ $status -eq 0 ] 232 | repo_run git-secrets --add -a -l 'testing+abc' 233 | [ $status -eq 1 ] 234 | repo_run git-secrets --list 235 | echo "$output" | grep -F 'secrets.patterns @todo' 236 | echo "$output" | grep -F 'secrets.patterns forbidden|me' 237 | echo "$output" | grep -F 'secrets.allowed testing+123' 238 | echo "$output" | grep -F 'secrets.allowed testing\+abc' 239 | } 240 | 241 | @test "Empty lines must be ignored in .gitallowed files" { 242 | setup_bad_repo 243 | echo '' >> $TEST_REPO/.gitallowed 244 | repo_run git-secrets --scan 245 | [ $status -eq 1 ] 246 | } 247 | 248 | @test "Comment lines must be ignored in .gitallowed files" { 249 | setup_bad_repo_with_hash 250 | repo_run git-secrets --scan 251 | [ $status -eq 1 ] 252 | echo '#hash' > $TEST_REPO/.gitallowed 253 | repo_run git-secrets --scan 254 | [ $status -eq 1 ] 255 | echo 'hash' > $TEST_REPO/.gitallowed 256 | repo_run git-secrets --scan 257 | [ $status -eq 0 ] 258 | } 259 | 260 | @test "Scans all files and allowing none of the bad patterns in .gitallowed" { 261 | setup_bad_repo 262 | echo 'hello' > $TEST_REPO/.gitallowed 263 | repo_run git-secrets --scan 264 | [ $status -eq 1 ] 265 | } 266 | 267 | @test "Scans all files and allowing all bad patterns in .gitallowed" { 268 | setup_bad_repo 269 | echo '@todo' > $TEST_REPO/.gitallowed 270 | echo 'forbidden' >> $TEST_REPO/.gitallowed 271 | echo 'me' >> $TEST_REPO/.gitallowed 272 | repo_run git-secrets --scan 273 | [ $status -eq 0 ] 274 | } 275 | 276 | @test "Adds common AWS patterns" { 277 | repo_run git config --unset-all secrets 278 | repo_run git-secrets --register-aws 279 | git config --local --get secrets.providers 280 | repo_run git-secrets --list 281 | echo "$output" | grep -F '(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}' 282 | echo "$output" | grep "AKIAIOSFODNN7EXAMPLE" 283 | echo "$output" | grep "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" 284 | } 285 | 286 | @test "Adds providers" { 287 | repo_run git-secrets --add-provider -- echo foo baz bar 288 | [ $status -eq 0 ] 289 | repo_run git-secrets --add-provider -- echo bam 290 | [ $status -eq 0 ] 291 | repo_run git-secrets --list 292 | echo "$output" | grep -F 'echo foo baz bar' 293 | echo "$output" | grep -F 'echo bam' 294 | echo 'foo baz bar' > $TEST_REPO/bad_file 295 | echo 'bam' >> $TEST_REPO/bad_file 296 | repo_run git-secrets --scan $TEST_REPO/bad_file 297 | [ $status -eq 1 ] 298 | echo "$output" | grep -F 'foo baz bar' 299 | echo "$output" | grep -F 'bam' 300 | } 301 | 302 | @test "Strips providers that return nothing" { 303 | repo_run git-secrets --add-provider -- 'echo' 304 | [ $status -eq 0 ] 305 | repo_run git-secrets --add-provider -- 'echo 123' 306 | [ $status -eq 0 ] 307 | repo_run git-secrets --list 308 | echo "$output" | grep -F 'echo 123' 309 | echo 'foo' > $TEST_REPO/bad_file 310 | repo_run git-secrets --scan $TEST_REPO/bad_file 311 | [ $status -eq 0 ] 312 | } 313 | 314 | @test "--recursive cannot be used with SCAN_*" { 315 | repo_run git-secrets --scan -r --cached 316 | [ $status -eq 1 ] 317 | repo_run git-secrets --scan -r --no-index 318 | [ $status -eq 1 ] 319 | repo_run git-secrets --scan -r --untracked 320 | [ $status -eq 1 ] 321 | } 322 | 323 | @test "--recursive can be used with --scan" { 324 | repo_run git-secrets --scan -r 325 | [ $status -eq 0 ] 326 | } 327 | 328 | @test "--recursive can't be used with --list" { 329 | repo_run git-secrets --list -r 330 | [ $status -eq 1 ] 331 | } 332 | 333 | @test "-f can only be used with --install" { 334 | repo_run git-secrets --scan -f 335 | [ $status -eq 1 ] 336 | } 337 | 338 | @test "-a can only be used with --add" { 339 | repo_run git-secrets --scan -a 340 | [ $status -eq 1 ] 341 | } 342 | 343 | @test "-l can only be used with --add" { 344 | repo_run git-secrets --scan -l 345 | [ $status -eq 1 ] 346 | } 347 | 348 | @test "--cached can only be used with --scan" { 349 | repo_run git-secrets --list --cached 350 | [ $status -eq 1 ] 351 | } 352 | 353 | @test "--no-index can only be used with --scan" { 354 | repo_run git-secrets --list --no-index 355 | [ $status -eq 1 ] 356 | } 357 | 358 | @test "--untracked can only be used with --scan" { 359 | repo_run git-secrets --list --untracked 360 | [ $status -eq 1 ] 361 | } 362 | -------------------------------------------------------------------------------- /test/pre-commit.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | load test_helper 3 | 4 | @test "Rejects commits with prohibited patterns in changeset" { 5 | setup_bad_repo 6 | repo_run git-secrets --install $TEST_REPO 7 | cd $TEST_REPO 8 | run git commit -m 'Contents are bad not the message' 9 | [ $status -eq 1 ] 10 | [ "${lines[0]}" == "data.txt:1:@todo more stuff" ] 11 | [ "${lines[1]}" == "failure1.txt:1:another line... forbidden" ] 12 | [ "${lines[2]}" == "failure2.txt:1:me" ] 13 | } 14 | 15 | @test "Rejects commits with prohibited patterns in changeset with filename that contain spaces" { 16 | setup_bad_repo_with_spaces 17 | repo_run git-secrets --install $TEST_REPO 18 | cd $TEST_REPO 19 | run git commit -m 'Contents are bad not the message' 20 | [ $status -eq 1 ] 21 | [ "${lines[0]}" == "da ta.txt:1:@todo more stuff" ] 22 | } 23 | 24 | @test "Scans staged files" { 25 | cd $TEST_REPO 26 | repo_run git-secrets --install $TEST_REPO 27 | echo '@todo more stuff' > $TEST_REPO/data.txt 28 | echo 'hi there' > $TEST_REPO/ok.txt 29 | git add -A 30 | echo 'fixed the working directory, but not staged' > $TEST_REPO/data.txt 31 | run git commit -m 'Contents are bad not the message' 32 | [ $status -eq 1 ] 33 | [ "${lines[0]}" == "data.txt:1:@todo more stuff" ] 34 | } 35 | 36 | @test "Allows commits that do not match prohibited patterns" { 37 | setup_good_repo 38 | repo_run git-secrets --install $TEST_REPO 39 | cd $TEST_REPO 40 | run git commit -m 'This is fine' 41 | [ $status -eq 0 ] 42 | # Ensure deleted files are filtered out of the grep 43 | rm $TEST_REPO/data.txt 44 | echo 'aaa' > $TEST_REPO/data_2.txt 45 | run git add -A 46 | run git commit -m 'This is also fine' 47 | [ $status -eq 0 ] 48 | } 49 | 50 | @test "Rejects commits with prohibited patterns in changeset when AWS provider is enabled" { 51 | setup_bad_repo 52 | repo_run git-secrets --install $TEST_REPO 53 | repo_run git-secrets --register-aws $TEST_REPO 54 | cd $TEST_REPO 55 | run git commit -m 'Contents are bad not the message' 56 | [ $status -eq 1 ] 57 | echo "${lines}" | grep -vq 'git secrets --aws-provider: command not found' 58 | 59 | [ "${lines[0]}" == "data.txt:1:@todo more stuff" ] 60 | [ "${lines[1]}" == "failure1.txt:1:another line... forbidden" ] 61 | [ "${lines[2]}" == "failure2.txt:1:me" ] 62 | } 63 | -------------------------------------------------------------------------------- /test/prepare-commit-msg.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | load test_helper 3 | 4 | @test "Rejects merges with prohibited patterns in history" { 5 | setup_good_repo 6 | repo_run git-secrets --install $TEST_REPO 7 | cd $TEST_REPO 8 | git commit -m 'OK' 9 | git checkout -b feature 10 | echo '@todo' > data.txt 11 | git add -A 12 | git commit -m 'Bad commit' --no-verify 13 | echo 'Fixing!' > data.txt 14 | git add -A 15 | git commit -m 'Fixing commit' 16 | git checkout master 17 | run git merge --no-ff feature 18 | [ $status -eq 1 ] 19 | } 20 | 21 | @test "Allows merges that do not match prohibited patterns" { 22 | setup_good_repo 23 | cd $TEST_REPO 24 | repo_run git-secrets --install 25 | git commit -m 'OK' 26 | git checkout -b feature 27 | echo 'Not bad' > data.txt 28 | git add -A 29 | git commit -m 'Good commit' 30 | git checkout master 31 | run git merge --no-ff feature 32 | [ $status -eq 0 ] 33 | } 34 | -------------------------------------------------------------------------------- /test/test_helper.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export TEST_REPO="$BATS_TMPDIR/test-repo" 3 | export TEMP_HOME="$BATS_TMPDIR/home" 4 | export TEMPLATE_DIR="${BATS_TMPDIR}/template" 5 | INITIAL_PATH="${PATH}" 6 | INITIAL_HOME=${HOME} 7 | 8 | setup() { 9 | setup_repo 10 | [ -d "${TEMPLATE_DIR}" ] && rm -rf "${TEMPLATE_DIR}" 11 | [ -d "${TEMP_HOME}" ] && rm -rf "${TEMP_HOME}" 12 | mkdir -p $TEMP_HOME 13 | export HOME=$TEMP_HOME 14 | export PATH="${BATS_TEST_DIRNAME}/..:${INITIAL_PATH}" 15 | cd $TEST_REPO 16 | } 17 | 18 | teardown() { 19 | delete_repo 20 | export PATH="${INITIAL_PATH}" 21 | export HOME="${INITIAL_HOME}" 22 | [ -d "${TEMP_HOME}" ] && rm -rf "${TEMP_HOME}" 23 | } 24 | 25 | delete_repo() { 26 | [ -d $TEST_REPO ] && rm -rf $TEST_REPO || true 27 | } 28 | 29 | setup_repo() { 30 | delete_repo 31 | mkdir -p $TEST_REPO 32 | cd $TEST_REPO 33 | git init --initial-branch=master 34 | git config --local --add secrets.patterns '@todo' 35 | git config --local --add secrets.patterns 'forbidden|me' 36 | git config --local --add secrets.patterns '#hash' 37 | git config --local user.email "you@example.com" 38 | git config --local user.name "Your Name" 39 | cd - 40 | } 41 | 42 | repo_run() { 43 | cmd="$1" 44 | shift 45 | cd "${TEST_REPO}" 46 | run "${BATS_TEST_DIRNAME}/../${cmd}" $@ 47 | cd - 48 | } 49 | 50 | # Creates a repo that should fail 51 | setup_bad_repo() { 52 | cd $TEST_REPO 53 | echo '@todo more stuff' > $TEST_REPO/data.txt 54 | echo 'hi there' > $TEST_REPO/ok.txt 55 | echo 'another line... forbidden' > $TEST_REPO/failure1.txt 56 | echo 'me' > $TEST_REPO/failure2.txt 57 | git add -A 58 | cd - 59 | } 60 | 61 | # Creates a repo that should fail 62 | setup_bad_repo_with_spaces() { 63 | cd $TEST_REPO 64 | echo '@todo more stuff' > "$TEST_REPO/da ta.txt" 65 | git add -A 66 | cd - 67 | } 68 | 69 | # Creates a repo that should fail 70 | setup_bad_repo_with_hash() { 71 | cd $TEST_REPO 72 | echo '#hash' > "$TEST_REPO/data.txt" 73 | git add -A 74 | cd - 75 | } 76 | 77 | # Creates a repo that should fail 78 | setup_bad_repo_history() { 79 | cd $TEST_REPO 80 | echo '@todo' > $TEST_REPO/history_failure.txt 81 | git add -A 82 | git commit -m "Testing history" 83 | echo 'todo' > $TEST_REPO/history_failure.txt 84 | git add -A 85 | cd - 86 | } 87 | 88 | # Creates a repo that does not fail 89 | setup_good_repo() { 90 | cd $TEST_REPO 91 | echo 'hello!' > $TEST_REPO/data.txt 92 | git add -A 93 | cd - 94 | } 95 | --------------------------------------------------------------------------------