Skip to content

feat: gitleaks secret-leak guardrails#11

Merged
bglusman merged 2 commits intomainfrom
feat/secret-guardrails
Apr 23, 2026
Merged

feat: gitleaks secret-leak guardrails#11
bglusman merged 2 commits intomainfrom
feat/secret-guardrails

Conversation

@bglusman
Copy link
Copy Markdown
Owner

Summary

Defense in depth against accidental commit of secrets and infrastructure identifiers into this public repo:

  • .gitleaks.toml — extends gitleaks' 40+ built-in rules with project-specific patterns (personal domains, home LAN/VPN IP ranges, DDNS hostnames, Bearer-tokens-in-header). Allowlists test fixtures, *.example.* files, RFC 5737 documentation IPs, and RFC 2606 example domains.
  • .github/workflows/secret-scan.yml — runs gitleaks on every push and PR. Blocks merge on any finding.
  • CLAUDE.md — explicit instructions for AI agents operating in this repo. What not to commit, what placeholders to use instead, how to run the local pre-push scan, and how to get a false positive properly allowlisted.
  • .gitignore — adds common secret-file shapes (.env, *.pem, *.key, *_rsa, secrets/, etc.) so obvious credential files can't be added accidentally.

Motivation

A recent session surfaced that the repo had hardcoded references to the maintainer's private infra (vault.enjyn.com, internal IPs). Nothing catastrophic — the URL is in cert-transparency logs anyway — but it's free reconnaissance for anyone targeting the maintainer's homelab. The rules below would have caught the pattern at commit time. Codifying the post-mortem so the next such accident hits a CI gate instead of a post-merge scramble.

Test plan

  • gitleaks protect --staged --config .gitleaks.toml passes on this PR's own diff
  • gitleaks detect --source . with this config detects historical matches as expected (confirms rules work)
  • CI job runs green on this PR (will verify after opening)
  • Manual sanity test: commit a test file with vault.enjyn.com in a scratch branch, confirm CI blocks merge

Not in scope (follow-ups)

  • Scrubbing historical vault.enjyn.com references from current files — deferred; maintainer plans full history rewrite during future rename
  • fail2ban at the vaultwarden host — tracked separately (hit a Debian 12 fail2ban datepattern bug)
  • Migrating openclaw-gateway.service inline env-secrets to onecli+vault — tracked separately

🤖 Generated with Claude Code

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds “defense in depth” guardrails to prevent accidental leakage of secrets and private infrastructure identifiers in this public repository.

Changes:

  • Introduces a project-specific gitleaks configuration extending default rules with infra-leak patterns and allowlists.
  • Adds a GitHub Actions workflow to run gitleaks on PRs/pushes and block merges on findings.
  • Adds contributor/agent guidance (CLAUDE.md) and expands .gitignore with common secret-file patterns.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 6 comments.

File Description
CLAUDE.md Documents AI/contributor do-not-commit rules and local scanning guidance.
.gitleaks.toml Defines custom infra-leak rules + allowlists on top of gitleaks defaults.
.gitignore Prevents accidental addition of common credential file types.
.github/workflows/secret-scan.yml CI enforcement: runs gitleaks and blocks merges on findings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/secret-scan.yml Outdated

permissions:
contents: read
pull-requests: read
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GITLEAKS_ENABLE_COMMENTS: true implies the action will attempt to create PR comments/annotations, but this workflow grants pull-requests: read only. If the action tries to comment, it may fail due to insufficient permissions. Either grant pull-requests: write (and any other required scopes) or disable comment posting.

Suggested change
pull-requests: read
pull-requests: write

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/secret-scan.yml Outdated
Comment on lines +8 to +9
# Run on every push to any branch so early feedback is available to PR authors
# before they open a PR. Remove this trigger if noise becomes a problem.
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says this runs “on every push to any branch”, but push.branches: [main] restricts it to main only. Either adjust the trigger to match the comment (run on all branches) or update the comment to match the actual trigger.

Suggested change
# Run on every push to any branch so early feedback is available to PR authors
# before they open a PR. Remove this trigger if noise becomes a problem.
# Run on pushes to main. Add more branches here if early feedback before
# opening a PR is needed for additional branches.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +26
with:
# Scan full history on PRs so we catch anything the PR introduces,
# no matter what base it forked from.
fetch-depth: 0
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This workflow explicitly fetches full git history (fetch-depth: 0) and the inline comment states the intent is to scan full history. However, the PR description notes existing historical matches are expected and are not being scrubbed in this PR; scanning history will keep failing until a baseline/allowlist/history rewrite is done. Consider scanning only the PR diff/new commits (or using a baseline file) so the CI gate focuses on newly introduced leaks.

