DevSecOps

Pre-commit Hooks for Security: Stop Vulnerabilities Before They Commit

How to run security checks automatically at commit time using git hooks and the CodeSlick CLI

What Are Pre-commit Hooks

Pre-commit hooks are scripts that run automatically before Git records a commit. They live in the .git/hooks/ directory and execute every time a developer runs git commit, before the commit is written to history. If the hook exits with a non-zero status, the commit is rejected and the developer sees the output explaining what failed.

Git supports multiple hook events. The pre-commit hook is the most valuable for security because it runs against staged changes — the exact code that is about to enter the repository. Unlike CI/CD pipelines, pre-commit hooks run locally on every developer's machine before code ever leaves their environment.

The security benefit is fundamental: a secret caught before commit never reaches the repository. A SQL injection pattern caught before commit cannot be deployed to production. The earlier a vulnerability is caught in the development cycle, the cheaper it is to fix. Pre-commit is earlier than any other automated gate.

Why Pre-commit Is the Earliest Security Gate

Security gates in a typical development workflow run at four points:

  1. IDE (real-time): Linter warnings as you type — fast but limited to style and obvious errors
  2. Pre-commit (local, before push): Security scanning against staged changes — catches vulnerabilities before they enter the repository
  3. CI/CD (remote, after push): Full test suite and SAST scanning — catches issues but requires a push, meaning the vulnerable code is already in the repository
  4. Production monitoring: Runtime detection — too late, the vulnerability is already live

Pre-commit occupies the critical position where code has been written and reviewed by the developer but has not yet entered shared history. At this point:

  • The developer still has full context of the change
  • No other developer has pulled the code yet
  • Secrets have not been logged by any CI system
  • The fix cost is minimal — the developer can fix and recommit immediately

After a secret or vulnerability enters the repository, the remediation cost rises sharply: git history must be rewritten, secrets must be rotated, all repository clones must be updated, and a post-incident review may be required.

Setting Up git Hooks Manually

Git hooks are shell scripts in the .git/hooks/ directory. To create a pre-commit hook manually:

# Create the hook file
touch .git/hooks/pre-commit

# Make it executable
chmod +x .git/hooks/pre-commit

A minimal hook that runs a security scan:

#!/bin/sh
# .git/hooks/pre-commit

echo "Running security checks..."

# Run CodeSlick on staged files
npx codeslick analyze --staged --fail-on critical

if [ $? -ne 0 ]; then
  echo "❌ Security check failed. Fix the issues above before committing."
  exit 1
fi

echo "✅ Security checks passed."
exit 0

Limitation: Files in .git/hooks/ are not committed to the repository. Each developer must set up the hook manually on their machine, making enforcement inconsistent across teams.

Husky and lint-staged Integration

Husky solves the sharing problem by storing hook configuration in package.json and installing hooks automatically when developers run npm install. Hooks are committed to the repository and enforced consistently across all machines.

Setup

# Install Husky and lint-staged
npm install --save-dev husky lint-staged codeslick-cli

# Enable Husky
npx husky init

This creates a .husky/ directory with a pre-commit script and adds a prepare script to package.json that installs hooks on npm install.

Configure the pre-commit hook

# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged

Configure lint-staged to run CodeSlick

Add to package.json:

{
  "lint-staged": {
    "*.{js,ts,jsx,tsx,py,java,go}": [
      "npx codeslick analyze --fail-on critical"
    ]
  }
}

lint-staged runs the configured commands only against files that are staged for commit. This keeps pre-commit hooks fast — a commit that touches two TypeScript files only scans those two files, not the entire codebase.

What to Check at Commit Time

The best pre-commit security checks are fast, have low false-positive rates, and catch issues that are expensive to remediate after the fact:

1. Secrets detection (always run)

Hardcoded API keys, tokens, and passwords are the highest-priority pre-commit check. A committed secret requires key rotation regardless of whether the commit is later reverted. CodeSlick scans for 38 secret patterns including AWS keys, Stripe tokens, GitHub tokens, and database connection strings.

2. SAST scanning (run on staged files)

Static analysis catches injection vulnerabilities, missing security headers, insecure cryptography, and other code-level security issues. Running on staged files only (via lint-staged) keeps this fast enough for pre-commit use — typically under 3 seconds per file.

3. Dependency audit (run when lockfile changes)

Run npm audit or pip-audit only when package-lock.json or requirements.txt is staged. New dependencies introduced in a commit should be checked before they enter the repository.

What NOT to run at pre-commit

  • Full test suite: Too slow for pre-commit. Reserve for CI/CD.
  • Build compilation: Adds 30–120 seconds. Use pre-push hooks instead.
  • Complete SBOM generation: Resource-intensive. Run in CI/CD.

How CodeSlick CLI Works as a Pre-commit Hook

The CodeSlick CLI is designed for pre-commit use: it exits with code 1 when critical or high findings are detected, enabling clean integration with git hooks.

Installation

npm install --save-dev codeslick-cli

Basic pre-commit integration

# .husky/pre-commit
npx codeslick analyze --staged --fail-on critical

The --staged flag analyzes only the files staged for commit. The --fail-on critical flag exits with code 1 if any Critical severity findings are detected, blocking the commit.

Pass/fail threshold configuration

Configure thresholds via .codeslick.yml in the project root:

# .codeslick.yml
thresholds:
  fail_on: critical        # Block commits with Critical findings
  warn_on: high            # Warn (but allow) on High findings
  secrets: block           # Always block hardcoded secrets
  ai_code: warn            # Warn on detected AI-generated code

What the CLI checks

  • 308 security checks across JavaScript, TypeScript, Python, Java, and Go
  • 38 secret detection patterns — AWS keys, Stripe tokens, GitHub tokens, database connection strings
  • 164 AI code detection signals — flags code from Copilot, ChatGPT, Claude for mandatory review
  • Dependency scanning — checks package-lock.json, requirements.txt, pom.xml, go.sum against CVE databases

All findings include CWE classification, CVSS severity, and fix suggestions. The pre-commit output is designed for developer readability — the finding, the file, the line, and the recommended fix appear directly in the terminal.

Add CodeSlick to your pre-commit workflow and catch SQL injection, hardcoded secrets, and XSS before every commit.

Frequently Asked Questions

Related Guides