Day-2 | DevSecOps for Git and GitHub | Top 10 Security Practices with Demos
Welcome to Episode 2 of the DevSecOps Zero to Hero series. While Episode 1 established that DevSecOps spans across every single activity that DevOps engineers perform—not just CI/CD pipelines—this chapter dives deep into securing the foundational layer of modern software development: Git and GitHub.
1. Why Do We Need Git Security? (The Problem Statement)
Git is often viewed merely as a utility to store and share source code. However, because Git is a distributed version control system that preserves history permanently, security failures here can cascade into enterprise-wide breaches.
Real-Life Analogy: The Accidental Secret Leak
Imagine an organization developing a Python microservice. A developer clones the repository to their local machine and updates a .env file to store a sensitive database password while testing local database changes. Along with their Python script modifications, the developer accidentally commits and pushes the .env file to the shared repository.
Developer Local Machine
|
| creates & modifies .env with DB password
v
Developer runs: git add . && git commit -m "update database scripts"
|
| (Accidentally tracks and commits .env)
v
Remote GitHub Repository (Secret is now permanently in Git history)
Why This is a P0 (Critical) Incident
- Git Never Forgets: Standard version control preserves history. Even if you execute
git revert, the secret remains accessible in previous commit hashes. - Uncontrolled Proliferation: By the time a revert is executed, other team members, external contributors, or automated scrapers may have already cloned or forked the repository.
- The Only Real Fix: Deleting commit history requires aggressive repository rewriting (e.g.,
git filter-branchor BFG Repo-Cleaner), contacting GitHub support, and—most importantly—immediately rotating the compromised database password.
To avoid reactive firefighting, DevSecOps engineers implement a defense-in-depth strategy consisting of 10 essential security practices.
2. Summary of the Top 10 Git Security Practices
The table below synthesizes the ten security controls explored in this chapter, categorized by where and how they operate in the development lifecycle:
3. Practice 1: Leveraging .gitignore Files
The first and easiest line of defense is the .gitignore file. It instructs Git which files and directories it should completely ignore and refuse to track.
How .gitignore Works
When a DevSecOps engineer establishes a repository, they create a comprehensive .gitignore file listing hundreds of common sensitive files, build artifacts, and local configuration files:
- Environment files:
.env,*.env - Terraform state:
*.tfstate,*.tfstate.* - SSH keys:
id_rsa,*.pem - IDE configs:
.vscode/,.idea/
Practical Demonstration
- Clone the repository:
BASH
git clone https://github.com/example/devsecops-zero-to-hero.git cd devsecops-zero-to-hero - Create a local
.envfile containing a secret:BASHecho "DB_PASSWORD=secret_valhalla" > .env - Attempt to track the file:
Git Output:BASH
git add .envTEXTThe following paths are ignored by one of your .gitignore files: .env hint: Use -f if you really want to force it.Note: Even if a user attempts to bundle ignored files alongside valid files using a wildcard command like
git add --all, Git will respect the.gitignorerules and refuse to track the sensitive files.
4. Practice 2: Custom Pre-Commit Hooks
While .gitignore protects against known file names, it fails if a developer accidentally hardcodes a secret directly inside a tracked source file (e.g., hardcoding an AWS secret inside a .py or .tf file). For this, we use Pre-Commit Hooks.
Mental Model: Known Files vs. Vulnerable Patterns
.gitignore: Operates on file names and extensions (e.g., ignore.env).- Pre-Commit Hooks: Operates on content patterns (e.g., scan code diffs for strings like
secret,token, orpassword).
Creating a Native Shell Script Hook
Git stores local hooks inside the .git/hooks directory. We can write a custom shell script named pre-commit inside .git/hooks/ to inspect staged changes using git diff.
#!/bin/bash
echo "Running native pre-commit hook..."
# Check git diff for the keyword 'secret'
if git diff --cached | grep -qi "secret"; then
echo "❌ SECURITY ALERT: Secret detected in staged changes! Commit blocked."
exit 1
fi
echo "✅ No secrets detected. Proceeding with commit."
exit 0
Make the script executable:
chmod +x .git/hooks/pre-commit
Testing the Native Hook
- Create a text file containing the forbidden keyword:
BASH
echo "secret = my_api_key_123" > test.txt git add test.txt - Attempt to commit:
Result: The commit is instantly blocked withBASH
git commit -m "Test commit with secret"❌ SECURITY ALERT: Secret detected....
5. Practice 3: The Pre-Commit Framework & GitLeaks
Writing custom shell scripts for every conceivable vulnerability pattern (tokens, passwords, API keys, AWS keys) is hard to maintain. Instead, enterprises utilize the Pre-Commit Framework backed by GitLeaks.
Installing and Configuring the Framework
- Remove the custom native hook:
BASH
rm -rf .git/hooks/pre-commit - Install the Pre-Commit Framework (macOS / Linux):
BASH
# For macOS via Homebrew brew install pre-commit # For Linux via Python pip pip install pre-commit - Create a
.pre-commit-config.yamlfile at the root of the repo:YAMLrepos: - repo: https://github.com/gitleaks/gitleaks rev: v8.18.0 # Use appropriate stable version hooks: - id: gitleaks - Install the git hooks configuration:
BASH
pre-commit install
Testing GitLeaks in Action
When attempting to commit a file containing an AWS secret key:
git add secrets.env
git commit -m "Adding test secrets"
GitLeaks Output:
gitleaks........................................................Failed
- hook id: gitleaks
- exit code: 1
INF █ █ ▀ █▀▀ █▀▀ █▀ █ █ █▀▀ █▀▀ █
INF █ ▀▄█ ██▄ █▄▄ █▀ ▀▄▀ █▀ █▄▄ █ v8.18.0
...
Leaking: AWS Secret Access Key
File: secrets.env
Line: 1
The framework automatically analyzes patterns, blocks the commit, identifies the exact file, and highlights the vulnerable line.
6. Practice 4: Historical Repository Scanning
What about repositories that have existed for years where secrets might be buried deep inside past commits? Since Git preserves history, developers often delete a secret in the latest commit, believing the threat is gone, while malicious actors can easily inspect past commit hashes.
Running GitLeaks Scan Across All History
You can scan your entire commit history using GitLeaks without needing a pre-commit block:
gitleaks detect -v
- What this command does: It iterates through all commits (e.g., all 11+ historical commits in the repo) and checks every file at every point in time.
- Enterprise Best Practice: Run repository scanning as an automated cron job across all organization repositories every 2 to 3 months.
7. Practice 5: Integrating GitLeaks into CI/CD (GitHub Actions)
Relying solely on local developer compliance (e.g., installing pre-commit hooks) is risky. Developers might bypass hooks or clone repos without them. Therefore, you must enforce security checks in your CI/CD pipeline.
GitHub Actions Workflow Configuration
Create .github/workflows/gitleaks.yml:
name: gitleaks
on:
push:
branches: [ "*" ]
pull_request:
branches: [ "*" ]
workflow_dispatch:
jobs:
scan:
name: gitleaks-scan
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Important: fetch full history for thorough scanning
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Flow Walkthrough: PR Security Gate
8. Practice 6: Branch Protection Rules
Allowing developers or DevOps engineers to execute git push origin main directly without code review is an enterprise anti-pattern.
Configuring Branch Protection
- Navigate to your GitHub repository Settings -> Branches.
- Click Add branch rule set.
- Set the target branch (e.g.,
mainor default branch patterns likerelease/*). - Enable Require a pull request before merging.
- Save the rule set.
Once active, any direct push to main will be rejected by GitHub:
git push origin main
# Remote Rejected: Protected branch hook declined the push.
9. Practice 7: Role-Based Access Control (RBAC)
RBAC ensures that users are granted permissions strictly aligned with their operational responsibilities.
10. Practice 8 & 9: Mandatory Reviews and Code Owners
Mandatory Reviews
To prevent rubber-stamping or accidental merges, configure branch protection rules to require a minimum number of peer reviews:
- Enable Require pull request before merging.
- Set Required number of approving reviews before merging to
2.
Code Owners (CODEOWNERS)
In large open-source projects or enterprise repositories with hundreds of contributors, you want specific architectural experts to review changes to critical files.
Create a file named CODEOWNERS in the repository root (or inside .github/CODEOWNERS):
# Global default owner
* @enterprise/core-maintainers
# Security-sensitive configurations
/infra/ @security-team
/*.yml @devops-leads
When a pull request modifies files matching these paths, GitHub automatically tags and requests reviews from the specified users and teams.
11. Practice 10: Dependabot (Automated Vulnerability Patching)
Modern applications rely heavily on third-party packages (Go modules, npm packages, Maven pom.xml, Docker base images). When a new vulnerability (CVE) is discovered in a package version you use, manual patching is too slow.
Configuring Dependabot
Create a .github/dependabot.yml configuration file:
version: 2
updates:
# Maintain dependencies for Go modules
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
open-pull-request-limit: 10
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
How Dependabot Operates
12. Summary and Key Takeaways
- Git is permanent: Version control stores history indefinitely; treating Git as a secure zone without active preventative guardrails exposes enterprises to severe P0 credential leaks.
- Layer 1 (.gitignore): Use
.gitignoreto prevent tracking known sensitive files (.env,*.tfstate, private keys) from ever entering version control. - Layer 2 (Pre-Commit Hooks & Frameworks): Implement local pre-commit checks using GitLeaks and the pre-commit framework to intercept vulnerable patterns before commits are finalized.
- Layer 3 (Historical Auditing): Regularly run
gitleaks detectto audit past commits and history for dormant or forgotten secrets. - Layer 4 (CI/CD Gates): Enforce automated GitLeaks checks inside GitHub Actions pull request pipelines so no code bypasses security checks.
- Branch Protection: Lock down critical branches (
main,release/*) to prohibit direct pushes and enforce pull-request-only workflows. - Access Governance: Apply strict RBAC (
Admin,Write,Read) to limit who can modify repository settings and merge code. - Mandatory Reviews & Code Owners: Require multiple peer approvals and configure
CODEOWNERSto ensure domain experts review sensitive changes. - Automated Remediation: Utilize Dependabot to continuously scan application packages against vulnerability databases and auto-generate patch pull requests.