feat: OpenSSF Scorecard and dependency review workflows#33
Conversation
scorecard.yml: - Runs weekly (Monday 06:00 UTC), on push to main, and on workflow_dispatch - Publishes results to the OpenSSF database (public repo, OIDC-signed) - Uploads SARIF to GitHub Security tab and as a retained artifact - Target score >= 8.0/10 per issue #29 acceptance criteria dependency-review.yml: - Triggers on PRs touching any supported lockfile format - Reusable via workflow_call with fail-on-severity and deny-licenses inputs - Posts a summary comment on the PR via comment-summary-in-pr: always - Default deny list: GPL-2.0, GPL-3.0, AGPL-3.0 Also brings .pre-commit-config.yaml forward from issue-25 branch so local hooks work while that PR is pending merge. All action references SHA-pinned per #25 authoring standards. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds organization-governance workflows to improve supply-chain security posture for this repository and to provide a reusable dependency gate for consuming repositories.
Changes:
- Introduces an OpenSSF Scorecard workflow that runs on schedule/push and uploads SARIF results to GitHub Security.
- Adds a reusable Dependency Review workflow (also runnable on PRs) with severity/license policy inputs.
- Adds a local
.pre-commit-config.yamlto runactionlintandzizmorconsistently.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
.pre-commit-config.yaml |
Adds pre-commit hooks for action/workflow linting and security checks. |
.github/workflows/scorecard.yml |
Runs OpenSSF Scorecard and uploads SARIF/artifacts for Security tab visibility. |
.github/workflows/dependency-review.yml |
Adds dependency review gate (PR + reusable workflow) with policy inputs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Code ReviewStrengths
Issues1. uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
gh api repos/actions/upload-artifact/commits/v4 --jq '.sha'2. No - uses: github/codeql-action/upload-sarif@...
with:
sarif_file: scorecard-results.sarifIf the Scorecard action exits non-zero (rate-limited, transient failure), the SARIF upload will be skipped. Adding 3. Duplicate defaults for
4. Blocking only on 5. The list is comprehensive but misses a few patterns for completeness as a general-purpose org template:
Not critical for Sparkgeo's current stack — flagging for awareness. 6. As noted in the PR description, this file is identical to the one in PR #32. Whichever PR merges second will need a no-op conflict resolution. Merge PR #32 first to avoid the conflict landing here. Priority before merging
Items 3 and 4 are policy/cleanup calls; items 5 and 6 are low priority. |
scorecard.yml: - Remove redundant workflow-level permissions: read-all; job-level block is definitive (job permissions override, not merge, workflow-level) - Add if: always() to SARIF upload so results are captured even on transient Scorecard failures dependency-review.yml: - Raise fail-on-severity default from critical to high — CVSS 8.x findings should not pass silently in a security-focused org library - Add pdm.lock, mix.lock, Package.resolved to lockfile path filter - Expose comment-summary-in-pr as a workflow_call input (default: on-failure) so callers can control PR comment verbosity; avoids requiring pull-requests: write for callers that don't want comments Note: upload-artifact v7.0.1 SHA (043fb46d) is confirmed correct — v7.0.1 is the current latest release of actions/upload-artifact. The || fallback pattern on inputs.* is intentional and necessary: inputs.default is only applied on workflow_call; pull_request-triggered runs receive an empty string from the inputs context, requiring the fallback to supply the default value. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scorecard.yml / dependency-review.yml: - Add workflow-level description comments (line 2) - Add job-level description comments - Capitalise job display names for consistency with Actions Quality Gate README.md: - Add OpenSSF Scorecard and Dependency Review to workflows table - Add usage examples for both workflows including workflow_call inputs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ci.yml calls workflow-lint.yml via workflow_call and runs the storage-optimizer composite action on every push and PR. Serves as both a test harness and a reference implementation for consuming repos. Also adds ci.yml to README workflows table. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ci.yml Each workflow capability is now a standalone composite action following the same pattern as storage-optimizer and terramate-opentofu-setup: - .github/actions/actionlint/ — actionlint via reviewdog - .github/actions/zizmor/ — zizmor static security analysis - .github/actions/scorecard/ — OpenSSF Scorecard + SARIF upload - .github/actions/dependency-review/ — dependency-review-action with inputs Deleted reusable workflows: workflow-lint.yml, scorecard.yml, dependency-review.yml. All logic moved into composite actions above. ci.yml updated to one job per composite action, all running in parallel. scorecard skips pull_request; dependency-review runs only on pull_request. schedule trigger added to ci.yml (weekly Monday 06:00 UTC) for scorecard. README rewritten to reflect composite-action-first structure with per-action usage examples and required permissions documented. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rename .github/actions/actionlint/ → .github/actions/github-actionlint/ to make it explicit the action is specific to GitHub Actions workflows. Update ci.yml and README references accordingly. CONTRIBUTING.md: - Update description from "workflows" to "composite actions" to match the new repo structure - Rewrite "Adding a new workflow" → "Adding a new action" to reflect composite-action-first approach: create action.yml, add ci.yml job, update README table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verifies both tools install correctly. Terramate stack steps (generate, init, list) are no-ops in this repo since no stacks are defined. OPENTOFU_VERSION and TERRAMATE_VERSION set via job-level env to match the action defaults. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
terramate --changed diffs against git history; shallow clone (depth 1) has only one commit so the flag errors. Full history required. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ossf/scorecard-action hard-requires the default branch and fails with "Only the default branch main is supported" on feature branches. Added github.ref == 'refs/heads/main' guard so the job is skipped on workflow_dispatch from feature branches and only runs on push to main and the weekly schedule. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When scorecard-action fails (e.g. wrong branch), scorecard-results.sarif is not produced. The if: always() upload steps then error with "Path does not exist". Add a check-sarif step that sets an output, and gate both upload steps on that output so they skip cleanly rather than error when the file is absent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Security Review:
|
| Finding | Initial Confidence | Filter Verdict | Reason |
|---|---|---|---|
checks: write + reviewdog compromise path |
7/10 | False positive (3/10) | Requires pre-compromised pinned SHA — supply chain risk, not a workflow vulnerability |
publish_results: true exposes posture data |
8/10 | False positive (2/10) | Requires independent repo visibility misconfiguration; not a concrete exploitable path |
| Terramate action ignores declared inputs (env vs inputs) | 9/10 | False positive (2/10) | Env vars are trusted values; no untrusted input path to exploit |
| SHA comment mismatch risk | 6/10 | Below threshold | Theoretical; SHA pinning is the trust anchor by design |
OIDC id-token: write blast radius |
5/10 | Below threshold | Mitigated by SHA pinning + refs/heads/main guard |
deny-licenses pass-through |
4/10 | Below threshold | No untrusted caller path in current usage |
| SARIF artifact predictable name | 5/10 | Below threshold | Artifact access requires repo read permission |
Confirmed safe patterns:
- No
pull_request_targettrigger (pwn-request vector absent) - No user-controlled context (
github.event.*,github.head_ref) interpolated intorun:steps - All
checkoutsteps usepersist-credentials: false - Top-level permissions locked to
contents: read; jobs narrow further - Scorecard correctly gated to
refs/heads/mainonly - No hardcoded secrets or tokens
🤖 Generated with Claude Code
Code ReviewFindings1.
|
GitHub Actions Security AuditRepository: sparkgeo/github-actions Findings⛔ HIGH — Default workflow permissions set to
|
| Check | Result |
|---|---|
| SHA pinning (all 10 references) | ✅ Pass |
Script injection (${{ }} in run: steps) |
✅ Pass |
pull_request_target trigger |
✅ Pass — not used |
persist-credentials: false on all checkouts |
✅ Pass |
| OIDC / no static cloud credentials | ✅ Pass |
| Job-level minimum permissions | ✅ Pass |
Scorecard gated to refs/heads/main only |
✅ Pass |
Score: 5/10
The workflow code is clean. The 3 HIGH findings are repo settings, not code — fix in Settings before treating this repo as a trusted security reference for the org.
🤖 Generated with Claude Code
Supply Chain Attack Detection ScanTool: Results
Note on permissions warningsThe scanner flagged all 6 composite Action publisher allow-list (confirmed)All external action references resolve to expected publishers:
VerdictNo supply chain attack vectors detected in workflow code. The outstanding gaps (no Dependabot SHA auto-update, 🤖 Generated with Claude Code |
terramate-opentofu-setup/action.yml: - Fix declared inputs being dead code: replace env.OPENTOFU_VERSION / env.TERRAMATE_VERSION with inputs.opentofu_version / inputs.terramate_version so callers using `with:` actually get their version honoured ci.yml: - Remove env: block from terramate-opentofu-setup job (now uses input defaults) - Gate storage-optimizer job to workflow_dispatch only — running docker prune on a throwaway ephemeral runner wastes CI minutes with zero benefit scorecard/action.yml: - Expose publish_results as an input (default: true) so private-repo callers can set it to false without forking the action - Replace check-sarif shell probe + output variable with if-no-files-found: ignore on upload-artifact and hashFiles() gate on upload-sarif — removes a step and eliminates the shell-subprocess failure mode - Add required permissions (id-token: write, security-events: write) to description CONTRIBUTING.md: - Add migration guide from the deleted workflow_call / workflow-lint.yml pattern to the equivalent composite action calls .github/dependabot.yml: add github-actions ecosystem, weekly schedule, 7-day cooldown to throttle supply-chain-attack-via-rapid-update vectors .github/CODEOWNERS: require @sparkgeo/security-team review for .github/ changes README.md: document publish_results input on scorecard action Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PR Review Fixes AppliedCode changes — commit
|
| Fix | File | Change |
|---|---|---|
| Inputs dead code | terramate-opentofu-setup/action.yml |
Steps now read ${{ inputs.opentofu_version }} / ${{ inputs.terramate_version }} instead of env.* — with: overrides now work |
| Duplicate version defaults | ci.yml |
Removed env: block from terramate-opentofu-setup job; action defaults apply directly |
check-sarif probe step |
scorecard/action.yml |
Replaced shell probe + output variable with if-no-files-found: ignore on upload-artifact and hashFiles() gate on upload-sarif |
publish_results hardcoded |
scorecard/action.yml |
Exposed as input (default: true); private-repo callers can pass publish_results: false |
| Undocumented permissions | scorecard/action.yml |
Required permissions added to action description |
workflow_call migration |
CONTRIBUTING.md |
Migration guide added: old workflow_call pattern → equivalent composite action calls |
| Storage optimizer overhead | ci.yml |
Job gated to workflow_dispatch only — no longer burns runner-minutes on every PR |
| Dependabot | .github/dependabot.yml |
Created — github-actions ecosystem, weekly, 7-day cooldown |
| CODEOWNERS | .github/CODEOWNERS |
Created — @sparkgeo/security-team required for .github/workflows/ and .github/actions/ |
Repository settings applied via API
| Setting | Before | After |
|---|---|---|
| Default workflow permissions | write |
read |
| Actions can approve PRs | true |
false |
Branch protection on main |
none | 1 required review + stale review dismissal + 4 required status checks + no force-push |
| Allowed actions | all |
GitHub-owned + ossf/, reviewdog/, zizmorcore/, opentofu/, terramate-io/ |
Required status checks on main: Actionlint, Zizmor, Dependency Review, Terramate + OpenTofu Setup
Remaining (not addressable in code or via API)
@sparkgeo/security-teamGitHub team must exist for CODEOWNERS to enforce review requirements — create the team in org settings and add membersrequire_code_owner_reviewsis currentlyfalsein branch protection; enable it once the team exists:Settings → Branches → main → Require review from code owners
🤖 Generated with Claude Code
…rkflow changes - Add concurrency group to ci.yml: cancels in-flight runs when new push arrives on same ref — avoids burning extra runner minutes on stale jobs - Add paths filter on push trigger: skip CI for doc/README-only pushes to main; scorecard still runs weekly via schedule so coverage unchanged - Parallelize storage-optimizer cleanup: run all rm -rf and docker prune in background with & + wait instead of sequentially — saves ~20-30s on workflow_dispatch Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Documents all nine external action references currently in use with pinned SHAs, versions, purposes, and review dates. Includes: - Security review checklist for approving new actions - Step-by-step process for adding a new publisher to the org allowlist - Renovate SHA update policy (pinDigests: true, no manual SHA edits) - Current org allowlist patterns (ossf/*, reviewdog/*, zizmorcore/*, opentofu/*, terramate-io/* — GitHub-owned via github_owned_allowed) Satisfies the docs/approved-actions.md acceptance criterion for #26. Remaining open item: Renovate github-actions manager (tracked in #8). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Implements the code-deliverable items from issue #29 (enterprise governance and observability).
scorecard.yml— weekly OpenSSF Scorecard analysis; publishes results to the OpenSSF database and uploads SARIF to the GitHub Security tabdependency-review.yml— blocks PRs that introduce dependencies with known vulnerabilities or denied licenses; reusable viaworkflow_callNotes
.pre-commit-config.yamlis included here to keep local hooks working while PR Add workflow authoring standards, actionlint/zizmor gate, and CONTRIB… #32 (issue feat: workflow authoring standards + actionlint/zizmor gate #25) is pending merge; the file is identical — no conflict on mergepublish_results: trueon Scorecard works becausesparkgeo/github-actionsis a public repo; results will appear on the OpenSSF Scorecard dashboardGPL-2.0, GPL-3.0, AGPL-3.0) match Sparkgeo's commercial use requirements — adjust via thedeny-licensesinput in consuming reposOrg-level items remaining in #29 (not implementable as code)
Closes #29
Test plan
actionlint+zizmorpass)For reviews I ran: