DevSecOps for Git: Shifting Security Left, One Commit at a Time.
A practical, hands-on guide to catching secrets and enforcing security before they ever reach production.
DevSecOps for Git: Shifting Security Left, One Commit at a Time.

https://github.com/SandeepKomal
A practical, hands-on guide to catching secrets and enforcing security before they ever reach production.
Every leaked API key, every exposed .env file, every "oops, I committed my AWS credentials" incident has one thing in common: it happened at the Git layer, and nobody stopped it in time.
DevSecOps isn’t just a buzzword tacked onto your CI/CD pipeline — it starts at the very first git add. In this post, we'll walk through a layered defense system for Git repositories, from the simplest .gitignore file all the way up to automated secret scanning, branch protection, and dependency management. Think of it as security in depth, applied to your version control workflow.
Why Git Needs Its Own Security Layer
Git repositories are often the softest target in an organization. Secrets get pasted into config files, .env files get committed "just this once," and history — unlike a live server — remembers everything forever. A secret removed in the next commit is still sitting in your Git history, discoverable by anyone with clone access.
The fix isn’t a single tool. It’s a pipeline of checks, each catching what the previous one missed:
- Stop tracked files from ever including secrets (
.gitignore) - Stop secrets from being committed locally (pre-commit hooks)
- Stop secrets from being pushed, even if a hook was bypassed (CI scanning)
- Stop unreviewed or unauthorized changes from reaching
main(branch protection, RBAC, CODEOWNERS) - Stop vulnerable dependencies from quietly piling up (Dependabot)
Let’s go through each layer.
1. .gitignore — Your First Line of Defense
Purpose
Prevent sensitive files from ever being tracked by Git in the first place.
Common Security Files to Ignore
.env
.env.*
*.pem
*.key
id_rsa
terraform.tfstate
.terraform/
node_modules/
dist/
Demo
bash
echo "AWS_SECRET_ACCESS_KEY=123" > .env
git status
Now add it to .gitignore:
bash
echo ".env" >> .gitignore
git status
✅ The file is no longer tracked.
⚠️ Important caveat: .gitignore only prevents future tracking. It does nothing to protect a secret that's already been committed — that secret is permanently baked into your Git history until it's purged and rotated.
2. Native Git Pre-Commit Hooks (Custom Scripts)
What This Is
A pre-commit hook is a script located at:
.git/hooks/pre-commit
Git executes it automatically before every commit is finalized — giving you a chance to block bad commits locally, before they ever leave your machine.
Exit Codes
CodeResult0Commit allowed≠ 0Commit blocked
Demo — A Minimal Native Secret Detector
bash
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
echo "Running native pre-commit hook..."
if git diff --cached | grep -i "secret"; then
echo "Secret detected. Commit blocked."
exit 1
fi
echo "Commit passed security checks."
exit 0
EOF
Make it executable:
bash
chmod +x .git/hooks/pre-commit
Test it:
bash
echo "my_secret=123" > test.txt
git add test.txt
git commit -m "test commit"
❌ Commit blocked.
This is a great learning exercise, but a grep for the word "secret" won't catch real-world credentials like AWS keys, JWTs, or private key blocks. That's where a purpose-built tool comes in.
3. Gitleaks — Blocking Commits (The Right Way)
Replace the Native Hook with Gitleaks
- Install pre-commit
- Create a
.pre-commit-config.yamlfile at the root of your repository:
yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.2
hooks:
- id: gitleaks
- Keep it current:
bash
pre-commit autoupdate
- Install the hook:
bash
pre-commit install
You’re all set — Gitleaks now runs automatically on every commit.
Demo — Block a Secret Commit
bash
echo "AWS_SECRET_ACCESS_KEY=AKIA123456789" > secrets.env
git add secrets.env
git commit -m "adding secrets"
❌ Commit blocked — Gitleaks recognizes the AWS key pattern and stops it cold.
4. Gitleaks — Repository & History Scanning
Pre-commit hooks only protect commits made after the hook is installed. To catch secrets already lurking in your history (or to add custom detection rules), run Gitleaks directly against the repo.
Create a Custom Rules File — custom-rules.toml
toml
[[rules]]
id = "generic-password"
description = "Detect any PASSWORD assignment"
regex = '''(?i)password\s*=\s*["'][^"']+["']'''
tags = ["password", "custom"]
Run the Scan
bash
gitleaks detect --config custom-rules.toml
This scans your entire commit history, not just the current diff — which is exactly where old, “already deleted” secrets tend to hide.
5. Gitleaks in GitHub Actions
Local hooks can be skipped with --no-verify. That's why every serious setup backs them up with a check that runs remotely, on every push and pull request, where developers have no way around it.
yaml
name: gitleaks
on: [pull_request, push, workflow_dispatch]
jobs:
scan:
name: gitleaks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} # Only required for Organizations, not personal accounts.
This is your safety net: even if someone bypasses their local hook, the CI pipeline still catches the leak before it merges.
6. Branch Protection Rules
Scanning secrets solves one problem. Controlling who can change what, and how solves another. Branch protection rules are where that control lives.
Enforce:
- No direct pushes to
main - Required pull requests
- Required status checks (including your Gitleaks Action!)
- No force pushes
This turns your main branch from "whatever anyone pushes" into a gate that every change must pass through.
7. RBAC — Least Privilege
Access should match responsibility, not convenience. A basic role model looks like this:
RolePermissionsAdminRepo settingsMaintainerMerge PRsDeveloperPR onlyAuditorRead-only
The principle is simple: nobody should have more access than their role requires.
8. Mandatory Reviews
Automated scanning catches known patterns. Human reviewers catch everything else — logic flaws, risky architecture decisions, and anything a regex was never going to detect.
Best practices:
- Minimum 1–2 reviewers per pull request
- Code owners assigned for sensitive paths
- A dedicated security review for anything touching auth, infrastructure, or CI
9. CODEOWNERS
Pair mandatory reviews with a CODEOWNERS file so the right people are automatically requested as reviewers for sensitive paths:
/.github/ @security-team
/terraform/ @cloud-team
Now a change to your CI workflows or infrastructure code can’t merge without the team that owns that risk signing off.
10. Dependabot — Don’t Forget Your Dependencies
Your own code isn’t the only attack surface — your dependencies are too. Dependabot automatically opens pull requests when a new version (including security patches) is available:
yaml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
Set it, forget it, and let outdated, vulnerable packages get flagged before they become the next CVE headline.
Putting It All Together
None of these layers is sufficient on its own — that’s the whole point of “defense in depth.” Here’s how they stack:
LayerStops….gitignoreSensitive files from being trackedPre-commit hooks / GitleaksSecrets from being committed locallyGitleaks history scanSecrets already buried in commit historyGitleaks in GitHub ActionsSecrets bypassing local hooksBranch protectionUnreviewed or unsafe changes reaching mainRBACOver-privileged accessMandatory reviews & CODEOWNERSRisky changes merging without the right eyesDependabotVulnerable dependencies going unnoticed
DevSecOps for Git isn’t about adding friction for its own sake — it’s about making the secure path the default path, so a single mistyped git add . doesn't turn into a security incident.
⭐ If you found this useful, don’t forget to **follow me on GitHub** for more projects and DevOps content! → https://github.com/SandeepKomal
DevSecOps #DevOps #Git #GitHub #GitLeaks #Dependabot #Precommithooks #RBAC #GitIgnore
메타데이터
- post_id
- 9d18297d5ab4
- slug
- devsecops-for-git-shifting-security-left-one-commit-at-a-time-9d18297d5ab4
- url
- https://medium.com/@sandeepkomalp/devsecops-for-git-shifting-security-left-one-commit-at-a-time-9d18297d5ab4
- canonical_url
- https://medium.com/@sandeepkomalp/devsecops-for-git-shifting-security-left-one-commit-at-a-time-9d18297d5ab4
- author_url
- https://medium.com/@sandeepkomalp
- status
- ok
- fetched_at
- 2026-08-18 16:37:06