Code Security
Securing the software supply chain requires automated checks at every stage โ from pre-commit hooks to production deployment gates. This guide implements defense-in-depth for code repositories, dependencies, and build pipelines.
GitLeaks
GitLeaks detects and prevents hardcoded secrets in git repositories using regex and entropy analysis. It scans the entire commit history, not just the current branch.
Installation
# macOS
brew install gitleaks
# Linux
wget https://github.com/gitleaks/gitleaks/releases/download/v8.18.2/gitleaks_8.18.2_linux_x64.tar.gz
tar -xzf gitleaks_8.18.2_linux_x64.tar.gz
sudo mv gitleaks /usr/local/bin/
# Docker
docker pull zricethezav/gitleaks:latest
# Verify installation
gitleaks version
Configuration
# .gitleaks.toml โ Production-grade configuration
# Extends default rules with organization-specific patterns
[extend]
useDefault = true
# Custom rules for organization-specific secrets
[[rules]]
id = "company-internal-api-key"
description = "Company Internal API Key"
regex = '''(?i)(company|compy)[_\-]?(api[_\-]?key|apikey)[\s]*[:=][\s]*['"]([a-zA-Z0-9\-_]{32,64})['"]'''
tags = ["apikey", "company", "internal"]
keywords = ["company", "apikey", "api_key"]
[[rules]]
id = "company-jwt-secret"
description = "Company JWT Signing Secret"
regex = '''(?i)(jwt[_\-]?signing|jwt[_\-]?secret|jwt[_\-]?key)[\s]*[:=][\s]*['"]([a-zA-Z0-9+/]{40,})['"]'''
tags = ["jwt", "secret", "crypto"]
[[rules]]
id = "internal-service-token"
description = "Internal Service Authentication Token"
regex = '''(?i)(service[_\-]?token|internal[_\-]?token)[\s]*[:=][\s]*['"](sk_[a-zA-Z0-9]{48,})['"]'''
tags = ["token", "internal"]
[[rules]]
id = "aws-account-id"
description = "AWS Account ID in context with other credentials"
regex = '''(?i)(account[_\-]?id)[\s]*[:=][\s]*['"]?(\d{12})['"]?'''
tags = ["aws", "account"]
# Global allowlist for test fixtures and documentation
[allowlist]
paths = [
'''\.gitleaks\.toml$''',
'''test\/fixtures\/.*''',
'''.*_test\.go$''',
'''.*\.test\..*$''',
'''tests?\/.*$''',
'''\/__mocks__\/.*''',
'''\.example\.''',
'''\.sample\.''',
'''README\.md$''',
'''CHANGELOG\.md$''',
'''docs\/.*\.md$''',
]
regexes = [
# Example/test values explicitly marked
'''test[_\-]?password''',
'''example[_\-]?key''',
'''YOUR[_\-]?API[_\-]?KEY[_\-]?HERE''',
'''dummy[_\-]?token''',
'''fake[_\-]?secret''',
'''AKIAIOSFODNN7EXAMPLE''', # AWS example key
'''wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY''',
]
commits = []
Pre-Commit Hook
#!/bin/bash
# .git/hooks/pre-commit โ GitLeaks pre-commit hook
# Install with: git config core.hooksPath .githooks
echo "Running GitLeaks secret scan..."
# Run GitLeaks on staged files only
if ! gitleaks protect --staged --verbose --config .gitleaks.toml; then
echo ""
echo "========================================"
echo "SECURITY VIOLATION: Potential secrets detected!"
echo "========================================"
echo ""
echo "GitLeaks found potential secrets in your commit."
echo "Do NOT commit secrets to the repository."
echo ""
echo "Remediation steps:"
echo " 1. Remove the secret from the file"
echo " 2. Move the secret to a secrets manager (Vault, AWS SM)"
echo " 3. Stage the fix and commit again"
echo ""
echo "If this is a false positive, add to .gitleaks.toml allowlist"
echo "with a detailed comment."
echo ""
exit 1
fi
echo "GitLeaks scan passed โ no secrets detected."
exit 0
Common Commands
# Scan current directory
gitleaks detect --source . --verbose
# Scan with custom config
gitleaks detect --source . --config .gitleaks.toml --report-format json --report-path gitleaks-report.json
# Scan only unstaged files (pre-commit)
gitleaks protect --staged --verbose
# Scan entire git history
gitleaks detect --source . --verbose --no-git
# Scan a specific commit range
gitleaks detect --source . --log-opts="--all --full-history" --commit-from=abc123 --commit-to=def456
# Baseline mode โ establish known-good baseline
gitleaks detect --source . --baseline-report gitleaks-baseline.json
# Future scans: gitleaks detect --source . --baseline-report gitleaks-baseline.json
TruffleHog: Deep Secret Scanning
TruffleHog goes beyond regex matching with entropy analysis, high-entropy detection, and live verification against actual APIs. It detects unknown secret types through entropy analysis.
# Install TruffleHog v3
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin
# Verify
trufflehog --version
# Scan filesystem
trufflehog filesystem . --only-verified
# Scan git history
trufflehog git . --since-commit=HEAD~10 --only-verified
# Scan with JSON output for CI/CD
trufflehog filesystem . --json --only-verified > trufflehog-results.json
# Scan a remote repository (no clone needed)
trufflehog git https://github.com/org/repo.git --only-verified
# Scan Docker image
trufflehog docker app:latest --only-verified
# Exclude specific paths
trufflehog filesystem . --exclude-paths=.trufflehogignore --only-verified
# .trufflehogignore โ Paths to exclude
test/
node_modules/
vendor/
*.md
dist/
build/
coverage/
.github/workflows/
Complete GitHub Actions Workflow
# .github/workflows/code-security.yml
name: Code Security โ Comprehensive Scan
on:
push:
branches: [main, develop, 'release/*']
pull_request:
branches: [main]
schedule:
- cron: '0 4 * * *' # Daily at 4 AM UTC
permissions:
contents: read
security-events: write
actions: read
jobs:
gitleaks:
name: GitLeaks Secret Scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history required
- name: Run GitLeaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_CONFIG: .gitleaks.toml
# Optional: Upload results for audit
- name: Upload Report
if: always()
uses: actions/upload-artifact@v4
with:
name: gitleaks-report
path: gitleaks.sarif
trufflehog:
name: TruffleHog Deep Scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: TruffleHog
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: main
head: HEAD
extra_args: --debug --only-verified
# Combined security gate โ either scanner can catch secrets
secrets-gate:
name: Secrets Security Gate
needs: [gitleaks, trufflehog]
runs-on: ubuntu-latest
if: always()
steps:
- name: Evaluate
run: |
if [ "${{ needs.gitleaks.result }}" == "failure" ] || \
[ "${{ needs.trufflehog.result }}" == "failure" ]; then
echo "::error::Secrets detected by one or more scanners!"
echo "Review the scanner output above for details."
echo ""
echo "If this is a false positive:"
echo " 1. Document the finding in the allowlist with justification"
echo " 2. Get security team approval for the allowlist change"
echo " 3. Re-run the pipeline"
exit 1
fi
echo "No secrets detected โ all clear."
Dependency Scanning
Snyk Integration
# .github/workflows/snyk-scan.yml
name: Snyk Dependency Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 3 * * *'
jobs:
snyk:
name: Snyk Security Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: snyk/actions/setup@master
- name: Install dependencies
run: npm ci
- name: Snyk Test
run: snyk test --severity-threshold=high --json-file-output=snyk-results.json
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
continue-on-error: true
- name: Check Critical Vulnerabilities
run: |
CRITICAL_COUNT=$(jq '[.vulnerabilities[]? | select(.severity == "critical")] | length' snyk-results.json || echo "0")
echo "Critical vulnerabilities: $CRITICAL_COUNT"
if [ "$CRITICAL_COUNT" -gt 0 ]; then
echo "::error::Found $CRITICAL_COUNT critical vulnerabilities!"
exit 1
fi
- name: Upload to GitHub Code Scanning
if: always()
run: snyk code test --sarif-file-output=snyk-code.sarif || true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: snyk-code.sarif
category: snyk
Dependabot Configuration
# .github/dependabot.yml
version: 2
updates:
# npm dependencies
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
time: "06:00"
timezone: "America/New_York"
open-pull-requests-limit: 10
reviewers:
- "security-team"
assignees:
- "security-champion"
labels:
- "dependencies"
- "security"
commit-message:
prefix: "security"
include: "scope"
# Security updates only for critical/high
groups:
security-updates:
applies-to: "security-updates"
patterns:
- "*"
update-types:
- "critical"
- "high"
# Ignore list (with documented reasoning)
ignore:
- dependency-name: "legacy-internal-lib"
versions: [">=2.0.0"]
# Reason: Migration in progress, scheduled for Q3
# Docker base images
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
reviewers:
- "platform-team"
labels:
- "docker"
- "security"
# GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "ci-cd"
- "dependencies"
Software Composition Analysis (SCA)
SCA tools analyze open-source components for known vulnerabilities, license compliance, and supply chain risk.
#!/bin/bash
# sca-pipeline.sh โ Software composition analysis pipeline
set -euo pipefail
PROJECT="api-gateway"
VERSION=$(git describe --tags --always)
EXIT_CODE=0
echo "=== Running Software Composition Analysis ==="
# 1. Generate dependency tree
echo "[1/5] Generating dependency tree..."
npm list --all --json > "deps-tree-${VERSION}.json" 2>/dev/null || true
# 2. Run OWASP Dependency-Check
echo "[2/5] OWASP Dependency-Check..."
dependency-check.sh \
--project "$PROJECT" \
--scan . \
--format JSON \
--format HTML \
--out "./reports/dependency-check" \
--enableExperimental \
--failOnCVSS 7 \
|| EXIT_CODE=$?
# 3. Run license check
echo "[3/5] License compliance check..."
npm exec license-checker -- \
--summary \
--onlyAllow 'MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;0BSD' \
--excludePackagesStartingWith "@company/" \
|| { echo "License violation detected!"; EXIT_CODE=1; }
# 4. Generate SBOM
echo "[4/5] Generating SBOM..."
cyclonedx-npm --output-file "sbom-${VERSION}.json" || \
npm exec @cyclonedx/cyclonedx-npm -- --output-file "sbom-${VERSION}.json"
# 5. Sign SBOM
echo "[5/5] Signing SBOM..."
cosign sign-blob "sbom-${VERSION}.json" \
--key env://COSIGN_PRIVATE_KEY \
--output-signature "sbom-${VERSION}.sig" \
--output-certificate "sbom-${VERSION}.cert"
# Archive reports
mkdir -p "reports/${VERSION}"
cp -r "reports/dependency-check/"* "reports/${VERSION}/" 2>/dev/null || true
cp "sbom-${VERSION}.json" "sbom-${VERSION}.sig" "reports/${VERSION}/"
echo "=== SCA Complete โ Exit Code: $EXIT_CODE ==="
exit $EXIT_CODE
Code Signing and Verification
# sign-release.sh โ Code signing with Sigstore/cosign
#!/bin/bash
set -euo pipefail
VERSION="${1:-$(git describe --tags --always)}"
ARTIFACT="dist/app-${VERSION}.tar.gz"
echo "Signing artifact: $ARTIFACT"
# Sign with cosign (keyless via OIDC)
cosign sign-blob "$ARTIFACT" \
--yes \
--output-signature "${ARTIFACT}.sig" \
--output-certificate "${ARTIFACT}.cert"
# Verify signature
cosign verify-blob "$ARTIFACT" \
--signature "${ARTIFACT}.sig" \
--certificate "${ARTIFACT}.cert" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--certificate-identity-regexp "^https://github.com/org/repo/.github/workflows/.*@refs/tags/${VERSION}$"
echo "Artifact signed and verified successfully"
# Upload to release
echo "Uploading to GitHub release ${VERSION}..."
gh release upload "$VERSION" \
"$ARTIFACT" \
"${ARTIFACT}.sig" \
"${ARTIFACT}.cert"
SBOM (Software Bill of Materials) Generation
# .github/workflows/sbom.yml
name: Generate and Sign SBOM
on:
release:
types: [published]
jobs:
sbom:
name: Generate SBOM
runs-on: ubuntu-latest
permissions:
id-token: write # For keyless signing
contents: write
steps:
- uses: actions/checkout@v4
- name: Generate SPDX SBOM
uses: anchore/sbom-action@v0
with:
format: spdx-json
output-file: sbom.spdx.json
- name: Generate CycloneDX SBOM
uses: anchore/sbom-action@v0
with:
format: cyclonedx-json
output-file: sbom.cyclonedx.json
- name: Sign SBOMs with Sigstore
uses: sigstore/cosign-installer@v3
- run: |
cosign sign-blob sbom.spdx.json \
--yes \
--output-signature sbom.spdx.json.sig
cosign sign-blob sbom.cyclonedx.json \
--yes \
--output-signature sbom.cyclonedx.json.sig
- name: Upload SBOM to release
run: |
gh release upload "${{ github.event.release.tag_name }}" \
sbom.spdx.json sbom.spdx.json.sig \
sbom.cyclonedx.json sbom.cyclonedx.json.sig
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Complete Supply Chain Security Pipeline
# .github/workflows/supply-chain-security.yml
name: Supply Chain Security
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
id-token: write
security-events: write
jobs:
provenance:
name: SLSA Provenance
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v4
- name: Build and push container
id: build
uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
provenance: true
sbom: true
- name: Attest build provenance
uses: actions/attest-build-provenance@v1
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
verify:
name: Verify Provenance
needs: [provenance]
runs-on: ubuntu-latest
steps:
- name: Install cosign
uses: sigstore/cosign-installer@v3
- name: Verify image signature
run: |
cosign verify \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--certificate-identity-regexp "^https://github.com/${{ github.repository }}/.github/workflows/.*" \
ghcr.io/${{ github.repository }}@${{ needs.provenance.outputs.digest }}
sast:
name: SAST
uses: ./.github/workflows/sast.yml
sca:
name: SCA
uses: ./.github/workflows/sca.yml
secrets:
name: Secret Scanning
uses: ./.github/workflows/secrets-scan.yml
# Final security gate
gate:
name: Supply Chain Gate
needs: [provenance, verify, sast, sca, secrets]
runs-on: ubuntu-latest
if: always()
steps:
- name: Check all jobs passed
run: |
for job in provenance verify sast sca secrets; do
if [ "$(echo '${{ toJson(needs) }}' | jq -r ".$job.result")" != "success" ]; then
echo "::error::Job $job failed"
exit 1
fi
done
echo "All supply chain security checks passed!"
Branch Protection Rules
#!/bin/bash
# configure-branch-protection.sh โ Enforce branch protection via GitHub API
ORG="mycompany"
REPO="api-gateway"
BRANCH="main"
GITHUB_TOKEN="${GITHUB_ADMIN_TOKEN}"
curl -X PUT "https://api.github.com/repos/${ORG}/${REPO}/branches/${BRANCH}/protection" \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github.v3+json" \
-d '{
"required_status_checks": {
"strict": true,
"contexts": [
"SAST โ SonarQube",
"SCA โ Dependency Check",
"Container Scan โ Trivy",
"Secrets Detection",
"Security Gate"
]
},
"enforce_admins": false,
"required_pull_request_reviews": {
"required_approving_review_count": 2,
"dismiss_stale_reviews": true,
"require_code_owner_reviews": true,
"require_last_push_approval": true,
"bypass_pull_request_allowances": {
"users": ["security-oncall"],
"teams": ["platform-admin"]
}
},
"restrictions": {
"users": [],
"teams": ["platform-team", "security-team"]
},
"required_signatures": true,
"required_linear_history": true,
"allow_force_pushes": false,
"allow_deletions": false,
"required_conversation_resolution": true,
"lock_branch": false,
"allow_fork_syncing": false
}'
# Require signed commits
curl -X POST "https://api.github.com/repos/${ORG}/${REPO}/branches/${BRANCH}/protection/required_signatures" \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github.v3+json"
echo "Branch protection configured for ${BRANCH}"
CODEOWNERS Best Practices
# .github/CODEOWNERS
# Global fallback โ platform team owns everything not explicitly assigned
* @company/platform-team
# Security-critical files โ require security team approval
/.github/workflows/ @company/security-team @company/platform-team
/secrets/ @company/security-team
/iam/ @company/security-team
/policies/ @company/security-team
*.pem @company/security-team
*.key @company/security-team
# Application code โ domain team ownership
/src/auth/ @company/auth-team @company/security-team
/src/payments/ @company/payments-team @company/security-team
/src/user-data/ @company/privacy-team
# Infrastructure โ platform team
/terraform/ @company/platform-team
/kubernetes/ @company/platform-team @company/sre-team
/docker/ @company/platform-team
# Documentation โ docs team with technical review
/docs/ @company/docs-team @company/platform-team
/README.md @company/docs-team
# Database migrations โ require DBA review
/migrations/ @company/dba-team @company/platform-team
# API contracts โ API governance team
/api/ @company/api-team
/openapi/ @company/api-team
# Compliance evidence โ compliance team
/compliance/ @company/compliance-team @company/security-team
GitHub Advanced Security
# .github/codeql/codeql-config.yml
name: "CodeQL Configuration"
queries:
- uses: security-extended
- uses: security-and-quality
- uses: github/codeql/javascript/ql/src/Security@main
paths:
- src/
- lib/
paths-ignore:
- '**/*.test.js'
- '**/*.spec.js'
- 'test/'
- 'dist/'
- 'node_modules/'
# Custom query suites
query-filters:
- exclude:
id: js/missing-rate-limiting
reason: "Handled by API gateway rate limiting"
- exclude:
id: js/insecure-randomness
reason: "Used only for non-cryptographic purposes (display IDs)"
# .github/workflows/codeql.yml
name: CodeQL Analysis
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 2 * * 1' # Weekly Monday 2 AM
jobs:
codeql:
name: CodeQL
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: javascript, typescript, python
config-file: ./.github/codeql/codeql-config.yml
- uses: github/codeql-action/analyze@v3
Security Policy and Reporting Process
<!-- SECURITY.md -->
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 2.x | :white_check_mark: |
| 1.x | :x: |
## Reporting a Vulnerability
We take security seriously. If you discover a security vulnerability,
please report it responsibly using the process below.
### How to Report
**DO NOT** open a public issue for security vulnerabilities.
Instead, please send an email to **security@company.com** with:
- Description of the vulnerability
- Steps to reproduce
- Affected versions
- Possible impact
- Suggested fix (if any)
### Response Timeline
| Stage | Timeline |
|-------|----------|
| Acknowledgment | Within 24 hours |
| Initial Assessment | Within 72 hours |
| Fix Development | Based on severity (Critical: 7 days, High: 30 days) |
| Disclosure | After fix is deployed |
### Security Measures
- All reports are treated confidentially
- We will coordinate disclosure with the reporter
- Credit will be given to the reporter (unless anonymous)
- Bug bounty may be awarded based on severity
### Scope
The following are in scope for security reports:
- API endpoints
- Authentication mechanisms
- Authorization controls
- Data handling and storage
- Infrastructure configurations
- Container images
- Dependencies with known CVEs
### Out of Scope
- Denial of service via brute force (rate limiting is in place)
- Social engineering attacks
- Physical security
- Third-party services not under our direct control
Security Policy Location
Place SECURITY.md at the repository root or in .github/SECURITY.md. GitHub automatically displays a link to this file in the "Security" tab of the repository, making it easy for researchers to find your disclosure policy.
Related Topics
- Secrets Management โ Zero-hardcoded-secrets policy and vault integration
- Vulnerability Scanning โ SAST, DAST, SCA, and container scanning
- SecOps Overview โ Security frameworks and DevSecOps principles