Copilot uses AI. Check for mistakes.
Comment thread CLAUDE.md Outdated
Comment on lines +37 to +44
This repo has a gitleaks config (`.gitleaks.toml`) enforced in CI
(`.github/workflows/secret-scan.yml`). You can run the same check locally
before pushing:

```
brew install gitleaks # or apt/download
gitleaks detect --source . --config .gitleaks.toml --verbose
```
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documented “Pre-push local check” uses gitleaks detect --source ., which may fail for contributors if the repo has existing findings outside their change (and it doesn’t match the PR’s own test plan which uses gitleaks protect --staged). Consider updating the instructions to the intended developer workflow (e.g., protect --staged for pre-push, and reserve full detect/history scans for dedicated audits or with a baseline).

Copilot uses AI. Check for mistakes.
Comment thread .gitleaks.toml Outdated
Comment on lines +25 to +26
description = "Maintainer's personal domain (any subdomain)"
regex = '''[a-z0-9-]+\.enjyn\.com'''
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enjyn-domain rule only matches subdomains like foo.enjyn.com and will not match the apex domain enjyn.com, even though the repo guidance says to avoid committing enjyn.com as well. Consider expanding the regex (or adding a second rule) to catch the apex domain too, and ideally make it case-insensitive.

Suggested change
description = "Maintainer's personal domain (any subdomain)"
regex = '''[a-z0-9-]+\.enjyn\.com'''
description = "Maintainer's personal domain (apex or any subdomain)"
regex = '''(?i)(?:[a-z0-9-]+\.)*enjyn\.com'''

Copilot uses AI. Check for mistakes.
Comment thread .gitleaks.toml Outdated
Comment on lines +3 to +4
# Enforced in CI via .github/workflows/secret-scan.yml. Fails the PR if any of
# these patterns appear in the diff.
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header comment claims this config “Fails the PR if any of these patterns appear in the diff”, but the workflow is configured to fetch full history and the gitleaks action typically scans the repository (and may scan git history). Please align the comment with the actual behavior (diff-only vs repo/history scan) to avoid misleading contributors.

Suggested change
# Enforced in CI via .github/workflows/secret-scan.yml. Fails the PR if any of
# these patterns appear in the diff.
# Enforced in CI via .github/workflows/secret-scan.yml. Fails CI when these
# patterns are detected during the repository scan (and, if configured, git history scan).

Copilot uses AI. Check for mistakes.
Adds a secret- and infrastructure-disclosure scanner with CI enforcement,
plus agent instructions that encode the same rules for anyone touching
this repo. Split across two files by audience:

- .gitleaks.toml (public, in-repo) — generic categorical rules: private
  IP ranges (RFC 1918, RFC 6598 CGNAT), Bearer-token-in-header,
  basic-auth-in-URL, plus gitleaks' 40+ built-in token patterns.
  Enforced in CI for every PR. Catches the SHAPE of disclosure without
  naming any specific deployment's identifiers.

- .gitleaks.local.toml (gitignored) — per-deployment specifics (your
  personal domains, DDNS hostnames, Matrix handles, internal model
  names). Template at .gitleaks.local.toml.example shows how to
  populate. Developers opt into the stricter local check with
  `gitleaks detect --config .gitleaks.local.toml`.

Motivation: a first-round draft of this guardrail named specific domains
and service identifiers directly in the public config, which is itself
infrastructure disclosure — the file exposes what it's trying to protect.
Two-layer design fixes that: generic patterns ship in-repo, specific
ones stay local.

## Files

- .gitleaks.toml — public rules + allowlist (RFC-reserved ranges, test
  fixtures, lockfiles, loopback/localhost)
- .gitleaks.local.toml.example — template with commented-out examples
  showing the shape for personal domains, DDNS, chat handles, model
  names; `.gitleaks.local.toml` itself is gitignored
- .github/workflows/secret-scan.yml — CI runs gitleaks on every push/PR
  with .gitleaks.toml; fails merge on any finding
- CLAUDE.md — agent instructions: never commit specifics, how to use
  the two-layer scanner, how to add allowlist entries safely
- .gitignore — adds `.env`/`.pem`/`.key`/`secrets/` style patterns,
  .gitleaks.local.toml, and homeserver.log* (local testing artifact)

## Test plan

- `gitleaks protect --staged --config .gitleaks.toml` passes on this
  PR's own diff (verified)
- CI workflow runs gitleaks on every PR going forward
- Manual: a test commit adding a private IP literal (not in allowlist)
  would fail scan — rules are verified empirically

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bglusman bglusman force-pushed the feat/secret-guardrails branch from 6c3e453 to 89a322e Compare April 23, 2026 16:17
Addresses Copilot review feedback on #11:
- Workflow has GITLEAKS_ENABLE_COMMENTS=true but only read pull-requests
  scope. Posting inline comments needs write; flipped and documented
  when it's safe to drop back to read.
