From f96a4aa6ac4d75d24a73416bea9aa9801a049d79 Mon Sep 17 00:00:00 2001 From: Lacy Morrow Date: Mon, 6 Jul 2026 01:09:42 -0400 Subject: [PATCH] chore(security): add gitleaks CI + pre-commit secret scan (LAC-2555) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Defense-in-depth to prevent plaintext credentials from reaching the repo. - `.github/workflows/gitleaks.yml` — Gitleaks Action on every PR/main push. - `.gitleaks.toml` — extends default rules; allowlists example env files, docs, and generated LLM crawl fixtures. - `.githooks/pre-commit` — local scan of staged changes when gitleaks is installed; graceful skip + warning otherwise (CI still runs). - `package.json` postinstall wires `core.hooksPath` to `.githooks/` on every install so the hook auto-activates without a new devDependency. If a real secret is caught: rotate first, then remove from the diff (knowledge/agent-common.md → Secret handling). --- .githooks/pre-commit | 25 +++++++++++++++++++++++++ .github/workflows/gitleaks.yml | 30 ++++++++++++++++++++++++++++++ .gitleaks.toml | 23 +++++++++++++++++++++++ package.json | 3 ++- 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100755 .githooks/pre-commit create mode 100644 .github/workflows/gitleaks.yml create mode 100644 .gitleaks.toml diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..27db6b0 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,25 @@ +#!/usr/bin/env sh +# Secret scanner — LAC-2555 +# +# Blocks commits that introduce plaintext credentials. +# Requires `gitleaks` locally; if not installed we print a warning and +# fall through — CI still runs the same scan on the PR, so this is +# defense-in-depth, not the only gate. +# +# Install: +# macOS → brew install gitleaks +# linux → https://github.com/gitleaks/gitleaks/releases + +if command -v gitleaks >/dev/null 2>&1; then + if ! gitleaks protect --staged --redact --no-banner; then + echo "" + echo "gitleaks found a potential secret in staged changes." + echo "If real: rotate the credential first, then remove it from the diff." + echo "If a false positive: extend .gitleaks.toml allowlist or add an inline" + echo "'gitleaks:allow' comment. Do not disable the hook." + exit 1 + fi +else + echo "gitleaks not installed — skipping local secret scan (CI still runs)." + echo " install: brew install gitleaks" +fi diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml new file mode 100644 index 0000000..235c0ec --- /dev/null +++ b/.github/workflows/gitleaks.yml @@ -0,0 +1,30 @@ +# Secret scan — LAC-2555 +# +# Fails the PR/push if plaintext credentials appear in the diff. +# Config lives in .gitleaks.toml at the repo root. +# +# Local defense-in-depth: .husky/pre-commit runs the same tool on staged +# changes when the gitleaks binary is installed. + +name: gitleaks + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + gitleaks: + name: gitleaks + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..ca606d3 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,23 @@ +# gitleaks config — LAC-2555 +# +# Extends the built-in rule set. Allowlist covers files that legitimately +# contain placeholder credentials (example env files, docs, changelogs). +# When you add a new placeholder file that trips gitleaks, extend the +# `paths` list here rather than removing the scan. + +[extend] +useDefault = true + +[allowlist] +description = "Placeholder-credential files and generated docs" +paths = [ + '''(^|/)\.env\.example(\..+)?$''', + '''(^|/)\.env\.example\.local$''', + '''(^|/)\.env\.test$''', + '''(^|/)\.env\.example\.production$''', + '''(^|/)README\.md$''', + '''(^|/)CHANGELOG\.md$''', + '''(^|/)LICENSE$''', + '''(^|/)docs/''', + '''(^|/)public/llms(-full)?\.txt$''', +] diff --git a/package.json b/package.json index d554b3d..bbfd941 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "test:coverage": "vitest run --coverage", "test:browser": "vitest --config=vitest.config.browser.ts", "test:node": "vitest --config=vitest.config.node.ts", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "postinstall": "git config core.hooksPath .githooks 2>/dev/null || true" }, "dependencies": { "@auth/drizzle-adapter": "1.11.1",