From 19a62001730bd08f4da882ee2f19e5882e780c81 Mon Sep 17 00:00:00 2001 From: RexOwenDev Date: Wed, 15 Apr 2026 03:05:18 +0800 Subject: [PATCH] security: add gitleaks config + GitHub Action for secret scanning Adds gitleaks prevention layer to catch secrets on every push/PR. Custom ruleset detects n8n public-api JWTs, Supabase service-role keys, and Anthropic/OpenAI/Cohere/Resend API keys beyond gitleaks defaults. Placeholder tokens (ANTHROPIC_API_KEY, POSTGRES_CREDENTIAL_ID, etc.) are allowlisted to prevent false positives. --- .github/workflows/gitleaks.yml | 33 ++++++++++++ .gitleaks.toml | 98 ++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 .github/workflows/gitleaks.yml create mode 100644 .gitleaks.toml diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml new file mode 100644 index 0000000..d3e01f4 --- /dev/null +++ b/.github/workflows/gitleaks.yml @@ -0,0 +1,33 @@ +name: gitleaks + +# Runs on every push to any branch + every PR. Catches secrets BEFORE they reach the default branch +# even if a commit was pushed to a topic branch. Uses a custom ruleset at .gitleaks.toml. + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +permissions: + contents: read + pull-requests: write + security-events: write + +jobs: + scan: + name: Scan for secrets + runs-on: ubuntu-latest + steps: + - name: Checkout code (full history for historical scan on PRs) + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run gitleaks + uses: gitleaks/gitleaks-action@v2 + env: + # GITHUB_TOKEN is provided automatically for public repos + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Point at our custom config (or remove this line to use gitleaks defaults only) + GITLEAKS_CONFIG: .gitleaks.toml diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..3e5b680 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,98 @@ +title = "Rex's n8n + Portfolio Gitleaks Rules" + +# Extend gitleaks default ruleset (catches AWS keys, Stripe, Slack tokens, GitHub tokens, etc.) +[extend] +useDefault = true + +# ─── Custom rules for patterns the defaults miss ────────────────────────────── + +[[rules]] +id = "n8n-api-jwt" +description = "n8n public-api JWT (HS256, contains 'aud:public-api')" +# Matches JWTs where the base64-decoded payload claims audience 'public-api' (n8n convention) +# The full pattern is: header.payload.signature where payload base64 contains aud:public-api +regex = '''eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]*cHVibGljLWFwaQ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]{10,}''' +tags = ["jwt", "n8n", "critical"] + +[[rules]] +id = "generic-signed-jwt" +description = "Generic signed JWT (3-part base64, header starts with 'eyJ')" +# Catch-all for any JWT token — flags for human review +regex = '''eyJ[A-Za-z0-9_-]{20,}\.eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}''' +tags = ["jwt"] + +[[rules]] +id = "anthropic-api-key" +description = "Anthropic Claude API key" +regex = '''sk-ant-[a-zA-Z0-9_-]{32,}''' +tags = ["anthropic", "api-key"] + +[[rules]] +id = "openai-api-key" +description = "OpenAI API key" +regex = '''sk-(?:proj-)?[a-zA-Z0-9_-]{40,}''' +tags = ["openai", "api-key"] + +[[rules]] +id = "supabase-service-role-key" +description = "Supabase service role JWT (grants bypass-RLS access — CRITICAL if leaked)" +regex = '''eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]*c2VydmljZV9yb2xl[A-Za-z0-9_-]*\.[A-Za-z0-9_-]{10,}''' +tags = ["supabase", "critical"] + +[[rules]] +id = "cohere-api-key" +description = "Cohere API key" +regex = '''[a-zA-Z0-9]{40,45}''' +path = '''(?i)\.env|cohere''' +tags = ["cohere", "api-key"] + +[[rules]] +id = "resend-api-key" +description = "Resend API key" +regex = '''re_[a-zA-Z0-9_-]{20,}''' +tags = ["resend", "api-key"] + +[[rules]] +id = "pdfco-api-key" +description = "PDF.co API key (typically email:key format in x-api-key header)" +regex = '''[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}_[a-zA-Z0-9]{40,}''' +tags = ["pdfco", "api-key"] + +# ─── Allowlist: patterns that look like secrets but are fine ────────────────── + +[allowlist] +description = "Known non-secret patterns" + +paths = [ + '''\.env\.example$''', + '''.*/tests/fixtures/.*''', + '''.*/__mocks__/.*''', + '''.*/node_modules/.*''', + '''.*/\.next/.*''', + '''.*/dist/.*''', + '''docs/.*\.(jpg|png|svg|jpeg)$''', + '''.*package-lock\.json$''', +] + +# Allowlist specific strings that are documented placeholders +regexes = [ + '''\[ANTHROPIC_API_KEY\]''', + '''\[OPENAI_API_KEY\]''', + '''\[POSTGRES_CREDENTIAL_ID\]''', + '''\[SLACK_[A-Z_]+\]''', + '''\[CLICKUP_[A-Z_]+\]''', + '''\[GMAIL_[A-Z_]+\]''', + '''\[GSUITE_[A-Z_]+\]''', + '''\[NOTION_[A-Z_]+\]''', + '''\[BAMBOOHR_[A-Z_]+\]''', + '''\[TYPEFORM_[A-Z_]+\]''', + '''\[HUBSPOT_[A-Z_]+\]''', + '''\[LINEAR_[A-Z_]+\]''', + '''\[CANVA_[A-Z_]+\]''', + '''\[GITHUB_[A-Z_]+\]''', + '''\[COMPANY_[A-Z_]+\]''', + '''\[CLIENT_[A-Z_]+\]''', + '''xoxb-YOUR_TOKEN''', + '''sk-ant-YOUR_KEY''', + '''sk-YOUR_KEY''', +]