- "every push to any branch" comment contradicted the push.branches=main
  filter. Comment now matches behavior.
- Pre-push instructions in CLAUDE.md used `detect --source .` which
  spuriously fails for developers when prior history has findings.
  Now recommends `protect --staged` for the pre-commit gate; reserves
  `detect` for full history audits.
- Clarified the `.gitleaks.toml` header comment: CI scans the repo +
  history, not a diff.
Copilot AI review requested due to automatic review settings April 23, 2026 18:16
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +5 to +8
branches: [main]
# Also run on pushes to main so we catch anything merged without a PR gate.
push:
branches: [main]
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow only triggers on main, but the existing CI workflows trigger on both main and master (.github/workflows/ci.yml:5-7, integration-tests.yml:5-7). If master is still in use (or kept for compatibility), secret scanning won’t run there. Consider adding master to the pull_request and push branch filters (or removing master from the other workflows if it’s truly unused) so coverage is consistent.

Suggested change
branches: [main]
# Also run on pushes to main so we catch anything merged without a PR gate.
push:
branches: [main]
branches: [main, master]
# Also run on pushes to main/master so we catch anything merged without a PR gate.
push:
branches: [main, master]

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +37
- name: Checkout
uses: actions/checkout@v4
with:
# Scan full history on PRs so we catch anything the PR introduces,
# no matter what base it forked from.
fetch-depth: 0

- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_CONFIG: .gitleaks.toml
# Exit non-zero if anything is found so the job fails and blocks the PR.
GITLEAKS_ENABLE_COMMENTS: true
GITLEAKS_ENABLE_SUMMARY: true
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With fetch-depth: 0 and the gitleaks action, this job will scan the whole repo (and likely history), which will currently fail on already-committed private IPs (e.g. docs/MANUAL_INSTALL.md contains 192.168.1.210, scripts/targets.txt contains 192.168.1.210, BACKLOG.md contains 192.168.1.210). If the intent is to block new leaks without immediately breaking CI, add a baseline allowlist (gitleaks supports baselines) or change the CI invocation to a diff/range scan for PRs and reserve full-history scans for workflow_dispatch/scheduled runs.

Suggested change
- name: Checkout
uses: actions/checkout@v4
with:
# Scan full history on PRs so we catch anything the PR introduces,
# no matter what base it forked from.
fetch-depth: 0
- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_CONFIG: .gitleaks.toml
# Exit non-zero if anything is found so the job fails and blocks the PR.
GITLEAKS_ENABLE_COMMENTS: true
GITLEAKS_ENABLE_SUMMARY: true
- name: Checkout for PRs and pushes
if: ${{ github.event_name != 'workflow_dispatch' }}
uses: actions/checkout@v4
with:
# Only fetch the commits needed for diff-based CI scans so existing
# historical findings do not immediately fail the workflow.
fetch-depth: 2
- name: Checkout full history for manual scans
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: actions/checkout@v4
with:
# Reserve full-history scans for explicitly requested manual runs.
fetch-depth: 0
- name: Install gitleaks
run: |
GITLEAKS_VERSION=8.24.2
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" -o gitleaks.tar.gz
tar -xzf gitleaks.tar.gz gitleaks
sudo install -m 0755 gitleaks /usr/local/bin/gitleaks
- name: Run gitleaks on PR commits
if: ${{ github.event_name == 'pull_request' }}
env:
GITLEAKS_CONFIG: .gitleaks.toml
run: |
# Scan only the commits introduced by the PR so CI blocks new leaks
# without failing on unrelated pre-existing findings.
gitleaks git \
--config "${GITLEAKS_CONFIG}" \
--log-opts="${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
- name: Run gitleaks on pushed commits
if: ${{ github.event_name == 'push' }}
env:
GITLEAKS_CONFIG: .gitleaks.toml
run: |
# Scan only the commits included in this push.
gitleaks git \
--config "${GITLEAKS_CONFIG}" \
--log-opts="${{ github.event.before }}..${{ github.sha }}"
- name: Run full-history gitleaks scan
if: ${{ github.event_name == 'workflow_dispatch' }}
env:
GITLEAKS_CONFIG: .gitleaks.toml
run: |
# Manual runs perform the comprehensive repository/history scan.
gitleaks git --config "${GITLEAKS_CONFIG}"

Copilot uses AI. Check for mistakes.
Comment thread .gitleaks.toml
Comment on lines +75 to +76
'''\.github/workflows/secret-scan\.yml$''',
'''CLAUDE\.md$''',
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[allowlist].paths currently skips scanning CLAUDE.md and .github/workflows/secret-scan.yml. That creates a blind spot where secrets or infra identifiers could be committed into documentation/workflows without being caught by CI. Prefer removing these from the allowlist and, if needed, allowlisting only the specific safe literals that were causing false positives.

