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
10 changes: 5 additions & 5 deletions .github/workflows/osv-scanner-pr.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Keeps the upstream OSV base/head diff check available on every PR. The
# central Security Scan workflow owns the blocking OSV result, finding logs,
# and SARIF upload so this supplemental check does not duplicate installation
# API calls or fail an otherwise clean PR when GitHub's upload quota is spent.
# Keeps this repository's historical OSV check context available for classic
# branch protection. Organization PRs receive OSV from security-scan.yml only;
# this supplemental workflow is intentionally absent from the organization
# required-workflow ruleset.
name: OSV-Scanner PR

on:
Expand Down Expand Up @@ -121,4 +121,4 @@ jobs:
upload-sarif: false
# Merge gating is done by central security-scan.yml with
# --fail-on-vuln=true after printing package, version, OSV ID and aliases.
fail-on-vuln: false
fail-on-vuln: false
10 changes: 5 additions & 5 deletions .github/workflows/scorecard-pr.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Runs a supplemental OpenSSF Scorecard analysis on every PR and preserves its
# filtered SARIF as an artifact. The central Security Scan workflow owns the
# PR code-scanning upload so this workflow does not duplicate installation API
# calls or fail a clean PR when GitHub's upload quota is spent.
# Keeps this repository's historical Scorecard context available for classic
# branch protection. Organization PRs receive Scorecard from security-scan.yml
# only; this supplemental workflow is intentionally absent from the
# organization required-workflow ruleset.
#
# NOTE: Scorecard reports repository-posture findings (branch protection, token
# permissions, dependency pinning, ...) that are unrelated to the PR diff. The
Expand Down Expand Up @@ -158,4 +158,4 @@ jobs:
with:
name: scorecard-pr-sarif-${{ github.run_id }}-${{ github.run_attempt }}
path: results.sarif
retention-days: 7
retention-days: 7
4 changes: 4 additions & 0 deletions .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
# trivy-fs HARD repo-wide — fails on FIXABLE MEDIUM/HIGH/CRITICAL findings
# scorecard SOFT repo posture — uploaded for visibility, never blocks
#
# This is the sole organization-required owner for OSV and Scorecard PR work.
# The standalone workflows remain local to this repository because its classic
# branch protection still requires their historical check contexts.
#
# Gating is by the JOB result (a failed job fails this required workflow ->
# merge blocked), NOT by the code_scanning ruleset rule. The code_scanning rule
# stays CodeQL-only on purpose: requiring multiple code-scanning TOOLS there is
Expand Down
2 changes: 0 additions & 2 deletions scripts/ci/audit_central_required_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@
".github/workflows/security-scan.yml",
".github/workflows/strix.yml",
".github/workflows/sast-semgrep.yml",
".github/workflows/osv-scanner-pr.yml",
".github/workflows/scorecard-pr.yml",
)
STACKED_WORKFLOW_PATH = ".github/workflows/opencode-review.yml"

