Conversation
There was a problem hiding this comment.
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
.gitignorewith 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.
|
|
||
| permissions: | ||
| contents: read | ||
| pull-requests: read |
There was a problem hiding this comment.
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.
| pull-requests: read | |
| pull-requests: write |
| # 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. |
There was a problem hiding this comment.
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.
| # 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. |
| with: | ||
| # Scan full history on PRs so we catch anything the PR introduces, | ||
| # no matter what base it forked from. | ||
| fetch-depth: 0 |
There was a problem hiding this comment.
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.
| 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 | ||
| ``` |
There was a problem hiding this comment.
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).
| description = "Maintainer's personal domain (any subdomain)" | ||
| regex = '''[a-z0-9-]+\.enjyn\.com''' |
There was a problem hiding this comment.
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.
| 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''' |
| # Enforced in CI via .github/workflows/secret-scan.yml. Fails the PR if any of | ||
| # these patterns appear in the diff. |
There was a problem hiding this comment.
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.
| # 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). |
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>
6c3e453 to
89a322e
Compare
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.
There was a problem hiding this comment.
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.
| branches: [main] | ||
| # Also run on pushes to main so we catch anything merged without a PR gate. | ||
| push: | ||
| branches: [main] |
There was a problem hiding this comment.
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.
| 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] |
| - 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 |
There was a problem hiding this comment.
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.
| - 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}" |
| '''\.github/workflows/secret-scan\.yml$''', | ||
| '''CLAUDE\.md$''', |
There was a problem hiding this comment.
[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.
| '''\.github/workflows/secret-scan\.yml$''', | |
| '''CLAUDE\.md$''', |
| # 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. |
There was a problem hiding this comment.
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).
* 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>
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.tomlpasses on this PR's own diffgitleaks detect --source .with this config detects historical matches as expected (confirms rules work)vault.enjyn.comin a scratch branch, confirm CI blocks mergeNot in scope (follow-ups)
vault.enjyn.comreferences from current files — deferred; maintainer plans full history rewrite during future renameopenclaw-gateway.serviceinline env-secrets to onecli+vault — tracked separately🤖 Generated with Claude Code