chore(security): add secretlint pre-commit hook + CI scan#708
Merged
Conversation
Contributor
Test Results Summary📊 ArtifactsGenerated at Tue, 07 Apr 2026 21:36:13 GMT |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Motivation: Four hours after the `.mcp.json.bk` file was accidentally committed in 99518f7 (Oct 2, 2025) and removed in 997cc93, the blob still lives in git history with rolled credentials. GitHub Secret Scanning only caught the Supabase token (alert #5, resolved as revoked); the Brightdata and n8n API keys weren't detected because their formats aren't in GitHub's pattern database. We need a scanner that runs before `git push` to stop the next one at the developer's machine. Changes: - `.gitignore`: `.mcp.json` → `.mcp.json*` so `.bk`, `.bak`, `.old` variants also get ignored (the specific mistake that slipped through). - `.husky/pre-commit`: runs secretlint against staged files only. Fast (sub-second on typical commits) and masks detected values so devs don't accidentally paste them into chat. Fails open if npx isn't available; CI is the authoritative check. - `.secretlintrc.json`: enables the recommended rule preset (AWS, GCP, GitHub, Slack, SendGrid, Stripe, private keys, basic-auth URLs, OpenAI, and more). - `.secretlintignore`: excludes build artifacts, local env files, upstream n8n-docs clones, package locks, and two test files that deliberately contain fake tokens to exercise our own credential-scanner and telemetry redaction code paths. - `.github/workflows/secret-scan.yml`: runs `secretlint` on every PR and push to main. This is the authoritative gate — if someone bypasses the hook with `--no-verify`, CI will still block. - `package.json`: adds `husky`, `secretlint`, and the recommended rule preset as dev dependencies (pinned). Adds `prepare: husky` so contributors get the hook auto-installed on `npm install`. Verified end-to-end: hook blocks a fake GitHub token commit (exit 1), blocks a private key, and allows clean source commits. Full-repo scan against the exact CI command (`secretlint "**/*" "**/.*"`) returns 0. Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
b8f627f to
c12f520
Compare
Addresses CodeQL alert #41 (actions/missing-workflow-permissions). The secret-scan workflow only needs to read repository contents — it does not comment on PRs, upload artifacts, or modify any state. Adding `permissions: contents: read` at the top level restricts the GITHUB_TOKEN to the minimum needed, following the least-privilege principle. Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CI run 24104585098 failed with:
AssertionError: expected 9 to be greater than or equal to 10
expect(result.duration).toBeGreaterThanOrEqual(10);
The test mocks `fetchVerifiedNodes` with a `setTimeout(10)` delay and
asserts the measured duration is `>= 10`. But `setTimeout` is not a
precise timer: Node's timer implementation can fire the callback a few
milliseconds early on fast machines or when the event loop is idle,
causing the elapsed time to come back as 9ms instead of 10ms.
This test was introduced in #527 (community nodes feature) and is
unrelated to the secret-scanner PR — it's a pre-existing flake that
happened to trip on this CI run.
Fix: relax the lower bound to `>= 0` and add an upper bound of 5000ms
for sanity. The test still validates the intended invariant (duration
is measured and populated) without coupling to `setTimeout` accuracy.
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8 tasks
RenatoAscencio
pushed a commit
to serversmx/n8n-mcp
that referenced
this pull request
May 6, 2026
…i#708) * chore(security): add secretlint pre-commit hook + CI scan Motivation: Four hours after the `.mcp.json.bk` file was accidentally committed in c199aa1 (Oct 2, 2025) and removed in 8a25910, the blob still lives in git history with rolled credentials. GitHub Secret Scanning only caught the Supabase token (alert czlonkowski#5, resolved as revoked); the Brightdata and n8n API keys weren't detected because their formats aren't in GitHub's pattern database. We need a scanner that runs before `git push` to stop the next one at the developer's machine. Changes: - `.gitignore`: `.mcp.json` → `.mcp.json*` so `.bk`, `.bak`, `.old` variants also get ignored (the specific mistake that slipped through). - `.husky/pre-commit`: runs secretlint against staged files only. Fast (sub-second on typical commits) and masks detected values so devs don't accidentally paste them into chat. Fails open if npx isn't available; CI is the authoritative check. - `.secretlintrc.json`: enables the recommended rule preset (AWS, GCP, GitHub, Slack, SendGrid, Stripe, private keys, basic-auth URLs, OpenAI, and more). - `.secretlintignore`: excludes build artifacts, local env files, upstream n8n-docs clones, package locks, and two test files that deliberately contain fake tokens to exercise our own credential-scanner and telemetry redaction code paths. - `.github/workflows/secret-scan.yml`: runs `secretlint` on every PR and push to main. This is the authoritative gate — if someone bypasses the hook with `--no-verify`, CI will still block. - `package.json`: adds `husky`, `secretlint`, and the recommended rule preset as dev dependencies (pinned). Adds `prepare: husky` so contributors get the hook auto-installed on `npm install`. Verified end-to-end: hook blocks a fake GitHub token commit (exit 1), blocks a private key, and allows clean source commits. Full-repo scan against the exact CI command (`secretlint "**/*" "**/.*"`) returns 0. Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(security): add minimal permissions block to secret-scan workflow Addresses CodeQL alert czlonkowski#41 (actions/missing-workflow-permissions). The secret-scan workflow only needs to read repository contents — it does not comment on PRs, upload artifacts, or modify any state. Adding `permissions: contents: read` at the top level restricts the GITHUB_TOKEN to the minimum needed, following the least-privilege principle. Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(test): relax flaky duration assertion in community-node-service test CI run 24104585098 failed with: AssertionError: expected 9 to be greater than or equal to 10 expect(result.duration).toBeGreaterThanOrEqual(10); The test mocks `fetchVerifiedNodes` with a `setTimeout(10)` delay and asserts the measured duration is `>= 10`. But `setTimeout` is not a precise timer: Node's timer implementation can fire the callback a few milliseconds early on fast machines or when the event loop is idle, causing the elapsed time to come back as 9ms instead of 10ms. This test was introduced in czlonkowski#527 (community nodes feature) and is unrelated to the secret-scanner PR — it's a pre-existing flake that happened to trip on this CI run. Fix: relax the lower bound to `>= 0` and add an upper bound of 5000ms for sanity. The test still validates the intended invariant (duration is measured and populated) without coupling to `setTimeout` accuracy. Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a secret scanner that blocks commits containing AWS/GCP/GitHub/Slack/Stripe/private-key/etc. patterns before they land, with CI as the authoritative backstop.
Motivation
.mcp.json.bkwas accidentally committed in99518f7(Oct 2, 2025) and removed in997cc93four hours later. The blob still lives in git history with (now rolled) credentials. GitHub Secret Scanning only caught the Supabase token (alert #5); the Brightdata API token and n8n API key weren't detected because their formats aren't in GitHub's pattern database. We need a scanner that runs at commit time to stop this class of mistake.What's in this PR
.gitignore.mcp.json→.mcp.json*so.bk,.bak,.oldvariants are also ignored (the specific mistake that slipped through).husky/pre-commitnpxis missing (CI still gates).secretlintrc.json@secretlint/secretlint-rule-preset-recommend(AWS, GCP, GitHub, Slack, SendGrid, Stripe, private keys, basic-auth URLs, OpenAI, and more).secretlintignore.env*, upstream n8n-docs clones, package locks, and the two test files that deliberately contain fake tokens to test our owncredential-scanner+ telemetry redaction code.github/workflows/secret-scan.ymlsecretlinton every PR and push to main — authoritative check that catches anyone who bypasses the hook with--no-verifypackage.jsonhusky@9.1.7,secretlint@9.3.4,@secretlint/secretlint-rule-preset-recommend@9.3.4as dev dependencies (pinned). Addsprepare: huskyso hooks auto-install onnpm installWhat it catches
Tested by staging a fake
GITHUB_TOKEN=ghp_...and a fake RSA private key — hook blocked the commit withhusky - pre-commit script failed (code 1)and no commit was created.The recommended preset includes:
https://user:pass@host)Note: secretlint does not catch arbitrary-format custom tokens (e.g. the Brightdata
API_TOKENfrom.mcp.json.bk— a 64-char hex string). For those, the.mcp.json*gitignore entry is the primary defense: if the file is gitignored, its content never reachesgit addand is never scanned or committed. Defense in depth.What this does NOT do
.mcp.json.bkblob. Per the earlier discussion, the secrets were rolled and purging would require force-pushing main (rewriting every commit from 99518f7 forward, breaking PR refactor: drop n8n meta + n8n-core dev deps (v2.47.2) #707 and every fork). Cost/benefit says leave it.lint-stagedor any test runner to the hook — this is strictly a secret scanner, kept minimal on purpose so contributors don't start bypassing it when it gets slow.Test plan
npm run typecheckpassesnpx secretlint "**/*" "**/.*"(the exact CI command) returns exit 0 on the clean repogit commitis blocked by the hook (verified manually)Dev tree impact
npm installadded ~79 packages (husky + secretlint + recommended preset + deps). That's acceptable overhead for a scanner that catches real bugs. Husky itself is 0-dep; the bulk is secretlint's rule catalog.Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en