Skip to content

Integrated Workflow

Security tools only work if they run automatically. Pre-commit hooks catch issues before commit. CI pipelines enforce checks before merge.

Automated Enforcement

Configure hooks once. Security runs on every commit with zero friction. Developers can't bypass the checks.

Pre-commit Hooks (.pre-commit-config.yaml)

repos:
  - repo: local
    hooks:
      - id: gofmt
        name: Format Go code
        entry: gofmt -s -w
        language: system
        files: '\.go$'

      - id: go-vet
        name: Go vet
        entry: go vet ./...
        language: system
        files: '\.go$'
        pass_filenames: false

      - id: golangci-lint
        name: golangci-lint
        entry: golangci-lint run
        language: system
        files: '\.go$'
        pass_filenames: false

      - id: gocyclo
        name: Check cyclomatic complexity
        entry: >
          bash -c '
          command -v gocyclo >/dev/null || go install github.com/fzipp/gocyclo/cmd/gocyclo@latest;
          gocyclo -over 15 .
          '
        language: system
        files: '\.go$'
        pass_filenames: false

      - id: go-test
        name: Run Go tests
        entry: >
          bash -c '
          command -v gotestsum >/dev/null || go install gotest.tools/gotestsum@latest;
          gotestsum --format testdox -- -race ./...
          '
        language: system
        files: '\.go$'
        pass_filenames: false

      - id: go-coverage
        name: Check test coverage threshold
        entry: >
          bash -c '
          command -v gotestsum >/dev/null || go install gotest.tools/gotestsum@latest;
          THRESHOLD=$(grep -A2 "project:" codecov.yml | grep "target:" | head -1 | sed "s/.*target: *\([0-9]*\).*/\1/") || THRESHOLD=95;
          gotestsum --format testdox -- -race -coverprofile=/tmp/coverage.out -covermode=atomic ./... &&
          COVERAGE=$(go tool cover -func=/tmp/coverage.out | grep total | awk "{print \$3}" | sed "s/%//") &&
          if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) )); then
            echo "Coverage ${COVERAGE}% is below ${THRESHOLD}%"; exit 1;
          fi
          '
        language: system
        files: '\.go$'
        pass_filenames: false
        stages: [pre-push]

CI Pipeline (.github/workflows/ci.yml)

name: CI

on:
  push:
    branches: [main]
  pull_request:

permissions: {}

jobs:
  lint:
    permissions:
      contents: read
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-go@v6
        with:
          go-version: "1.23"

      - name: golangci-lint
        uses: golangci/golangci-lint-action@v7
        with:
          version: latest
          args: --timeout 5m

  test:
    permissions:
      contents: read
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-go@v6
        with:
          go-version: "1.23"

      - name: Install gotestsum
        run: go install gotest.tools/gotestsum@latest

      - name: Run tests with coverage and race detection
        run: |
          gotestsum \
            --junitfile junit.xml \
            --format testdox \
            -- -v -race -coverprofile=coverage.out -covermode=atomic ./...

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v5
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./coverage.out
          flags: unit

  security:
    permissions:
      contents: read
      security-events: write
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Run Trivy scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'

      - name: Upload Trivy results to GitHub Security
        uses: github/codeql-action/upload-sarif@v4
        with:
          sarif_file: 'trivy-results.sarif'
          category: 'trivy'

      - name: TruffleHog secret scan
        uses: trufflesecurity/trufflehog@main
        with:
          extra_args: --only-verified

      - name: Generate SBOM
        run: |
          curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
          syft packages . -o cyclonedx-json > sbom.cdx.json

      - name: Upload SBOM artifact
        uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.cdx.json

Comments