41 pages ยท 8 sections
Ctrl K
GitHub Portfolio

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