Uh oh!
There was an error while loading. Please reload this page.
ci(gitleaks): pin the version, verify the checksum and drop to least privilege - #1455
Conversation
…privilege The reusable workflow resolved the latest gitleaks release at run time and piped the archive straight into tar, so CI executed an unverified binary whose version could change without a commit. It also ran with whatever permissions the caller happened to grant. - Pin gitleaks 8.30.1 and its official linux_x64 SHA-256 side by side, and verify the archive with sha256sum --check before extraction. - Pin actions/checkout to the immutable SHA for v7.0.1, version in a same-line comment, with persist-credentials: false. - Add explicit contents: read at workflow and job level, timeout-minutes, and set -euo pipefail in every run block. - Remove the temporary directory under if: always(). - Replace the undocumented detect alias with the supported dir and git commands. - Pass values into shell through env instead of interpolating expressions. - Fall back gracefully when a calling repository has no .gitleaks.toml. - Add gitleaks-update.yml, a weekly updater that pairs each version bump with its official checksum, re-verifies the download and opens a PR without merging. - Document the controls and the recovery path in docs/secret-scanning.md.
🚫 This PR description is missing required template content. Missing required section(s): Global DoD checklist Please update the PR body using one of the repository PR templates:
Empty placeholders, unchecked checklist boxes, and stub issue references do not count. |
⏱️ Aging and SLA annotation
Maintained by project-meta-sync workflow. |
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe change hardens reusable Gitleaks scanning with pinned, checksum-verified binaries, read-only permissions, immutable checkout, supported scan commands, timeouts, and cleanup. It adds a scheduled updater that verifies stable releases and opens pull requests. Documentation covers usage and recovery procedures. ChangesGitleaks workflow hardening
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/gitleaks-update.yml:
- Around line 100-129: Make the “Open the update pull request” step idempotent
by checking for an existing open pull request whose head branch is
deps/gitleaks-${NEW_VERSION} before git checkout or push. If found, exit
successfully (or update the existing pull request) without attempting to
recreate the branch; otherwise preserve the current branch creation, commit,
push, and PR flow.
- Around line 26-31: Update the create-github-app-token step identified by id
token to explicitly set permission-contents, permission-pull-requests, and
permission-workflows to write, limiting the generated App token to the
permissions required by this job.
In `@CHANGELOG.md`:
- Line 96: Add the required pull request link and issue link to the Unreleased
changelog entry describing Gitleaks hardening, preserving the existing entry
text and following the repository’s established changelog link format.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 1e4d86de-7437-46bd-883b-dd71607f3f6f
📒 Files selected for processing (5)
.github/workflows/gitleaks-reusable.yml.github/workflows/gitleaks-update.yml.github/workflows/gitleaks.ymlCHANGELOG.mddocs/secret-scanning.md
📜 Review details
⏰ Context from checks skipped due to timeout. (7)
- GitHub Check: lint-and-links
- GitHub Check: Testing
- GitHub Check: coderabbit-gate
- GitHub Check: Mergify Merge Protections
- GitHub Check: Analyze (python)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Summary
⚠️ CI failures not shown inline (2)
GitHub Actions: Validate PR Template / 0_validate-pr-template.txt: ci(gitleaks): pin the version, verify the checksum and drop to least privilege
Conclusion: failure
##[group]Run actions/github-script@v7
with:
script: const { validatePullRequestBody } = require('./scripts/validation/template-helpers.cjs');
const marker = '<!-- template-enforcement -->';
const pr = context.payload.pull_request;
const author = pr.user?.login || '';
const isDependabot = author === 'dependabot[bot]' || author === 'app/dependabot';
const isImgbot = author === 'imgbot[bot]' || author === 'app/imgbot';
if (isDependabot || isImgbot) {
core.info(`Skipping PR template validation for bot author ${author}.`);
return;
}
const validation = validatePullRequestBody(pr.body || '', pr.labels || [], pr.head?.ref || '');
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
per_page: 100
});
const previous = comments.find((comment) =>
comment.user?.type === 'Bot' && comment.body?.includes(marker)
);
if (validation.missing.length === 0) {
if (previous) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: previous.id,
body: `${marker}\n✅ Template check passed after update. Thanks for fixing the PR description.`
});
}
return;
}
const message = [
marker,
'🚫 This PR description is missing required template content.',
'',
`Missing required section(s): ${validation.missing.join(', ')}`,
'',
'Please update the PR body using one of the repository PR templates:',
'- https://github.com/lightspeedwp/.github/blob/develop/.github/pull_request_template.md',
'- https://github.com/lightspeedwp/.github/tree/develop/.github/PULL_REQUEST_TEMPLATE',
'',
'Empty placeholders, unchecked checklist boxes, and stub issue references do not count.'
].join('\n');
if (previous) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: previous.id,
body: message
});
} else {
await github.rest.issues....
GitHub Actions: Validate PR Template / validate-pr-template: ci(gitleaks): pin the version, verify the checksum and drop to least privilege
Conclusion: failure
##[group]Run actions/github-script@v7
with:
script: const { validatePullRequestBody } = require('./scripts/validation/template-helpers.cjs');
const marker = '<!-- template-enforcement -->';
const pr = context.payload.pull_request;
const author = pr.user?.login || '';
const isDependabot = author === 'dependabot[bot]' || author === 'app/dependabot';
const isImgbot = author === 'imgbot[bot]' || author === 'app/imgbot';
if (isDependabot || isImgbot) {
core.info(`Skipping PR template validation for bot author ${author}.`);
return;
}
const validation = validatePullRequestBody(pr.body || '', pr.labels || [], pr.head?.ref || '');
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
per_page: 100
});
const previous = comments.find((comment) =>
comment.user?.type === 'Bot' && comment.body?.includes(marker)
);
if (validation.missing.length === 0) {
if (previous) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: previous.id,
body: `${marker}\n✅ Template check passed after update. Thanks for fixing the PR description.`
});
}
return;
}
const message = [
marker,
'🚫 This PR description is missing required template content.',
'',
`Missing required section(s): ${validation.missing.join(', ')}`,
'',
'Please update the PR body using one of the repository PR templates:',
'- https://github.com/lightspeedwp/.github/blob/develop/.github/pull_request_template.md',
'- https://github.com/lightspeedwp/.github/tree/develop/.github/PULL_REQUEST_TEMPLATE',
'',
'Empty placeholders, unchecked checklist boxes, and stub issue references do not count.'
].join('\n');
if (previous) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: previous.id,
body: message
});
} else {
await github.rest.issues....
🧰 Additional context used
📓 Path-based instructions (6)
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
**/*: Do not place reusable AI assets under.github/; store them in the appropriate top-level portable asset folder.
Portable JSON schemas belong in the root.schemas/directory; portable agents belong inagents/; permanent human documentation belongs indocs/; temporary scratch files belong in.github/tmp/and must be cleaned up.
Do not commitnode_modules/,build/, or other generated artefacts.
**/*: Use UK English and optimise solutions for clarity, scalability, maintainability, and profitable outcomes.
Prefer minimal, modular solutions; justify heavier tools based on return on investment and maintenance cost.
Files:
docs/secret-scanning.mdCHANGELOG.md
**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Use UK English throughout documentation and Markdown content, including spellings such as
optimise,organisation,colour, andbehaviour.
**/*.md: Documentation and AI infrastructure files must follow their applicable documented standards, including required structure, frontmatter, quality gates, and validation.
Run Markdown linting withnpm run lint:mdand validate frontmatter withnpm run validate:frontmatterwhere applicable.
Files:
docs/secret-scanning.mdCHANGELOG.md
**/*.{md,yml,yaml,json}
📄 CodeRabbit inference engine (CLAUDE.md)
Do not use a
referencesfrontmatter field; use inline links or footer sections instead.
Files:
docs/secret-scanning.mdCHANGELOG.md
**/docs/**/*.md
⚙️ CodeRabbit configuration file
**/docs/**/*.md: Review documentation files:
- Ensure markdown is linted and formatted per project style guides.
- Flag illogical folder structures, file naming, or misplaced content.
- Confirm documentation is up to date, accurate, and cross-referenced.
- Ensure accessibility (heading hierarchy, alt text for images, UK English).
Files:
docs/secret-scanning.md
**/.github/workflows/*.yml
⚙️ CodeRabbit configuration file
**/.github/workflows/*.yml: Review GitHub Actions workflows for this governance repo:
- Security: check for least-privilege permissions (use
permissions:at job level, default to read-only).- Secret handling: ensure secrets are passed via env vars, not interpolated directly into run: steps to prevent injection.
- Action pinning: prefer SHA-pinned actions over mutable tags (e.g.
actions/checkout@v4is acceptable; SHA pins are better).- No
pull_request_targetwith untrusted code execution unless explicitly justified.- Avoid storing sensitive outputs as unmasked step outputs.
- Check for reusable workflow patterns and matrix strategies where appropriate.
- Validate
on:triggers: ensure branch/path filters are present to avoid unnecessary runs.- Confirm workflows are documented, DRY, and maintainable.
- Ensure agent-triggered workflows use
workflow_dispatchwith defined inputs.
Files:
.github/workflows/gitleaks.yml.github/workflows/gitleaks-update.yml.github/workflows/gitleaks-reusable.yml
CHANGELOG.md
⚙️ CodeRabbit configuration file
CHANGELOG.md: Review CHANGELOG.md:
- Confirm entries follow Keep a Changelog 1.1.0 format.
- Each entry under [Unreleased] must include a PR link and issue link.
- Verify entries use the correct section headings (Added, Changed, Fixed, Deprecated, Removed, Security, Documentation, Performance).
- Check UK English spelling throughout.
Files:
CHANGELOG.md
🪛 LanguageTool
docs/secret-scanning.md
[grammar] ~34-~34: A determiner may be missing.
Context: ...ence | persist-credentials: false | | Least privilege | `permissions: contents: rea...
(THE_SUPERLATIVE)
[style] ~45-~45: Would you like to use the Oxford spelling “organization”? The spelling ‘organisation’ is also correct.
Context: ... public, so private repositories in the organisation can call the reusable workflow with no ...
(OXFORD_SPELLING_Z_NOT_S)
[uncategorized] ~62-~62: “Neither…nor” is a correlative construction, so the comma here may be unnecessary.
Context: ...that is neither a draft nor a prerelease, retrieves the official checksums.txt,...
(NEITHER_NOR_SUPERFLUOUS_COMMA)
🪛 zizmor (1.28.0)
.github/workflows/gitleaks-update.yml
[warning] 33-36: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 28-28: dangerous use of GitHub App tokens (github-app): app token inherits blanket installation permissions
(github-app)
🔇 Additional comments (5)
.github/workflows/gitleaks-reusable.yml (1)
2-68: LGTM!.github/workflows/gitleaks.yml (1)
2-26: LGTM!docs/secret-scanning.md (2)
16-74: LGTM!
1-14: 📐 Maintainability & Code QualityMark the documentation checks as complete.
docs/secret-scanning.mdhas the required frontmatter fields, so this check can be cleared..github/workflows/gitleaks-update.yml (1)
1-24: LGTM!Also applies to: 38-99
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
🔍 Reviewer Summary for PR #1455CI Status: ❌ Recommendations
|
The scan job was hard-coded to ubuntu-latest, so a caller wanting the scan on its own unprivileged self-hosted group could not have it. Add an optional 'runner' input carrying a JSON runner selection, defaulting to '"ubuntu-latest"' so every existing caller is unchanged. workflow_call inputs are limited to boolean, number and string, so the value is a JSON string decoded with fromJSON. That accepts both a single label and a label array.
…nk the changelog Addresses the three unresolved review findings on PR #1455. Token scoping: request only contents, pull-requests and workflows write, scoped to this repository through the repositories input. The workflows scope is not speculative — the update diff edits .github/workflows/gitleaks-reusable.yml and a push touching a workflow file is rejected without it, which is also why GITHUB_TOKEN cannot perform this job. Idempotency: repeated scheduled runs previously failed once the deps branch existed. The job now converges from all four states — no branch and no PR, an open PR for the version, a branch with no PR, and a stale branch with different content — using checkout -B, a no-op guard when the tree already matches the target pin, and force-with-lease so a branch advanced by someone else is never discarded silently. The pull request is opened only when none is already open. Changelog: added the PR and issue links required by the path instructions.
Tick the box to add this pull request to the merge queue (same as
|
Uh oh!
There was an error while loading. Please reload this page.
Linked issues
Closes#1454
Build/CI change
What: Hardens the organisation-wide gitleaks implementation and adds a scheduled updater.
Why: The reusable workflow resolved the latest gitleaks release at run time and piped the archive
directly into
tar. CI therefore executed a binary that was never integrity-checked, at a versionthat could change without any commit to this repository. It also inherited whatever permissions the
calling workflow granted, had no timeout, and left the extracted binary in the workspace.
Changes:
8.30.1and its officiallinux_x64SHA-256 on adjacent lines; verify withsha256sum --checkbefore extraction.actions/checkoutto3d3c42e5aac5ba805825da76410c181273ba90b1(v7.0.1), version in asame-line comment, with
persist-credentials: false.permissions: contents: readat workflow and job level;timeout-minutes: 15;set -euo pipefailin every run block; temp directory removed underif: always().gitleaks detectalias with the supportedgitleaks dirandgitleaks gitcommands.envrather than interpolating${{ }}into script bodies..gitleaks.toml.runnerinput so a caller can run the scan on its own unprivileged self-hostedgroup. Defaults to
'"ubuntu-latest"', so every existing caller is unchanged.gitleaks-update.yml: weekly, excludes drafts and prereleases, retrieves the officialchecksum manifest, updates version and checksum together, re-verifies the download, opens a PR,
never merges.
Still using the open-source CLI directly, so no paid gitleaks organisation licence is required.
Baseline & Target
Before: version resolved from
api.github.comon every run; no checksum; no explicitpermissions; no timeout;
actions/checkout@v7floating tag; extracted binary left in the workspace;scan runner not selectable.
After: fixed version and verified checksum;
contents: read; 15-minute timeout; immutable actionSHA; workspace cleaned; runner selectable by the caller. Scan runtime is unchanged — the download was
already happening, it is now verified.
Rollback
Revert this PR. The pinned values live on two adjacent lines in
gitleaks-reusable.yml, so a versioncan also be rolled back on its own by restoring the previous version and checksum pair. Recovery
paths are documented in
docs/secret-scanning.md.Notes
Secrets and permissions. The scan workflows request
contents: readonly and reference nosecrets. The updater mints a short-lived App installation token via
actions/create-github-app-tokenpinned to
bcd2ba49218906704ab6c1aa796996da409d3eb1(v3.2.0), scoped withrepositories: .githuband exactly three write scopes —
contents(push the branch),pull-requests(open the PR) andworkflows. Theworkflowsscope is required, not speculative: the update diff edits.github/workflows/gitleaks-reusable.yml, and a push touching a workflow file is rejected withoutit. That is also why
GITHUB_TOKENcannot perform this job.Prerequisite for the updater only.
vars.DEP_MERGE_APP_IDandsecrets.DEP_MERGE_APP_PRIVATE_KEYare not yet present on this repository, so the weekly updater will not run until they are added. The
scan path — the part every other repository consumes — has no such dependency and works today.
Live version verification, 2 August 2026.
actions/checkoutv7.0.1 (published 2026-07-20),actions/create-github-app-tokenv3.2.0 (2026-05-12), gitleaks v8.30.1 (2026-03-21, latestnon-prerelease). Immutable references recorded above.
Test evidence
13 of 13 local checks passed against the real 8.30.1 binary:
.gitleaks.tomlloads; clean working-tree scangitscanlatestresolution in the scan pathThe seeded credential was randomly generated in a temporary directory outside the repository and was
never written to git.
Idempotency of the updater was reasoned through all four reachable states — no branch and no PR, an
open PR for the version, a branch with no PR, and a stale branch with different content — and
implemented with
checkout -B, a no-op guard when the tree already matches the target pin,--force-with-lease, and PR creation only when none is open.Required vs optional checks
Branch protection on
developrequires exactly one status check,validate-pr-template, and noapproving reviews:
LintingandValidationare not required. Both fail on pre-existing repository contentunrelated to this change (SCSS, Markdown,
slacks-summary.js, and workflow issues inconfig-summary.yml,ui-standardisation.yml,validate-changelog.yml, unresolvedNGINX_SITE_UPPERreferences and anick-fields/retry@v3reference). This PR touches none of thosefiles — its diff is limited to three gitleaks workflows,
CHANGELOG.mdanddocs/secret-scanning.md.Changelog
Changed
with a weekly updater that pairs each version bump with its official checksum.
Checklist (Global DoD / PR)
docs/secret-scanning.mdadded, CHANGELOG updated with PR and issue linksenv, not interpolated into script bodies--redactprevents findings printing secret materialcontents: readand hold no token. The updater's token is scoped to this repository with three justified write scopes.ec46856developcontent