Suggested change
'''\.github/workflows/secret-scan\.yml$''',
'''CLAUDE\.md$''',

Copilot uses AI. Check for mistakes.
Comment thread .gitleaks.toml
Comment on lines +9 to +22
# TWO-LAYER DESIGN — why this file is deliberately generic:
# - This file ships in the public repo, so anything named here is itself
# disclosed. A rule that names a specific domain teaches attackers which
# domain to search for. So this file carries only patterns generic to a
# class of users, not specific to any deployment.
# - Personal / per-deployment patterns (specific domains, service
# identifiers tied to a real user) live in `.gitleaks.local.toml` — a
# gitignored file. See `.gitleaks.local.toml.example` for the template +
# how to extend. CI runs only this file; developers opt into the stricter
# local config for their own commits:
# gitleaks detect --config .gitleaks.local.toml
#
# Rules here catch the SHAPE of disclosure (private-IP ranges, generic
# credential-in-header patterns), not any specific user's identifiers.
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says .gitleaks.toml adds project-specific rules for personal domains/DDNS hostnames, but this committed config is intentionally generic and does not include any domain/DDNS patterns. As a result, CI won’t flag leaks like the existing vault.enjyn.com references in the repo (e.g. crates/onecli-client/src/vault.rs, crates/onecli-client/VAULT_SETUP.md). Either update the PR description to match the two-layer design, or add additional generic domain/hostname rules that are safe to publish (and keep truly personal identifiers in .gitleaks.local.toml).

Copilot uses AI. Check for mistakes.
@bglusman bglusman marked this pull request as ready for review April 23, 2026 18:26
@bglusman bglusman merged commit 62d2eff into main Apr 23, 2026
21 checks passed
bglusman added a commit that referenced this pull request Apr 25, 2026
* feat: gitleaks guardrails — two-layer (public generic + local specific)

Adds a secret- and infrastructure-disclosure scanner with CI enforcement,
plus agent instructions that encode the same rules for anyone touching
this repo. Split across two files by audience:

- .gitleaks.toml (public, in-repo) — generic categorical rules: private
  IP ranges (RFC 1918, RFC 6598 CGNAT), Bearer-token-in-header,
  basic-auth-in-URL, plus gitleaks' 40+ built-in token patterns.
  Enforced in CI for every PR. Catches the SHAPE of disclosure without
  naming any specific deployment's identifiers.

- .gitleaks.local.toml (gitignored) — per-deployment specifics (your
  personal domains, DDNS hostnames, Matrix handles, internal model
  names). Template at .gitleaks.local.toml.example shows how to
  populate. Developers opt into the stricter local check with
  `gitleaks detect --config .gitleaks.local.toml`.

Motivation: a first-round draft of this guardrail named specific domains
and service identifiers directly in the public config, which is itself
infrastructure disclosure — the file exposes what it's trying to protect.
Two-layer design fixes that: generic patterns ship in-repo, specific
ones stay local.

## Files

- .gitleaks.toml — public rules + allowlist (RFC-reserved ranges, test
  fixtures, lockfiles, loopback/localhost)
- .gitleaks.local.toml.example — template with commented-out examples
  showing the shape for personal domains, DDNS, chat handles, model
  names; `.gitleaks.local.toml` itself is gitignored
- .github/workflows/secret-scan.yml — CI runs gitleaks on every push/PR
  with .gitleaks.toml; fails merge on any finding
- CLAUDE.md — agent instructions: never commit specifics, how to use
  the two-layer scanner, how to add allowlist entries safely
- .gitignore — adds `.env`/`.pem`/`.key`/`secrets/` style patterns,
  .gitleaks.local.toml, and homeserver.log* (local testing artifact)

## Test plan

- `gitleaks protect --staged --config .gitleaks.toml` passes on this
  PR's own diff (verified)
- CI workflow runs gitleaks on every PR going forward
- Manual: a test commit adding a private IP literal (not in allowlist)
  would fail scan — rules are verified empirically

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(secret-scan): permissions, comment alignment, pre-push guidance

Addresses Copilot review feedback on #11:
- Workflow has GITLEAKS_ENABLE_COMMENTS=true but only read pull-requests
  scope. Posting inline comments needs write; flipped and documented
  when it's safe to drop back to read.
- "every push to any branch" comment contradicted the push.branches=main
  filter. Comment now matches behavior.
- Pre-push instructions in CLAUDE.md used `detect --source .` which
  spuriously fails for developers when prior history has findings.
  Now recommends `protect --staged` for the pre-commit gate; reserves
  `detect` for full history audits.
- Clarified the `.gitleaks.toml` header comment: CI scans the repo +
  history, not a diff.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bglusman bglusman deleted the feat/secret-guardrails branch May 1, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants