Zum Hauptinhalt springen

Git-Security - Quellcode und Commits absichern

· 8 Minuten Lesezeit
Pascal Nehlsen
Platform & Security Engineering

Dein Git-Repository ist mehr als Code. Es enthält Credentials, API-Keys, geistiges Eigentum und die Kronjuwelen deines Unternehmens. Sichern wir es richtig ab.

Der Vorfall mit dem GitHub-Token​

Letzte Woche hat ein Praktikant einen Commit mit einem GitHub Personal Access Token samt vollem Repo-Zugriff gepusht. Innerhalb von Minuten hat ein automatisierter Bot ihn gefunden und:

  1. alle unsere privaten Repositories geklont
  2. Secrets aus der Commit-Historie gesammelt
  3. Backdoor-Accounts angelegt

Dieser Beitrag handelt davon, das zu verhindern.

1. Commits mit GPG signieren​

Warum Commits signieren?​

Jeder kann sich mit git config user.name "Dein Name" als du ausgeben. Signierte Commits belegen die Echtheit.

GPG-Key einrichten​

# Generate GPG key
gpg --full-generate-key
# Choose: RSA and RSA, 4096 bits, no expiration

# List keys
gpg --list-secret-keys --keyid-format=long

# Export public key
gpg --armor --export YOUR_KEY_ID

# Add to GitHub: Settings > SSH and GPG keys > New GPG key

Git konfigurieren​

# Set signing key
git config --global user.signingkey YOUR_KEY_ID

# Sign all commits by default
git config --global commit.gpgsign true

# Sign tags
git config --global tag.gpgSign true

Signierte Commits prüfen​

# Verify last commit
git verify-commit HEAD

# Show signature
git log --show-signature

# Verify tag
git verify-tag v1.0.0

2. Branch-Protection-Regeln​

Branch Protection bei GitHub​

# .github/branch-protection.yml
protection:
required_status_checks:
strict: true
contexts:
- "security/sast"
- "security/secrets-scan"
- "tests/unit"
- "tests/integration"
required_pull_request_reviews:
required_approving_review_count: 2
dismiss_stale_reviews: true
require_code_owner_reviews: true
restrictions:
users: []
teams:
- "security-team"
enforce_admins: true
required_linear_history: true
allow_force_pushes: false
allow_deletions: false

Protected Branches bei GitLab​

# Via GitLab UI or API
POST /projects/:id/protected_branches
{
"name": "main",
"push_access_level": 0, # No one
"merge_access_level": 30, # Developers
"unprotect_access_level": 40, # Maintainers
"allow_force_push": false,
"code_owner_approval_required": true
}

3. Pre-commit-Hooks zur Secret-Erkennung​

pre-commit-Framework einrichten​

# Install
pip install pre-commit

# Create .pre-commit-config.yaml
cat > .pre-commit-config.yaml <<EOF
repos:
# Detect secrets
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']

# Git-secrets
- repo: https://github.com/awslabs/git-secrets
rev: master
hooks:
- id: git-secrets

# Check for large files
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-added-large-files
args: ['--maxkb=500']
- id: check-merge-conflict
- id: check-yaml
- id: check-json
- id: trailing-whitespace
- id: end-of-file-fixer

# Gitleaks for secret scanning
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
EOF

# Install hooks
pre-commit install

# Run on all files
pre-commit run --all-files

Eigener Pre-commit-Hook​

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

echo "Running security checks..."

# Check for AWS credentials
if grep -r "AKIA[0-9A-Z]{16}" --exclude-dir=.git .; then
echo "ERROR: AWS Access Key detected!"
exit 1
fi

# Check for private keys
if grep -r "BEGIN.*PRIVATE KEY" --exclude-dir=.git .; then
echo "ERROR: Private key detected!"
exit 1
fi

# Check for common password patterns
if grep -ri "password\s*=\s*['\"]\w" --exclude-dir=.git .; then
echo "ERROR: Hardcoded password detected!"
exit 1
fi

# Check for Stripe keys
if grep -r "sk_live_[0-9a-zA-Z]{24}" --exclude-dir=.git .; then
echo "ERROR: Stripe secret key detected!"
exit 1
fi

echo "All security checks passed"
exit 0

4. Git-Historie aufräumen​

Sensible Datei aus der Historie entfernen​

# Using BFG Repo-Cleaner (faster than git filter-branch)
wget https://repo1.maven.org/maven2/com/madgag/bfg/1.14.0/bfg-1.14.0.jar

# Remove file
java -jar bfg-1.14.0.jar --delete-files secrets.env

# Remove passwords from all files
java -jar bfg-1.14.0.jar --replace-text passwords.txt

# Clean up
git reflog expire --expire=now --all
git gc --prune=now --aggressive

# Force push (coordinate with team!)
git push --force

Mit git-filter-repo​

# Install
pip install git-filter-repo

# Remove specific file
git filter-repo --path secrets.env --invert-paths

# Remove by pattern
git filter-repo --path-glob '*.key' --invert-paths

# Replace text
echo 'password123==>***REMOVED***' > replacements.txt
git filter-repo --replace-text replacements.txt

5. Repository scannen​

Mit Gitleaks​

# Install
brew install gitleaks # macOS
# or
wget https://github.com/gitleaks/gitleaks/releases/download/v8.18.0/gitleaks_8.18.0_linux_x64.tar.gz

# Scan current repository
gitleaks detect --verbose

# Scan specific commit range
gitleaks detect --log-opts="HEAD^..HEAD"

# Generate report
gitleaks detect --report-path gitleaks-report.json --report-format json

Mit TruffleHog​

# Install
pip install truffleHog

# Scan repository
trufflehog git https://github.com/your-org/your-repo

# Scan local repo
trufflehog filesystem /path/to/repo

# Scan since specific commit
trufflehog git file:///path/to/repo --since-commit abcd1234

Einbindung in CI/CD​

# .gitlab-ci.yml
security:gitleaks:
stage: security
image: zricethezav/gitleaks:latest
script:
- gitleaks detect --verbose --report-path gitleaks-report.json
artifacts:
reports:
secret_detection: gitleaks-report.json
paths:
- gitleaks-report.json
allow_failure: false

security:trufflehog:
stage: security
image: python:3.11
script:
- pip install truffleHog
- trufflehog filesystem . --json > trufflehog-report.json
artifacts:
paths:
- trufflehog-report.json

6. Sichere Git-Workflows​

Gitflow mit Security-Gates​

main (protected)
├── develop (protected)
│ ├── feature/user-auth (requires: SAST, secrets scan)
│ ├── feature/payment (requires: SAST, DAST, security review)
│ └── bugfix/login-issue
├── release/v1.2.0 (requires: full security scan)
└── hotfix/critical-vuln (emergency process)

Security-Prüfungen je Branch-Typ​

# .gitlab-ci.yml
workflow:
rules:
- if: '$CI_COMMIT_BRANCH =~ /^feature\/.*$/'
variables:
SECURITY_LEVEL: "standard"
- if: '$CI_COMMIT_BRANCH =~ /^release\/.*$/'
variables:
SECURITY_LEVEL: "full"
- if: '$CI_COMMIT_BRANCH == "main"'
variables:
SECURITY_LEVEL: "full"

security:scan:
script:
- |
if [ "$SECURITY_LEVEL" == "full" ]; then
echo "Running full security scan"
gitleaks detect
trivy fs . --severity HIGH,CRITICAL
snyk test
else
echo "Running standard security scan"
gitleaks detect
fi

7. Zugriffskontrolle und Berechtigungen​

Team-Struktur bei GitHub​

Organization
├── Admins (full access)
├── Security Team (all repos, branch protection)
├── Developers
│ ├── Backend Team (backend repos, write access)
│ ├── Frontend Team (frontend repos, write access)
│ └── DevOps Team (infra repos, write access)
└── External Contributors (read-only, can fork)

CODEOWNERS-Datei​

# .github/CODEOWNERS

# Default owners for everything
* @org/developers

# Security-sensitive files require security team review
/security/ @org/security-team
/.github/ @org/security-team @org/devops
/Dockerfile @org/security-team @org/devops
/docker-compose.yml @org/security-team @org/devops

# Infrastructure code requires DevOps review
/terraform/ @org/devops @org/security-team
/kubernetes/ @org/devops @org/security-team
/.gitlab-ci.yml @org/devops

# Backend requires backend team
/backend/ @org/backend-team
/api/ @org/backend-team

# Frontend requires frontend team
/frontend/ @org/frontend-team
/web/ @org/frontend-team

# Database migrations require multiple approvals
/migrations/ @org/backend-team @org/database-admins

# Critical security files require admin approval
secrets.yml @org/admins @org/security-team
.env.* @org/admins @org/security-team

8. Audit-Logging für Git​

Git-Operationen überwachen​

# git_audit_logger.py
import git
import json
import logging
from datetime import datetime

class GitAuditLogger:
def __init__(self, repo_path):
self.repo = git.Repo(repo_path)
self.setup_logging()

