Repository navigation
ci(security): gitleaks secret scan and OpenSSF Scorecard - #118
Conversation
gitleaks.yml scans a PR's commits over the base branch, and the whole history on a push to main, with the gitleaks CLI (8.30.1, tarball checked against a SHA-256 in the workflow; gitleaks-action needs a license key for organization repos). Findings are redacted in the log. The same version runs as a pre-commit hook on staged changes; CI's pre-commit step skips it. Renovate bumps the hook rev and the workflow pin in one PR. scorecard.yml runs ossf/scorecard-action 2.5.0 on pushes to main, weekly and on branch protection changes, publishes the result (api.scorecard.dev, the README badge) and uploads the SARIF to code scanning. 2.5.0 is the first release that pulls its image from ghcr.io; older ones fail since gcr.io was retired. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #118 +/- ##
=======================================
Coverage 91.12% 91.12%
=======================================
Files 212 212
Lines 26031 26031
Branches 4238 4238
=======================================
Hits 23721 23721
Misses 1643 1643
Partials 667 667 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🟡 Changes recommended
Merge-only secrets can escape scanning, and offline local CI does not skip the new download-dependent job.
2 open issues
What changed in this PR
Adds secret scanning and OpenSSF Scorecard reporting to the repository’s security checks.
Changes:
- Adds gitleaks CI scanning and a staged-change pre-commit hook.
- Adds Scorecard publication and a README badge.
- Groups gitleaks version updates and documents the integration.
| File | Description |
|---|---|
| README.md | Adds the Scorecard badge. |
| dokimi/CONTRIBUTING.md | Documents scanning and grouped updates. |
| .pre-commit-config.yaml | Adds the pinned gitleaks hook. |
| .github/workflows/scorecard.yml | Publishes scores and uploads security findings. |
| .github/workflows/gitleaks.yml | Installs gitleaks and scans commit history. |
| .github/workflows/ci.yml | Skips staged-change scanning in general CI. |
| .github/renovate.json5 | Groups gitleaks hook and workflow updates. |
Review effort: Balanced
| curl -fsSL --retry 3 -o "$RUNNER_TEMP/$tarball" \ | ||
| "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/$tarball" |
There was a problem hiding this comment.
Fixed in commit 9e6501b: added "gitleaks" to NEEDS_NETWORK in dokimi/scripts/ci_local.py. Regression test added: test_gitleaks_job_in_needs_network verifies the job is in the set. Now `make ci-local --offline` will skip the gitleaks job instead of failing.
There was a problem hiding this comment.
Already in place ("gitleaks" in NEEDS_NETWORK, ci_local.py; regression test test_gitleaks_job_in_needs_network). Re-checked on 7a86ddb: no change needed. Left open for the reviewer.
… support - Add -m flag to gitleaks log-opts to include commits resolving merge conflicts in PR secret scans, fixing undetected secrets added during merges - Add 'gitleaks' to NEEDS_NETWORK in ci_local.py so `make ci-local --offline` correctly skips gitleaks job instead of failing on missing binary - Add regression tests verifying both fixes Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 9e6501b. The workflow, pins and Renovate wiring hold up. One thing to fix before merge: the merge-diff fix covers only the PR scan, not the push-to-main scan. One nit on the new test.
What I ran, on a Linux review host at 9e6501b:
make ci-local: 21 passed, 0 failed, 4 skipped. The matrix legs on other Pythons were not run.tests/test_ci_local.pyagainst deliberately broken copies ofgitleaks.ymlandci_local.py.- A reproduction of the merge-resolution case with the local gitleaks binary, on a throwaway repo.
Checked, no issues
- Offline skip: with
"gitleaks"inNEEDS_NETWORK,ci-local --offlineskips both gitleaks steps with oneskip (needs network)line. Removing the entry failstest_gitleaks_job_in_needs_network. - Scorecard workflow: top-level
permissions: {}, no workflow env or defaults,id-token: writein the one job,ubuntu-latest, and only the allowed steps. - Docs: the
ci.ymlSKIP list, the CONTRIBUTING.md hook list and Renovate sentence, and the "about 280 commits" figure match the tree (origin/mainhas 281 commits). The Renovate regex matches theGITLEAKS_VERSION: 8.30.1line.
The -m flag enables merge diff tracking in git log. Applied only to PR scans in the previous fix, but merge commits added only during conflict resolution are skipped on push-to-main without it. Add -m to both branches, and strengthen test_gitleaks_workflow_includes_merge_diffs to verify both scan paths. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
6 open issues
RenovatemanagerFilePatternstypically expects regex strings (e.g.… · NewBASE_REFis read fromgithub.event.pull_request.base.ref, but this workflow also runs onpush… · New The comment says newer runs replace older ones, butcancel-in-progress: falseinstead queues… · New Mark gitleaks as requiring network in offline CI This header comment listsharden-runneras an allowed step, but the workflow does not include it.… · New These assertions depend on an exact bash formatting string (including quoting/spacing), which makes… · New
Review effort: Lite
- Thread 4314963262: Fix Renovate managerFilePatterns regex format. Changed slash-delimited patterns to plain regex strings (remove leading/trailing slashes) so Renovate correctly matches gitleaks.yml. - Thread 4314963393: Fix BASE_REF null on push events. Changed from github.event.pull_request.base.ref (only available in PR context) to github.base_ref (available in both PR and push contexts, empty on push). - Thread 4314963466: Fix cancel-in-progress behavior. Set to true unconditionally to ensure only the newest workflow run proceeds (not conditional on event_name), matching the intent to replace older runs. - Thread 4314963701: Improve test maintainability by replacing exact string assertions with semantic regex checks. Instead of checking exact formatting, verify presence of --log-opts and -m flags, plus range patterns. Survives harmless formatting changes in the shell script. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
There was a problem hiding this comment.
🟡 Changes recommended
Renovate matching, local scan execution, and merge-diff regression assertions need correction.
6 open issues
Renovate regex lacks delimiters and misses workflow version · New Local runner skips scans due to missing github.base_ref mapping · New Assertions do not verify -m for each scan branch · New The comment says newer runs replace older ones, butcancel-in-progress: falseinstead queues… Mark gitleaks as requiring network in offline CI This header comment listsharden-runneras an allowed step, but the workflow does not include it.…
3 resolved since last review
Review effort: Balanced
antonwolfy
left a comment
There was a problem hiding this comment.
Review at e91b0f1. The push-to-main scan now passes -m, so merge commits are scanned there too; its scope is a nit below. The regression test is still the open item, and it blocks.
What I ran, on a Linux review host at e91b0f1:
make ci-local: 20 passed, 0 failed, 5 skipped.gitleaks / Install gitleakspasses.gitleaks / Scan commitsis skipped.- Mutation runs of
tests/test_ci_local.pyagainst six broken copies ofgitleaks.yml.
I agree with @copilot-pull-request-reviewer on github.base_ref in ci_local.py and on the delimiter-free managerFilePatterns at renovate.json5:90. New evidence: make ci-local at this head prints gitleaks.yml:gitleaks / Scan commits: skip (unknown expression 'github.base_ref'), so the scan step no longer runs locally (the PR description says that row passes), and the other three managerFilePatterns entries in the file (lines 52, 67, 79) all use the /.../ form.
Still open from my earlier review:
dokimi/tests/test_ci_local.py:380:test_gitleaks_workflow_includes_merge_diffsstill passes when-mis removed from either branch, when--redactis removed, or when theifguard is flipped.pr_pattern.search(run_script) or '"' in run_scriptis always true, and the push regex ends in a lookahead that always matches. All six mutants (drop-mon the PR branch, drop-mon the push branch, remove--redact, flip the guard topush, reverse the range, delete theelse) give18 passed. Suggestion: run the step's script with a stubgitleaksforEVENT=pull_requestandEVENT=pushand assert the arguments of each run..github/workflows/gitleaks.yml:69(nit): push runs now get merge diffs, but the scope changed. In gitleaks' git source, a--log-optsvalue replaces its default--full-history --all, so--log-opts="-m"scans only what HEAD reaches, not every ref thefetch-depth: 0checkout brings, while the header (lines 6-8) says "the whole history". I read this from the gitleaks source and did not run it. Suggestion, as in the thread:range=(--log-opts="-m --full-history --all").
…novate regex The push-to-main scan passed --log-opts, which replaces gitleaks' default --all, so only HEAD's history was scanned; repeat --full-history --all there. ci-local lacked github.base_ref, so the scan step was skipped locally. Renovate needs the slash-delimited regex. The gitleaks merge-diff test now runs the step against a stub binary and checks the argv for both events. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
-m diffs a merge against each parent, so a line from main came back under the merge's SHA, and its .gitleaksignore fingerprint (keyed on the commit that added it) no longer matched. remerge shows only what a merge adds over the automatic merge, so conflict resolutions are still scanned. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
gitleaks logs git's error, scans 0 commits and exits 0 when its range does not resolve, so an empty or wrong BASE_REF or a shallow checkout passed. The test now expands the step's own env: mapping and pins fetch-depth: 0. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
|
|
Verified your two pushed commits on e3d7fd9 (no changes needed from my side):
Checks on e3d7fd9: Nothing open on my side for this review. 🤖 Generated with Claude Code |
antonwolfy
left a comment
There was a problem hiding this comment.
Review at e3d7fd9. The remerge change, the --full-history --all push scope and the unresolvable-base guard all do what the comments say, and the earlier points (merge diffs on both scans, --all, github.base_ref, the Renovate slashes) are fixed. Two things are worth fixing before merge, both inline: a test gap on the push scope and a doc claim about b/ paths.
What I ran, on a Linux review host at e3d7fd9:
make ci-local: 21 passed, 0 failed, 4 skipped.tests/test_ci_local.py: 18 passed.- Mutation runs of the new test against edited copies of
gitleaks.yml. - Reproductions of both inline findings with gitleaks 8.30.1 on a throwaway repo (fake AWS key).
Checked, no issues
- Merge resolutions: a key added only in a conflict resolution is found by the PR range and by the push scan. A key added in a feature commit and removed in the next one is still found, under its own commit.
- Guard:
git rev-parse --verify --quiet "origin/$BASE_REF^{commit}"with|| { ...; exit 1; }fails the step for an unfetched base. The test kills the mutants that drop the guard, make itexit 0, flip the event test, or dropfetch-depth: 0. - Renovate and docs: the
GITLEAKS_VERSIONregex matches the workflow line, the hook rev and the workflow pin are both 8.30.1, "about 280 commits" matches (origin/mainhas 281), and theci.ymlSKIP list matches the hook id. - Scorecard workflow: top-level
permissions: {},id-token: writein the one job,ubuntu-latest, only the allowed steps, every action pinned to a SHA.
The gitleaks test did not cover the push scan's --full-history --all: a commit on a ref HEAD does not reach was never scanned by the test. Add such a commit and assert it is reported on push and not on the PR, and that a PR line is reported once under its own commit. Document that a finding in a merge commit is named b/<path> in gitleaks output, so a .gitleaks.toml path entry must match it. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 03caf62. Both earlier threads are fixed: the new test fails without --all, and the header now documents the b/<path> form. Two test gaps remain, both inline, and I'd close them before merge.
What I ran, on a Linux review host at 03caf62:
make ci-local: 21 passed, 0 failed, 4 skipped. The skips are the same as before.tests/test_ci_local.pyagainst deliberately broken copies ofgitleaks.yml: 18 passed on the unmodified file.- A reproduction of the
b/path claim with gitleaks 8.30.1 on a throwaway repo.
Checked, no issues
- Push scope: dropping
--allfrom the push--log-optsnow fails withassert 'ONLY_ON_OTHER' in {...}. - PR leg:
first-parenton the PR leg fails withassert 2 == 1. - Header comment: a conflict-resolution finding is reported as
File: b/f.txt. A.gitleaks.tomlpath entry^(b/)?f\.txt$suppresses it, and^f\.txt$does not. - Test isolation: only git, bash and a stub gitleaks run, with a minimal environment, and no network.
The gitleaks scan test accepted a three-dot PR range and a first-parent push scan, which would re-report a PR's lines or flag main-only commits. Add a PR cut before main's newer commits and a count check on the push scan. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
antonwolfy
left a comment
There was a problem hiding this comment.
LGTM. Both test gaps from my last review are closed in d76df53, and nothing new turned up in the changes since 03caf62.
What I ran, on a Linux review host at d76df53:
make ci-local: 21 passed, 0 failed, 4 skipped. The skips are the same as before (docker, job conditions, semgrep).gitleaks / Install gitleaksandgitleaks / Scan commitspass.tests/test_ci_local.pyagainst deliberately broken copies ofgitleaks.yml: 18 passed on the unmodified file.
Checked, no issues
- Push scan, merge-diff mode: changing the push
--log-optsfrom--diff-merges=remergeto--diff-merges=first-parentnow fails the test (1 failed, 17 passed). - PR range: changing
origin/$BASE_REF..HEADtoorigin/$BASE_REF...HEADnow fails the test (1 failed, 17 passed). The newearlybranch makes the two forms differ. - Other mutants: dropping
--redact, and removing--diff-merges=remergefrom both legs, also fail the test. - Scope of the change: d76df53 touches only
dokimi/tests/test_ci_local.py. The test runs git, bash and a stub gitleaks with a minimal environment, with no network.
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com> # Conflicts: # dokimi/scripts/ci_local.py # dokimi/tests/test_ci_local.py



Summary
Two new workflows, a pre-commit hook, and a Renovate group that keeps the hook and the CI pin on the same gitleaks version.
ci.ymlchanges in one line: its pre-commit step skips the new hook, which only checks staged changes.gitleaks. This uses the CLI, not
gitleaks/gitleaks-action. The action needs aGITLEAKS_LICENSEsecret for repos owned by an organization, and it is no longer MIT-licensed. The workflow downloads the 8.30.1 tarball and checks it against a SHA-256 written in the workflow file (the release's own checksums file comes from the same place as the tarball, so it proves nothing extra). Then:--redactkeeps secret values out of the public log. To handle a false positive, addgitleaks:allowon the line, or add a.gitleaks.tomlallowlist, or put the fingerprint in.gitleaksignorefor a commit already on main. The workflow header says how. No allowlist ships with this PR, because the history scan below found nothing to allowlist.Scorecard. This is
ossf/scorecard-actionv2.5.0, the first release that pulls its image from ghcr.io. Older releases fail because gcr.io has been retired (Scorecard v5.6.0 release notes). The 7-day Renovate age policy would have held 2.5.0 back for one more day, but 2.4.4 no longer runs, so this PR pins 2.5.0. The workflow keeps the rules the results API enforces when results are published:permissions: {}, with no workflow env or defaultsid-token: writeandsecurity-events: writeonly in that jobubuntu-latestrunner (the API rejects other runners)Evidence
Full-history scan, gitleaks 8.30.1, default rules,
--redact:refs/pull/*/headof intel/dokimimainonlygitleaks dir)main..HEAD)To check that the rules actually fire, I scanned a canary file with a fake AWS key: the scan found 1 leak (
aws-access-token, value printed asREDACTED). The pre-commit hook also failed on the same canary when it was staged. The hook takes about 20 s the first time (pre-commit fetches Go and builds gitleaks) and about 0.2 s per commit after that.Scorecard v5.6.0, run locally on
main@ 6e695ce (ghcr.io/ossf/scorecard:v5.6.0): 7.2 / 10pytest>=7.4.0floor, not from the lock (9.1.1)pip install1/16 by hashWith the new workflows, local Scorecard on this branch (
--local) still gives Token-Permissions 10 and Dangerous-Workflow 10.Cheapest next steps, none of them in this PR:
main's protection, require status checks (for example the static and test jobs and the gitleaks scan), require CODEOWNERS review, dismiss stale approvals, require approval of the last push, and apply the rules to admins. These raise Branch-Protection. The Action's default token may not be able to read every protection setting, so the published score can be different from this local run.>=9.0.3inrequirements-test.txtand the dev extra. This clears one Vulnerabilities finding. Hash-pinning thepip installsteps would raise Pinned-Dependencies, but that is a larger change.Local checks on this branch:
make devthenmake ci-local: 0 failed, 21 passed, 4 skipped. The skips are docker, the job conditions, and semgrep. The newgitleaks / Install gitleaksandgitleaks / Scan commitsrows pass.zizmor --offline --min-severity medium .github/workflows(1.30.1): no findings. Online--persona pedanticon the two new files flagged only one low-severity issue, missing concurrency, and I fixed it.pre-commit run --all-files: the new gitleaks hook and the check-* and zizmor hooks pass.ruff-checkfails on 50 findings under.agent-eval/that are already on main; CI skips that hook.renovate-config-validator --strict: valid.renovate --platform=local --dry-run=extract: extractsgitleaks/gitleaksfrom both.pre-commit-config.yaml(v8.30.1 + SHA) andgitleaks.yml(8.30.1).Merge Danger
Door: two-way. Both workflows are new files, and reverting the PR removes them. One thing cannot be undone: once the first push-to-main run publishes, the score is public at api.scorecard.dev. Scorecard's weekly crawl already scores public repos, so the score was public anyway; publishing only switches it to the repo's own runs.
Blast Radius: CI
pre-commit install. A match blocks the commit, andSKIP=gitleaksbypasses it.GITLEAKS_SHA256is updated by hand. That is intended: there is no datasource for the tarball digest. This is documented in the workflow and inrenovate.json5.scorecardcategory. Until the first run on main publishes, the README badge shows whatever the weekly crawl last recorded, or no score at all.Checklist
pytestpasses (no package code changed; the suite passes inmake ci-local)ruff,mypy, and schema checks pass.github/.licenserc.yaml)🤖 Generated with Claude Code