/git-hooks

A CLI tool to implement git custom hooks

Primary LanguagePythonApache License 2.0Apache-2.0

Git Hooks CLI tool for custom hooks

A CLI too to implement git-hooks in a automated fashion. It uses the normal git hooks flow. A great tool when studying or attending boot-camps. The implemented mask hook will protect you from committing your predefined sensitive secrets to your Github repo.

This is still a proof of concept. I've created it for educational purposes. Further work is still needed.

How git hooks work:

When using git, it can execute scripts before and after some git commands. You can read all about them form here.

The pre-commit hook is run first, before you even type in a commit message. It’s used to inspect the snapshot that’s about to be committed.

Currently Supported Hooks:

  • mask: A check that is run before a git git commit command to mask the user's pre-defined sensitive data.
  • (more are yet to come 😉)

Setup and usage:

  1. The cli tool is written in Python which will need Python installed on your system.
  2. Clone this repo into your local machine
git clone https://github.com/FadyGrAb/git-hooks.git
  1. cd into to the package directory
cd git-hooks
  1. Install the package using pip. This will install it in for the current Python environment.
pip install .
  1. To show the tool's help, use
git-hooks --help

or to show commands related help, use

git-hooks COMMAND --help
  1. List the currently supported hooks
git-hooks list

(currently only the mask hook is supported)

  1. Initiate a hook with the init command.You must execute this inside a git repo. For the mask hook, This will create the pre-commit hook inside your repo's .git/hooks directory, create the mask.config in your repo's root directory and add _unmasked_* and \mask.config to your .gitignore file. If you want to commit your mask.config, remove it from .gitignore (⚠️not recommended if you wrote your secrets in plaintext inside it)
git-hooks init HOOK
  1. Disable a hook with the disable command. The hook must be initiated first.
git-hooks disable HOOK
  1. Enable a hook with the enable command.
git-hooks enable HOOK
  1. Get the status of your hooks with the status command. The status is either Enabled, Disabled or Not initialized.
git-hooks status
  1. Execute the hook in a "reverse" fashion (reverse its effect) by using the -r/--reverse option in the exec command. You can use the -f/--file option to target a single file with the hook effect. If the -f/--file option isn't specified, the hook will be reversed on all the files in the .ghunmask in the repo's root.
git-hooks exec -rf /path/to/file HOOK

The Mask Git-hook:

Motivation:

When I commit code to public repos, I usually mask my sensitive data manually which is not practical nor scalable. So I needed a way to automate that at each git commit.

Implementation details:

It's actually a straight forward process, the "pre-commit" script in your project's .git/hooks directory will read the "mask.config" config file which is basically telling the script what and how to mask your data with an optional ignore files list that won't be skipped from the checks.
The script will check only the modified files. If it finds data that needs to be masked, it will make a copy of original unmasked file adding the "*_unmasked_*" prefix to the file name in the same directory and then mask the original. That way you will always have access to the unmasked data which is ignored by git as the "_unmasked_*" entry is already in .gitignore.
You don't have to put any files in the .git/hooks directory, the cli tool will do it for you.

Mask.conig structure:

[show]                      # The sensitive data you want to mask.
1234565678 = 4              # This will show only the last 4 characters i.e. "****5678"
lksjdfljalkjdfllkjsa = 0    # This will show 0 characters i.e. full mask "********************"
{{ ENV_VAR }} = 4           # You can reference environment variables inside the file.

[ignore]                    # The list of files to ignore
files=["ignoreme.html", "ignoreme2.html"]

You write your secrets in the [show] table and specify how many characters you want to show from them (from the right). If you write 0, it will be a full mask. To reference environnement variables, put the variable's name inside a pair of curly braces {{ <variable name> }}. If the variable is not set, it will be ignored and a warning message will show up. The mask.config file almost resembles toml syntax.
💡 If you have referenced all your secrets with env vars, you can safely remove mask.config from .gitignore and commit it.

To activate the mask git hook script, just commit as usual (if there are untracked files, you should add them first to the git staging area git add example.file)

git commit -am "test commit"

Mask Git hook Example:

  1. Install the tool as Setup and usage section.
  2. Create a new directory and CD into it
mkdir my-project
cd my-project
  1. Run git init in this directory. Notice the new .git directory that is created.
  2. Run git-hooks init mask.
  3. Edit the "mask.config" as follows (located in your repo's root directory):
[show]
123456789 = 4            
00f0264d065dd4ee7922dfe6 = 0
{{  SECRET_API_KEY  }} = 8

[ignore]
files=[]
  1. Set the SECRET_API_KEY env var:
# bash
export SECRET_API_KEY="laskdjflkasjdfloiaudsfiuo"
# windows powershell
$ENV:SECRET_API_KEY="laskdjflkasjdfloiaudsfiuo"
  1. Add the following file (config.json) to your project's root directory:
{
    "MyAccountNumber": "123456789",
    "MySecretKey": "00f0264d065dd4ee7922dfe6",
    "ApiKey": "laskdjflkasjdfloiaudsfiuo"
}
  1. Run git add .
  2. Commit your changes git commit -m "mask commit"
  3. After the commit you will get the a message for every file that was processed If you didn't receive any, then none contained sensitive data matching your configuration. Your file will be as follows:
{
    "MyAccountNumber": "*****6789",
    "MySecretKey": "************************",
    "ApiKey": "*****************audsfiuo"
}

Testing:

The test scripts are using the pytest package. to install it:

pip install pytest

To run the tests:

pytest -v ./tests

Things to consider:

  • This tool is developed on a windows machine and supposed to be os agnostic. I did't fully test it on other OSs.
  • This script lacks proper exception handling (for now) so please don't strain it too much :)
  • To disable this script, just rename it or add a file extension to it.
  • You can add the full path of a file to be ignored in the [ignore] table starting from your project's root directory.
  • The .git directory isn't pushed to Github with a push, check here. So in theory, my sensitive data should be safe. But further research is needed.

Known issues:

  • For the mask hook, if there are 2 or more secrets with the same length that are completely masked (show = 0), then the reverse-masked operation might not return the correct value.

Further work to be done:

  • Proper exception handling.
  • Packaging.
  • Testing.
  • Add more features to mask.config.