Expand Down
54 changes: 33 additions & 21 deletions tests/test_central_required_workflow_ruleset_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ def ruleset_payload() -> dict:
"security-scan.yml",
"strix.yml",
"sast-semgrep.yml",
"osv-scanner-pr.yml",
"scorecard-pr.yml",
)
return {
"id": 18156473,
Expand Down Expand Up @@ -115,7 +113,7 @@ def test_expected_central_ruleset_passes(monkeypatch, capsys) -> None:

assert audit.main([]) == 0
assert (
"PASS: ruleset 18156473 enforces 9 central required workflows"
"PASS: ruleset 18156473 enforces 7 central required workflows"
in capsys.readouterr().out
)

Expand Down Expand Up @@ -254,32 +252,42 @@ def test_missing_noema_workflow_reports_exact_drift() -> None:
assert "missing central required workflow .github/workflows/noema-review.yml" in errors


def test_missing_osv_scanner_workflow_reports_exact_drift() -> None:
def test_readded_osv_scanner_workflow_reports_duplicate_scan() -> None:
payload = ruleset_payload()
workflow_rule = next(rule for rule in payload["rules"] if rule["type"] == "workflows")
workflow_rule["parameters"]["workflows"] = [
workflow
for workflow in workflow_rule["parameters"]["workflows"]
if workflow["path"] != ".github/workflows/osv-scanner-pr.yml"
]
workflow_rule["parameters"]["workflows"].append(
{
"repository_id": 1274066402,
"path": ".github/workflows/osv-scanner-pr.yml",
"ref": "refs/heads/main",
}
)

errors = audit.audit_ruleset(payload)

assert "missing central required workflow .github/workflows/osv-scanner-pr.yml" in errors
assert (
"unexpected workflow present in required set: .github/workflows/osv-scanner-pr.yml"
in errors
)


def test_missing_scorecard_workflow_reports_exact_drift() -> None:
def test_readded_scorecard_workflow_reports_duplicate_scan() -> None:
payload = ruleset_payload()
workflow_rule = next(rule for rule in payload["rules"] if rule["type"] == "workflows")
workflow_rule["parameters"]["workflows"] = [
workflow
for workflow in workflow_rule["parameters"]["workflows"]
if workflow["path"] != ".github/workflows/scorecard-pr.yml"
]
workflow_rule["parameters"]["workflows"].append(
{
"repository_id": 1274066402,
"path": ".github/workflows/scorecard-pr.yml",
"ref": "refs/heads/main",
}
)

errors = audit.audit_ruleset(payload)

assert "missing central required workflow .github/workflows/scorecard-pr.yml" in errors
assert (
"unexpected workflow present in required set: .github/workflows/scorecard-pr.yml"
in errors
)


def test_readded_codeql_workflow_alongside_full_set_reports_unexpected_entry() -> None:
Expand Down Expand Up @@ -383,8 +391,6 @@ def test_audit_reports_all_structural_and_protection_drift() -> None:
"missing central required workflow .github/workflows/security-scan.yml",
"missing central required workflow .github/workflows/strix.yml",
"missing central required workflow .github/workflows/sast-semgrep.yml",
"missing central required workflow .github/workflows/osv-scanner-pr.yml",
"missing central required workflow .github/workflows/scorecard-pr.yml",
"expected one pull_request rule, found 0",
"default-branch deletion protection is missing",
"default-branch non-fast-forward protection is missing",
Expand All @@ -397,7 +403,13 @@ def test_audit_reports_malformed_duplicate_workflows_and_weak_review_parameters(
workflows = workflow_rule["parameters"]["workflows"]
workflows.insert(0, "malformed")
workflows.insert(1, {"path": 42})
workflows.append(deepcopy(workflows[-1]))
security_scan = next(
workflow
for workflow in workflows
if isinstance(workflow, dict)
and workflow.get("path") == ".github/workflows/security-scan.yml"
)
workflows.append(deepcopy(security_scan))
review_rule = next(rule for rule in payload["rules"] if rule["type"] == "pull_request")
review_rule["parameters"] = {
"required_approving_review_count": 0,
Expand All @@ -411,7 +423,7 @@ def test_audit_reports_malformed_duplicate_workflows_and_weak_review_parameters(

assert "central required workflow entry 0 is malformed" in errors
assert "central required workflow entry 1 is malformed" in errors
assert "central required workflow .github/workflows/scorecard-pr.yml is configured 2 times" in errors
assert "central required workflow .github/workflows/security-scan.yml is configured 2 times" in errors
assert "exactly two approving reviews are not required" in errors
assert "stale-review dismissal on push is disabled" in errors
assert "last-push approval protection is disabled" in errors
Expand Down
28 changes: 23 additions & 5 deletions tests/test_code_scanning_required_workflow_contract.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
"""Regression contract for organization-required code-scanning workflows."""
"""Regression contract for the consolidated organization security scan."""

from pathlib import Path

from scripts.ci import audit_central_required_workflows as audit


_REQUIRED_CODE_SCANNING_WORKFLOW_PATHS = {
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
_SUPPLEMENTAL_CODE_SCANNING_WORKFLOW_PATHS = {
".github/workflows/scorecard-pr.yml",
".github/workflows/osv-scanner-pr.yml",
}


def test_ruleset_audit_requires_every_code_scanning_workflow() -> None:
"""The central audit must fail if either live code-scanning requirement disappears."""
assert _REQUIRED_CODE_SCANNING_WORKFLOW_PATHS <= set(audit.REQUIRED_WORKFLOW_PATHS)
def test_ruleset_requires_only_the_consolidated_security_scan() -> None:
"""Do not inject duplicate OSV and Scorecard runs into every repository PR."""
required_paths = set(audit.REQUIRED_WORKFLOW_PATHS)

assert ".github/workflows/security-scan.yml" in required_paths
assert _SUPPLEMENTAL_CODE_SCANNING_WORKFLOW_PATHS.isdisjoint(required_paths)


def test_consolidated_security_scan_preserves_osv_and_scorecard_evidence() -> None:
"""The sole required owner must retain both scanners and their SARIF uploads."""
workflow = (
REPOSITORY_ROOT / ".github/workflows/security-scan.yml"
).read_text(encoding="utf-8")

assert " osv-scan:" in workflow
assert " scorecard:" in workflow
assert "Upload OSV SARIF to code scanning" in workflow
assert "Upload Scorecard SARIF to code scanning" in workflow


def test_ruleset_audit_deliberately_excludes_codeql_pr() -> None:
Expand Down
Loading