Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions changelog.d/2026-08-04-pin-assertion-property-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### Changed

- CI security-contract tests now assert action-pin *properties* instead of
exact pin fixtures: the Gitleaks, CodeQL init/analyze, and upload-artifact
assertions require a real `uses:` line pinned to a full 40-hex commit SHA
with a version comment, but no longer freeze WHICH SHA or major version.
The frozen fixtures broke every Dependabot actions-group PR by construction
(#9, #29, #67) while adding no enforcement beyond the repo-wide
full-SHA pin test and CODEOWNERS review of workflow diffs — Dependabot
cannot update test fixtures that mirror the workflow it bumps. The
replacement regexes are anchored to `uses:` lines (a comment or string
mentioning an action no longer satisfies them — a strictness *increase*
over the old substring assertions, per Codex cross-family review) and
accept dotted version comments (`# v3.0.0`) as Dependabot writes them.
Test infrastructure only — no version bump.
11 changes: 10 additions & 1 deletion tests/test_ci_security_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,18 @@ def test_secret_scan_combines_local_and_full_history_scanners(self) -> None:
"python scripts/secret_scan.py",
"fetch-depth: 0",
"name: Gitleaks",
"gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7",
):
self.assertIn(token, text)
# Property, not fixture: the Gitleaks step must exist as a real
# `uses:` line pinned to a full commit SHA with a version comment.
# Freezing the exact SHA here only mirrored the workflow and broke
# every Dependabot bump; WHICH SHA runs is governed by the workflow
# diff review (CODEOWNERS) and the repo-wide pin test above.
self.assertRegex(
text,
r"(?m)^\s*(?:-\s*)?uses:\s*"
r"gitleaks/gitleaks-action@[0-9a-f]{40}\s+# v\d+(?:\.\d+)*\s*$",
Comment on lines +138 to +139

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict the pin regex whitespace to one line

When an inline version comment is removed or moved, Python's \s+ can consume the newline, so a pinned uses: line followed by a separate # v4 comment still satisfies this assertion; ^\s* similarly allows matching an action-looking line inside a YAML block scalar. The same construction is used for all four new assertions, so they do not actually guarantee a real uses: line with an inline version comment as documented. Use horizontal whitespace such as [ \t] around the tokens so the multiline anchors remain line-local.

Useful? React with 👍 / 👎.

)

def test_release_assets_use_download_stable_filenames(self) -> None:
text = (WORKFLOWS / "release.yml").read_text(encoding="utf-8")
Expand Down
17 changes: 14 additions & 3 deletions tests/test_provider_plugin_retirement.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,20 @@ def test_public_security_workflows_are_present(self) -> None:
secret_scan = (
ROOT / ".github" / "workflows" / "secret-scan.yml"
).read_text(encoding="utf-8")
# Property, not fixture: each required action must appear as a real
# `uses:` line pinned to a full commit SHA with a version comment.
# The exact SHA/major is deliberately NOT frozen here — freezing it
# broke every Dependabot bump while adding no enforcement beyond the
# repo-wide pin test and CODEOWNERS review of workflow diffs.
self.assertRegex(
codeql, r"github/codeql-action/init@[0-9a-f]{40} # v4"
codeql,
r"(?m)^\s*(?:-\s*)?uses:\s*"
r"github/codeql-action/init@[0-9a-f]{40}\s+# v\d+(?:\.\d+)*\s*$",
)
self.assertRegex(
codeql, r"github/codeql-action/analyze@[0-9a-f]{40} # v4"
codeql,
r"(?m)^\s*(?:-\s*)?uses:\s*"
r"github/codeql-action/analyze@[0-9a-f]{40}\s+# v\d+(?:\.\d+)*\s*$",
)
self.assertRegex(codeql, r"(?m)^ actions: read$")
self.assertIn("id: analyze", codeql)
Expand All @@ -156,7 +165,9 @@ def test_public_security_workflows_are_present(self) -> None:
)
self.assertIn("if: github.event.repository.private", codeql)
self.assertRegex(
codeql, r"actions/upload-artifact@[0-9a-f]{40} # v4"
codeql,
r"(?m)^\s*(?:-\s*)?uses:\s*"
r"actions/upload-artifact@[0-9a-f]{40}\s+# v\d+(?:\.\d+)*\s*$",
)
self.assertIn("path: ${{ steps.analyze.outputs.sarif-output }}", codeql)
self.assertIn("scripts/secret_scan.py", secret_scan)
Expand Down
Loading