Git Hooks with Xygeni

A git hook is a custom action that is launched when certain events occur in the Git version control system.

Hooks are stored in the .git/hooks directory. Hooks could be run client-side or server-side. Client-side hooks are triggered by operations such as committing and merging, while server-side hooks run on network operations such as receiving pushed commits. The hook is a script named under the event that triggers its execution. For example, pre-commit runs for git commit command, before the commit message is set.

Server side hooks like pre-receive could be useful for scanning changes for security issues before a push is accepted, but they could be registered only when you operate the Git remote host.

The client-side hooks useful for our purposes are:

  • pre-commit, which could be used to inspect the snapshot that is about to be committed, by running the scanner at the client-side. This is the recommended way to perform a scan and block commits with important security issues.

  • post-commit, where results from a previous scan could be processed or sent to some recipients. This could be also done in the pre-commit hook, but here the commit was complete.

  • pre-push runs during git push, before transferring any objects. It could be used to scan a repository before a reference is pushed to a remote.

The server-side events useful for our purposes are:

  • pre-receive, when handling a push from a client. The Xygeni scanner can reject a push based on issues found. The stdin contains a line for each ref to update: <old-value> <new-value> <ref-name>

Hook scripts typically follow the convention on exit codes: 0 means 'success', non-zero means 'failure' and often aborts the git operation.

Client-side pre-commit hooks are recommended to detect and block unintentional severity issues like secret leaks. Server side pre-receive hooks can block the repository to integrate commits pushed, but often cloud-based source code management (SCM) systems like GitHub do not allow hooking on such events. You may add the scan as a 'detector'. This is a limitation of the on-cloud SCM itself, and not a limitation of the Xygeni scanner. Server-side hooks are usually limited to self-managed or on-premises editions.

Sharing Git Hooks

Please remind that git hooks are not copied when a repository is cloned.

To share hook scripts it is necessary to (1) have a convention for sharing tooling data at the project and organization scopes, and (2) a mechanism to sync the tooling data in each developer workstation.

For example, you may keep tooling under a ./dev project directory as a matter of convention, placed under version control.

Take special care of not hard-coding secrets and other sensitive information in the tooling configuration files.

Git hook scripts are critical code that needs to be protected against code tampering. It is recommended to use special measures when changes are needed, like security reviews before allowing mergeing any commit with modifications.

If the Git hook scripts are stored e.g. in ./dev/hooks directory, and not too old git version is used (git >= 2.9), you may set the git core.hooksPath configuration, with git config core.hooksPath ./dev/hooks or a function like this:

install_hooks() {
  git config core.hooksPath \
    || git config core.hooksPath ./dev/hooks
}

Refer to Keeping Git Hooks in Sync blog for more details.

Running scanner as a gate in a git hook

To run Xygeni scanner at client-side, before a commit is applied, you may add a simple pre-commit executable script like this (example for Linux and macOS):

#!/usr/bin/env bash

RED='\033[1;31m' # Bold red
GRAY='\033[1;90m' # Bold gray
NC='\033[0m' # No Color

# Add Xygeni scanner location to the PATH (if not in PATH already)
PATH=${PATH}:${XYGENI_HOME}

if ! [ -x "$(command -v xygeni)" ]
then
    echo -e "${RED}Xygeni scanner could not be found.${NC} Please make sure Xygeni is installed properly."
    exit 1
fi

# Run secrets scan on the files changed in the commit ("staged") only
# "no new secrets of high+ severity":
xygeni -q secrets -d . --staged-files --fail-on=high --no-stdin --format text --output ./.secrets.txt
EXIT_CODE=$?

if [[ $EXIT_CODE -gt 127 ]]; then
  # Print secrets found if anu
  echo -e "${RED}Secrets found !${NC} Please remove them before committing changes."
  cat ./.secrets.txt
elif [[ $EXIT_CODE -ne 0 ]]; then
  echo -e "${RED}Xygeni scan failed.${NC} Please contact your Xygeni administrator."
  echo -e "Log files can be found in ${GRAY}logs/noproject/secrets_noproject.log${NC}"
fi

# Cleanup and tell the git commit to stop (not 0) or continue (0)
rm -f ./.secrets.txt >/dev/null 2>&1
exit $EXIT_CODE

The --fail-on option is configured to make the build fail when at least one issue with critical or high severity is found (so the scanner acts as a security gate).

For running the scan as an optional pipeline check, use --fail-on=never instead.

The --no-stdin option is recommended to avoid the scanner to get the files to process from the standard input. Under some environments, this could make the scanner to analyze no files at all.

This example is a similar security gatekeeper, but commits are allowed only when no scan of the given types has a critical issue (only the scanner execution is shown, for brevity):

#!/usr/bin/env bash

${XYGENI_HOME}/xygeni scan --fail-on=critical --no-upload --run=secrets,misconf,iac \
  --format=text --output=.report.txt

When a critical issue is detected in any of the scans for secrets, misconfigurations or IaC flaws, the commit will be aborted.

Using pre-commit framework

There are some popular frameworks for improving the hook mechanism provided by Git, mainly aimed at sharing the scripts and simplifying the configuration when multiple actions need to be added for a git event.

One of the most popular frameworks is pre-commit. The list of actions required are listed in a YAML file, and `pre-commit manages the installation and execution of scripts written in any language.

Follow the instructions given in pre-commit quick start for installing the framework. Essentially you need python and pip, and run pip install pre-commit in the node where the git commit will be intercepted.

Then add the following entry to you .pre-commit-config.yaml:

# ... Other configuration properties ...
repos:
  # ... more repos and hooks ...

  - repo: https://github.com/xygeni/xygeni-action
    rev: v2.1
    hooks:
    - id: xygeni
      stages: [commit]
      args: [
        '-q', 'scan', '--format=text',
        '--run=secrets,suspectdeps,misconf,iac,codetamper,compliance',
        '--fail-on=critical']

Or, for running the scanner Docker image:

- repo: git://github.com/xygeni/xygeni-action
  rev: v1.2
  hooks:
  - id: xygeni-docker
    stages: [commit]
    args: [
      '-q', 'scan', '--format=text',
      '--run=secrets,suspectdeps,misconf,iac,codetamper,compliance',
      '--fail-on=critical']

Then install the git hook scripts using

# You may test the hook configuration with
#   pre-commit try-repo or pre-commit run
pre-commit install

Xygeni scanner will run before each commit. The configuration given above works for setting Xygeni as a security gate to avoid commits having critical security issues. To avoid break the builds, --fail-on=never could be used instead.

git commit -m "this commit contains issues!"

  Running xygeni (script) scan ........Failed!
  ...

To disable the pre-commit hook, for example when after analysis the findings found were deemed as false positives or on inactive / no-risk issues, use SKIP=xygeni in front of the git command: ---

SKIP=xygeni git commit -m "this commit contains issues!"
    xygeni SKIPPED...

Last updated