def setup_logging(self):
logging.basicConfig(
filename='git-audit.log',
level=logging.INFO,
format='%(asctime)s - %(message)s'
)

def log_commit(self, commit):
"""Log commit details"""
log_entry = {
'action': 'commit',
'sha': commit.hexsha,
'author': commit.author.name,
'email': commit.author.email,
'message': commit.message,
'timestamp': commit.committed_datetime.isoformat(),
'files_changed': len(commit.stats.files),
'insertions': commit.stats.total['insertions'],
'deletions': commit.stats.total['deletions']
}
logging.info(json.dumps(log_entry))

def audit_recent_commits(self, days=7):
"""Audit commits from last N days"""
since = datetime.now() - timedelta(days=days)
for commit in self.repo.iter_commits(since=since):
self.log_commit(commit)

def detect_suspicious_activity(self):
"""Detect suspicious patterns"""
alerts = []

for commit in self.repo.iter_commits(max_count=100):
# Large deletions (potential data destruction)
if commit.stats.total['deletions'] > 1000:
alerts.append({
'severity': 'high',
'type': 'large_deletion',
'commit': commit.hexsha,
'author': commit.author.email
})

# Commits at unusual hours (potential compromise)
hour = commit.committed_datetime.hour
if hour < 6 or hour > 22:
alerts.append({
'severity': 'medium',
'type': 'unusual_hour',
'commit': commit.hexsha,
'time': commit.committed_datetime.isoformat()
})

return alerts

# Usage
audit = GitAuditLogger('/path/to/repo')
audit.audit_recent_commits(days=30)
alerts = audit.detect_suspicious_activity()
for alert in alerts:
print(f"WARNING: {alert['severity'].upper()}: {alert['type']} - {alert['commit']}")

9. Sichere Git-Konfiguration​

Globale Security-Einstellungen​

# Prevent accidental pushes to wrong remote
git config --global push.default simple

# Always use SSH instead of HTTPS
git config --global url."git@github.com:".insteadOf "https://github.com/"

# Enable credential helper (cached credentials)
git config --global credential.helper 'cache --timeout=3600'

# Auto-fetch with prune
git config --global fetch.prune true

# Show status in short format
git config --global status.short true

# Use rebase instead of merge for pulls
git config --global pull.rebase true

# Sign all tags by default
git config --global tag.gpgSign true

Einstellungen pro Repository​

# Inside repository
cd your-repo

# Require signed commits
git config commit.gpgsign true

# Set up hooks directory
git config core.hooksPath .githooks

# Enable fsck on receive
git config receive.fsckObjects true

Security-Checkliste​

  • GPG-Commit-Signierung aktivieren
  • Branch-Protection-Regeln konfigurieren
  • Pre-commit-Hooks zur Secret-Erkennung einrichten
  • CODEOWNERS-Datei anlegen
  • 2FA für alle Teammitglieder aktivieren
  • Repository regelmäßig scannen (gitleaks, trufflehog)
  • Git-Zugriffslogs monatlich prüfen
  • Force-Push-Rechte einschränken
  • SSH-Keys jährlich prüfen und rotieren
  • Team zu Git-Security schulen
  • Incident Response für geleakte Credentials dokumentieren
  • SSH-Keys statt Passwörter verwenden
  • Security-Gates in CI/CD einbauen

Notfall: geleakte Credentials​

#!/bin/bash
# emergency-git-cleanup.sh

echo "EMERGENCY: Credential Leak Response"

# 1. Revoke compromised credentials immediately
echo "[1/5] Revoking credentials..."
# Call your secrets management API

# 2. Scan entire git history
echo "[2/5] Scanning git history..."
gitleaks detect --verbose --log-opts="--all"

# 3. Remove secrets from history
echo "[3/5] Cleaning git history..."
java -jar bfg.jar --replace-text passwords.txt
git reflog expire --expire=now --all
git gc --prune=now --aggressive

# 4. Notify team
echo "[4/5] Notifying team..."
gh api /repos/:owner/:repo/issues \
-X POST \
-f title="Security Incident: Credentials Leaked" \
-f body="Immediate action required. Check Slack for details."

# 5. Force push (DANGEROUS - coordinate first!)
echo "[5/5] Force pushing clean history..."
read -p "Are you sure you want to force push? (yes/no): " confirm
if [ "$confirm" == "yes" ]; then
git push --force --all
git push --force --tags
fi

echo "Emergency response complete"

Sicherst du deine Git-Workflows ab? Teile deine Tipps auf GitHub!