Introduction: From Manual Scans to Automated Security Gates

In the previous lessons, we learned how to use powerful tools like ESLint, Semgrep, and Gitleaks to find security vulnerabilities in our code. However, relying on memory to run these tools manually is a flawed strategy. In a busy development cycle, it is very easy to forget to run a scan before pushing code, allowing vulnerabilities to slip into the production codebase. Even the most security-conscious developers make mistakes when they are rushing to meet a deadline.

To solve this problem, we need to move from manual scanning to automated security gates. Automation ensures that security checks happen consistently without requiring human intervention. In this lesson, we will explore two distinct layers of automation: Git Hooks, which run locally on your machine to catch issues instantly, and CI/CD Pipelines, which act as a final safety net on the server to prevent vulnerable code from being merged or deployed.

Understanding Git Hooks for Local Security

Git Hooks are scripts that git executes before or after events such as committing, pushing, and receiving. Every git repository has a hidden folder called .git/hooks/ that contains these scripts. By default, this folder contains sample scripts, but we can replace them with our own custom logic to enforce rules. Think of these hooks as "security guards" that inspect your code actions before allowing them to proceed.

For security purposes, the most important hook is the pre-commit hook. This script triggers the moment you type git commit, but before the commit is actually created in the database. If the script exits with an error (a non-zero status code), the commit is aborted completely. This is incredibly powerful because it prevents vulnerable code from ever entering your local version history.

By configuring a pre-commit hook to run our SAST tools, we create an immediate feedback loop. If you try to save code with a known security flaw, the system will stop you and say, "Please fix this first." This saves time because fixing a bug while you are still writing the code is much cheaper and faster than fixing it weeks later after it has been deployed.

Installing a Pre-Commit Security Hook

Git hooks are not distributed with the repository code by default. This is a security feature; you wouldn't want a random project you downloaded to automatically run scripts on your computer without your permission. Therefore, to install a hook, you must manually place the script into the .git/hooks/ directory. On the CodeSignal platform (and in most Linux/Mac environments), these scripts must also be marked as executable.

Here is how we typically install a custom hook. In this example, we assume we have a pre-written script located in a folder named hooks/:

# Copy the script to the hidden git hooks directory
cp hooks/pre-commit .git/hooks/pre-commit

# Verify the file is there and has execution permissions (look for 'x' in permissions)
ls -la .git/hooks/pre-commit

Let's look at what a simple pre-commit script might look like inside. It is usually a shell script that runs your security tools.

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

echo "Running security scans..."

# Run ESLint scan
npx eslint src/ --ext .ts,.tsx
STATUS=$?

# If the scan failed (found issues), stop the commit
if [ $STATUS -ne 0 ]; then
    echo "Commit failed: Security vulnerabilities found."
    exit 1
fi

echo "Security check passed!"
exit 0

This script is straightforward: it runs the scan command, checks the result, and if the tool finds errors, it forces the commit to fail by using exit 1.

How Pre-Commit Hooks Block Vulnerable Code

Once the hook is installed, it works automatically in the background. You do not need to run any special commands to trigger it; you simply use git as you normally would. When you stage your files and attempt to commit, the hook intercepts the action. If your code is clean, the commit succeeds, and you barely notice the hook running. However, if your code contains issues, the process is interrupted.

Let's see what happens when we try to commit a file that contains a security vulnerability, such as a hardcoded secret or a dangerous SQL query:

# Stage the files
git add src/vulnerable-file.ts

# Attempt to commit
git commit -m "Add feature with vulnerability"

The output in your terminal would look something like this:

Running security scans...

/app/learn-pastebin/src/vulnerable-file.ts
  5:12  error  Generic Object Injection Vulnerability  security/detect-object-injection

✖ 1 problem (1 error, 0 warnings)

Commit failed: Security vulnerabilities found.

Notice that the commit did not happen. If you check your git log, the new entry will not be there. The pre-commit hook successfully acted as a gate, forcing you to acknowledge and fix the security issue before you can save your work. You must now edit the file to fix the vulnerability, add the file again, and retry the commit.

CI/CD Pipeline Security with GitHub Actions

While local hooks are excellent for individual developers, they can be bypassed. A developer might delete the hook, use the --no-verify flag to skip it, or simply forget to install it on a new machine. Because of this, we need a second layer of defense that runs on the server. This is where CI/CD (Continuous Integration/Continuous Deployment) comes in.

In modern platforms like GitHub, GitLab, or Bitbucket, we define "workflows" or "pipelines." These are sets of instructions that the server executes whenever code is pushed or a Pull Request is opened. For this course, we will look at a GitHub Actions workflow file, which is always stored in the .github/workflows/ directory.

Here is an example of a security scanning workflow defined in YAML:

# .github/workflows/security-scan.yml
name: Security Scan

on: [push, pull_request]

jobs:
  sast-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Install dependencies
        run: npm install

      - name: Run Gitleaks
        run: gitleaks detect --source . -v

      - name: Run ESLint Security
        run: npx eslint src/

This file tells the server: "Every time someone pushes code, check out the code, install the tools, and run Gitleaks and ESLint." If any of these steps fail (e.g., Gitleaks finds a secret), the entire build is marked as Failed. In a professional setting, this usually blocks the ability to merge code into the main branch, acting as the final, unbreakable security gate.

Balancing Security Gates with Developer Productivity

When implementing these gates, it is important to find a balance between security and developer productivity. If your security checks take 20 minutes to run, developers will become frustrated waiting for every commit. If the checks flag too many "false positives" (issues that aren't actually real problems), developers will stop trusting the tools and might try to bypass them.

To maintain this balance, we often split checks between local and CI/CD environments.

  • Local (Pre-commit): Run fast, high-confidence checks. For example, linting (ESLint) and secret scanning (Gitleaks) usually run in seconds. This gives immediate feedback without slowing down the workflow.
  • CI/CD (Server): Run slower, deeper analysis. Heavy tools or full-system scans are better suited for the server, where they can run in the background while the developer moves on to other tasks.

We also configure thresholds. For a local pre-commit hook, we might only block the commit on Critical or High severity issues. Less severe warnings might be displayed in the console but allowed to pass, leaving them to be reviewed during the Pull Request process. This ensures that security improves code quality without becoming a bottleneck that stops work entirely.

Summary and Next Steps

In this lesson, we established a robust defense strategy using automated security gates. We learned that manual scanning is unreliable and that automation is key to consistent security. We explored Git Hooks, specifically the pre-commit hook, which allows us to block vulnerable code locally before it is even saved to the git history. We also examined CI/CD pipelines using GitHub Actions, which act as the final authority on the server to prevent bad code from merging.

We also discussed the importance of balancing strict security with developer speed by choosing the right tools for local versus server-side scanning. Now, you are ready to put this into practice. In the upcoming exercises, you will manually install a pre-commit hook, trigger a security block by writing vulnerable code, and fix the code to successfully pass the automated security gates.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal