From 3ce22479e7b7757cd4cd7653d31b2ecf6529949b Mon Sep 17 00:00:00 2001 From: ms280690 Date: Thu, 21 May 2026 15:15:19 -0700 Subject: [PATCH 01/13] Add OpenSSF Scorecard and dependency review workflows (#29) 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 --- .github/workflows/dependency-review.yml | 47 +++++++++++++++++++++++++ .github/workflows/scorecard.yml | 46 ++++++++++++++++++++++++ .pre-commit-config.yaml | 10 ++++++ 3 files changed, 103 insertions(+) create mode 100644 .github/workflows/dependency-review.yml create mode 100644 .github/workflows/scorecard.yml create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..fdeba50 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,47 @@ +name: Dependency Review + +on: + pull_request: + paths: + - '**/package-lock.json' + - '**/yarn.lock' + - '**/pnpm-lock.yaml' + - '**/Pipfile.lock' + - '**/poetry.lock' + - '**/uv.lock' + - '**/go.sum' + - '**/Cargo.lock' + - '**/Gemfile.lock' + - '**/composer.lock' + - '**/requirements*.txt' + workflow_call: + inputs: + fail-on-severity: + description: 'Minimum vulnerability severity to fail: critical|high|moderate|low' + default: 'critical' + type: string + deny-licenses: + description: 'Comma-separated list of SPDX license identifiers to deny' + default: 'GPL-2.0,GPL-3.0,AGPL-3.0' + type: string + +permissions: + contents: read + +jobs: + review: + name: Dependency review + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write # post summary comment on PR + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + with: + fail-on-severity: ${{ inputs.fail-on-severity || 'critical' }} + deny-licenses: ${{ inputs.deny-licenses || 'GPL-2.0,GPL-3.0,AGPL-3.0' }} + comment-summary-in-pr: always diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..44a2e04 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,46 @@ +name: OpenSSF Scorecard + +on: + schedule: + - cron: '0 6 * * 1' # weekly Monday 06:00 UTC + push: + branches: [main] + workflow_dispatch: + +# Scorecard requires read access across the full repo surface. +# Job-level permissions below are the minimum required per step. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + contents: read + actions: read # read workflow files for dangerous-workflow check + security-events: write # upload SARIF to GitHub Security tab + id-token: write # sign and publish results to OpenSSF database (OIDC) + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: scorecard-results.sarif + results_format: sarif + repo_token: ${{ secrets.GITHUB_TOKEN }} + # publish_results: true uploads to the public OpenSSF Scorecard database and + # generates the Scorecard badge. Only works on public repos. + publish_results: true + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scorecard-results + path: scorecard-results.sarif + retention-days: 5 + + - uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 + with: + sarif_file: scorecard-results.sarif + category: scorecard diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..dac4505 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: + - repo: https://github.com/rhysd/actionlint + rev: 914e7df21a07ef503a81201c76d2b11c789d3fca # v1.7.12 + hooks: + - id: actionlint + + - repo: https://github.com/woodruffw/zizmor-pre-commit + rev: 9257c6050c0261b8c57e712f632dc4a8010109a9 # v1.25.2 + hooks: + - id: zizmor From 8d966961daabdacaf7d0cf72d0837384863f9fa5 Mon Sep 17 00:00:00 2001 From: ms280690 Date: Thu, 21 May 2026 15:30:37 -0700 Subject: [PATCH 02/13] Address PR review comments on scorecard and dependency-review workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/workflows/dependency-review.yml | 15 +++++++++++---- .github/workflows/scorecard.yml | 5 +---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index fdeba50..8ce7b57 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -14,16 +14,23 @@ on: - '**/Gemfile.lock' - '**/composer.lock' - '**/requirements*.txt' + - '**/pdm.lock' + - '**/mix.lock' + - '**/Package.resolved' workflow_call: inputs: fail-on-severity: description: 'Minimum vulnerability severity to fail: critical|high|moderate|low' - default: 'critical' + default: 'high' type: string deny-licenses: description: 'Comma-separated list of SPDX license identifiers to deny' default: 'GPL-2.0,GPL-3.0,AGPL-3.0' type: string + comment-summary-in-pr: + description: 'Post a summary comment on the PR: always|on-failure|never' + default: 'on-failure' + type: string permissions: contents: read @@ -34,7 +41,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - pull-requests: write # post summary comment on PR + pull-requests: write # required for comment-summary-in-pr steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -42,6 +49,6 @@ jobs: - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 with: - fail-on-severity: ${{ inputs.fail-on-severity || 'critical' }} + fail-on-severity: ${{ inputs.fail-on-severity || 'high' }} deny-licenses: ${{ inputs.deny-licenses || 'GPL-2.0,GPL-3.0,AGPL-3.0' }} - comment-summary-in-pr: always + comment-summary-in-pr: ${{ inputs.comment-summary-in-pr || 'on-failure' }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 44a2e04..0e732ae 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -7,10 +7,6 @@ on: branches: [main] workflow_dispatch: -# Scorecard requires read access across the full repo surface. -# Job-level permissions below are the minimum required per step. -permissions: read-all - jobs: analysis: name: Scorecard analysis @@ -41,6 +37,7 @@ jobs: retention-days: 5 - uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 + if: always() with: sarif_file: scorecard-results.sarif category: scorecard From 72f762c23861a75581d8facd99be7408b447325b Mon Sep 17 00:00:00 2001 From: ms280690 Date: Thu, 28 May 2026 12:59:54 -0700 Subject: [PATCH 03/13] Improve workflow descriptions, job names, and README documentation 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 --- .github/workflows/dependency-review.yml | 4 ++- .github/workflows/scorecard.yml | 4 ++- README.md | 46 +++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 8ce7b57..100e81c 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -1,4 +1,5 @@ name: Dependency Review +# Blocks PRs that introduce dependencies with known vulnerabilities or denied licenses. Reusable via workflow_call. on: pull_request: @@ -36,8 +37,9 @@ permissions: contents: read jobs: + # Scans dependency changes for known vulnerabilities and denied licenses; posts a summary comment on the PR. review: - name: Dependency review + name: Dependency Review runs-on: ubuntu-latest permissions: contents: read diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 0e732ae..352dc3b 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,4 +1,5 @@ name: OpenSSF Scorecard +# Runs weekly OpenSSF Scorecard analysis and publishes results to the OpenSSF database and GitHub Security tab. on: schedule: @@ -8,8 +9,9 @@ on: workflow_dispatch: jobs: + # Runs OpenSSF Scorecard checks, uploads SARIF to the Security tab, and publishes results to the OpenSSF database. analysis: - name: Scorecard analysis + name: Scorecard Analysis runs-on: ubuntu-latest permissions: contents: read diff --git a/README.md b/README.md index 0de93da..fa85d33 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Call these from any Sparkgeo repo by referencing the workflow file at a pinned S | Workflow | File | Triggers | Purpose | |---|---|---|---| | Actions Quality Gate | [`workflow-lint.yml`](.github/workflows/workflow-lint.yml) | `pull_request` on `.github/**`, `workflow_call` | Runs `actionlint` and `zizmor` against all workflow and composite action YAML files; posts annotations via GitHub Checks and uploads SARIF to the Security tab | +| OpenSSF Scorecard | [`scorecard.yml`](.github/workflows/scorecard.yml) | `schedule` (weekly Monday 06:00 UTC), `push` to `main`, `workflow_dispatch` | Runs OpenSSF Scorecard security checks; publishes results to the OpenSSF database and uploads SARIF to the GitHub Security tab | +| Dependency Review | [`dependency-review.yml`](.github/workflows/dependency-review.yml) | `pull_request` on lockfiles, `workflow_call` | Blocks PRs that introduce dependencies with known vulnerabilities or denied licenses; posts a summary comment on the PR | ### Usage @@ -37,6 +39,50 @@ Replace `` with the full commit SHA of the version you want to pin to: gh api repos/sparkgeo/github-actions/commits/main --jq '.sha' ``` +### OpenSSF Scorecard + +Runs automatically on a schedule. Add to any repo that should publish a Scorecard badge: + +```yaml +# .github/workflows/scorecard.yml (in a consuming repo) +on: + schedule: + - cron: '0 6 * * 1' + push: + branches: [main] + workflow_dispatch: + +jobs: + scorecard: + uses: sparkgeo/github-actions/.github/workflows/scorecard.yml@ + permissions: + contents: read + actions: read + security-events: write + id-token: write +``` + +### Dependency Review + +Blocks PRs that introduce vulnerable or denied-license dependencies. Callers can override defaults via `workflow_call` inputs: + +```yaml +# .github/workflows/dependency-review.yml (in a consuming repo) +on: + pull_request: + +jobs: + dependency-review: + uses: sparkgeo/github-actions/.github/workflows/dependency-review.yml@ + permissions: + contents: read + pull-requests: write + with: + fail-on-severity: high # critical | high | moderate | low + deny-licenses: GPL-2.0,AGPL-3.0 # SPDX identifiers + comment-summary-in-pr: always # always | on-failure | never +``` + ## Composite Actions Drop these into any job with a `uses:` step. From 84f4bc5bcb84039740d41076c40fbf67af39fa7c Mon Sep 17 00:00:00 2001 From: ms280690 Date: Thu, 28 May 2026 13:06:44 -0700 Subject: [PATCH 04/13] Add CI self-test workflow that dogfoods repo's own actions 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 --- .github/workflows/ci.yml | 35 +++++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 36 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..50ce500 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI +# Dogfoods this repo's own reusable workflows and composite actions on every push and PR. +# Serves as a live reference implementation for consuming repos. + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + # Run the repo's own workflow quality gate against itself. + workflow-lint: + name: Actions Quality Gate + uses: ./.github/workflows/workflow-lint.yml + permissions: + contents: read + checks: write + security-events: write + + # Verify the storage optimizer composite action runs without error. + storage-optimizer: + name: Storage Optimizer + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: ./.github/actions/storage-optimizer diff --git a/README.md b/README.md index fa85d33..7fd36ee 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Call these from any Sparkgeo repo by referencing the workflow file at a pinned S | Workflow | File | Triggers | Purpose | |---|---|---|---| +| CI | [`ci.yml`](.github/workflows/ci.yml) | `push` to `main`, `pull_request`, `workflow_dispatch` | Dogfoods this repo's own workflows and composite actions; serves as a live reference implementation for consuming repos | | Actions Quality Gate | [`workflow-lint.yml`](.github/workflows/workflow-lint.yml) | `pull_request` on `.github/**`, `workflow_call` | Runs `actionlint` and `zizmor` against all workflow and composite action YAML files; posts annotations via GitHub Checks and uploads SARIF to the Security tab | | OpenSSF Scorecard | [`scorecard.yml`](.github/workflows/scorecard.yml) | `schedule` (weekly Monday 06:00 UTC), `push` to `main`, `workflow_dispatch` | Runs OpenSSF Scorecard security checks; publishes results to the OpenSSF database and uploads SARIF to the GitHub Security tab | | Dependency Review | [`dependency-review.yml`](.github/workflows/dependency-review.yml) | `pull_request` on lockfiles, `workflow_call` | Blocks PRs that introduce dependencies with known vulnerabilities or denied licenses; posts a summary comment on the PR | From 7ee973b72d88894e3f898ee96c8dd093bca86e9c Mon Sep 17 00:00:00 2001 From: ms280690 Date: Thu, 28 May 2026 13:18:48 -0700 Subject: [PATCH 05/13] Restructure: convert reusable workflows to composite actions, single ci.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/actions/actionlint/action.yml | 10 ++ .github/actions/dependency-review/action.yml | 20 ++++ .github/actions/scorecard/action.yml | 25 +++++ .github/actions/zizmor/action.yml | 11 ++ .github/workflows/ci.yml | 65 ++++++++++- .github/workflows/dependency-review.yml | 56 ---------- .github/workflows/scorecard.yml | 45 -------- .github/workflows/workflow-lint.yml | 54 --------- README.md | 112 ++++++++++--------- 9 files changed, 187 insertions(+), 211 deletions(-) create mode 100644 .github/actions/actionlint/action.yml create mode 100644 .github/actions/dependency-review/action.yml create mode 100644 .github/actions/scorecard/action.yml create mode 100644 .github/actions/zizmor/action.yml delete mode 100644 .github/workflows/dependency-review.yml delete mode 100644 .github/workflows/scorecard.yml delete mode 100644 .github/workflows/workflow-lint.yml diff --git a/.github/actions/actionlint/action.yml b/.github/actions/actionlint/action.yml new file mode 100644 index 0000000..bcf13ac --- /dev/null +++ b/.github/actions/actionlint/action.yml @@ -0,0 +1,10 @@ +name: "Actionlint" +description: "Lints GitHub Actions workflow and composite action YAML files using actionlint via reviewdog." +runs: + using: "composite" + steps: + - uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1.72.0 + with: + github_token: ${{ github.token }} + reporter: github-check + fail_level: error diff --git a/.github/actions/dependency-review/action.yml b/.github/actions/dependency-review/action.yml new file mode 100644 index 0000000..6d2ee88 --- /dev/null +++ b/.github/actions/dependency-review/action.yml @@ -0,0 +1,20 @@ +name: "Dependency Review" +description: "Blocks PRs that introduce dependencies with known vulnerabilities or denied licenses; posts a summary comment on the PR." +inputs: + fail-on-severity: + description: "Minimum vulnerability severity to fail: critical|high|moderate|low" + default: "high" + deny-licenses: + description: "Comma-separated list of SPDX license identifiers to deny" + default: "GPL-2.0,GPL-3.0,AGPL-3.0" + comment-summary-in-pr: + description: "Post a summary comment on the PR: always|on-failure|never" + default: "on-failure" +runs: + using: "composite" + steps: + - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + with: + fail-on-severity: ${{ inputs.fail-on-severity }} + deny-licenses: ${{ inputs.deny-licenses }} + comment-summary-in-pr: ${{ inputs.comment-summary-in-pr }} diff --git a/.github/actions/scorecard/action.yml b/.github/actions/scorecard/action.yml new file mode 100644 index 0000000..5bbb7ef --- /dev/null +++ b/.github/actions/scorecard/action.yml @@ -0,0 +1,25 @@ +name: "OpenSSF Scorecard" +description: "Runs OpenSSF Scorecard checks, uploads SARIF to the GitHub Security tab, and publishes results to the OpenSSF database." +runs: + using: "composite" + steps: + - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: scorecard-results.sarif + results_format: sarif + repo_token: ${{ github.token }} + # publish_results uploads to the public OpenSSF Scorecard database and + # generates the Scorecard badge. Only works on public repos. + publish_results: true + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scorecard-results + path: scorecard-results.sarif + retention-days: 5 + + - uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 + if: always() + with: + sarif_file: scorecard-results.sarif + category: scorecard diff --git a/.github/actions/zizmor/action.yml b/.github/actions/zizmor/action.yml new file mode 100644 index 0000000..df14ed7 --- /dev/null +++ b/.github/actions/zizmor/action.yml @@ -0,0 +1,11 @@ +name: "Zizmor" +description: "Runs zizmor static security analysis against GitHub Actions workflow and action YAML files; uploads findings as SARIF to the GitHub Security tab." +runs: + using: "composite" + steps: + - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 + with: + version: "1.25.2" + advanced-security: "true" + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50ce500..d62cf22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,25 +1,80 @@ name: CI -# Dogfoods this repo's own reusable workflows and composite actions on every push and PR. +# Dogfoods this repo's own composite actions on every push and PR. # Serves as a live reference implementation for consuming repos. on: push: branches: [main] pull_request: + schedule: + - cron: '0 6 * * 1' # weekly Monday 06:00 UTC — for scorecard workflow_dispatch: permissions: contents: read jobs: - # Run the repo's own workflow quality gate against itself. - workflow-lint: - name: Actions Quality Gate - uses: ./.github/workflows/workflow-lint.yml + # Lint workflow and action YAML files with actionlint. + actionlint: + name: Actionlint + runs-on: ubuntu-latest permissions: contents: read checks: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: ./.github/actions/actionlint + + # Security-scan workflow and action YAML files with zizmor. + zizmor: + name: Zizmor + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: ./.github/actions/zizmor + + # Run OpenSSF Scorecard on push to main, schedule, and manual dispatch. + # Skipped on pull_request — publishing partial results to OpenSSF is not useful. + scorecard: + name: OpenSSF Scorecard + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + permissions: + contents: read + actions: read security-events: write + id-token: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: ./.github/actions/scorecard + + # Review dependency changes for vulnerabilities and license issues. + # Only meaningful on pull_request — requires PR base/head context. + dependency-review: + name: Dependency Review + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: ./.github/actions/dependency-review # Verify the storage optimizer composite action runs without error. storage-optimizer: diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml deleted file mode 100644 index 100e81c..0000000 --- a/.github/workflows/dependency-review.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Dependency Review -# Blocks PRs that introduce dependencies with known vulnerabilities or denied licenses. Reusable via workflow_call. - -on: - pull_request: - paths: - - '**/package-lock.json' - - '**/yarn.lock' - - '**/pnpm-lock.yaml' - - '**/Pipfile.lock' - - '**/poetry.lock' - - '**/uv.lock' - - '**/go.sum' - - '**/Cargo.lock' - - '**/Gemfile.lock' - - '**/composer.lock' - - '**/requirements*.txt' - - '**/pdm.lock' - - '**/mix.lock' - - '**/Package.resolved' - workflow_call: - inputs: - fail-on-severity: - description: 'Minimum vulnerability severity to fail: critical|high|moderate|low' - default: 'high' - type: string - deny-licenses: - description: 'Comma-separated list of SPDX license identifiers to deny' - default: 'GPL-2.0,GPL-3.0,AGPL-3.0' - type: string - comment-summary-in-pr: - description: 'Post a summary comment on the PR: always|on-failure|never' - default: 'on-failure' - type: string - -permissions: - contents: read - -jobs: - # Scans dependency changes for known vulnerabilities and denied licenses; posts a summary comment on the PR. - review: - name: Dependency Review - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write # required for comment-summary-in-pr - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 - with: - fail-on-severity: ${{ inputs.fail-on-severity || 'high' }} - deny-licenses: ${{ inputs.deny-licenses || 'GPL-2.0,GPL-3.0,AGPL-3.0' }} - comment-summary-in-pr: ${{ inputs.comment-summary-in-pr || 'on-failure' }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml deleted file mode 100644 index 352dc3b..0000000 --- a/.github/workflows/scorecard.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: OpenSSF Scorecard -# Runs weekly OpenSSF Scorecard analysis and publishes results to the OpenSSF database and GitHub Security tab. - -on: - schedule: - - cron: '0 6 * * 1' # weekly Monday 06:00 UTC - push: - branches: [main] - workflow_dispatch: - -jobs: - # Runs OpenSSF Scorecard checks, uploads SARIF to the Security tab, and publishes results to the OpenSSF database. - analysis: - name: Scorecard Analysis - runs-on: ubuntu-latest - permissions: - contents: read - actions: read # read workflow files for dangerous-workflow check - security-events: write # upload SARIF to GitHub Security tab - id-token: write # sign and publish results to OpenSSF database (OIDC) - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 - with: - results_file: scorecard-results.sarif - results_format: sarif - repo_token: ${{ secrets.GITHUB_TOKEN }} - # publish_results: true uploads to the public OpenSSF Scorecard database and - # generates the Scorecard badge. Only works on public repos. - publish_results: true - - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: scorecard-results - path: scorecard-results.sarif - retention-days: 5 - - - uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 - if: always() - with: - sarif_file: scorecard-results.sarif - category: scorecard diff --git a/.github/workflows/workflow-lint.yml b/.github/workflows/workflow-lint.yml deleted file mode 100644 index 07f3294..0000000 --- a/.github/workflows/workflow-lint.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Actions Quality Gate -# Lints and security-scans all workflow and composite action YAML files using actionlint and zizmor. - -on: - pull_request: - paths: - - '.github/workflows/**' - - '.github/actions/**' - # Intentionally zero-input: callers get the same lint gate with no configuration. - # Pin this workflow at a SHA in calling workflows for supply-chain safety. - workflow_call: - -permissions: - contents: read - -jobs: - # Validates workflow and action YAML syntax and logic using actionlint. - # Posts findings as GitHub Check annotations visible in the PR checks tab. - actionlint: - name: Workflow Lint - runs-on: ubuntu-latest - permissions: - contents: read - checks: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1.72.0 - with: - github_token: ${{ github.token }} - reporter: github-check - fail_level: error - - # Runs zizmor static security analysis against workflow and action YAML files. - # Uploads findings as SARIF to the GitHub Security tab for triage and tracking. - zizmor: - name: Static Security Analysis - runs-on: ubuntu-latest - permissions: - contents: read - security-events: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 - with: - version: "1.25.2" - advanced-security: "true" - env: - GITHUB_TOKEN: ${{ github.token }} diff --git a/README.md b/README.md index 7fd36ee..0afc112 100644 --- a/README.md +++ b/README.md @@ -1,98 +1,108 @@ # sparkgeo/github-actions -Reusable GitHub Actions workflows and composite actions for the Sparkgeo organisation. +Reusable GitHub Actions composite actions and CI workflow for the Sparkgeo organisation. -All action references in this repo are pinned to full commit SHAs. See [CONTRIBUTING.md](CONTRIBUTING.md) for authoring standards and how to add new workflows. +All action references in this repo are pinned to full commit SHAs. See [CONTRIBUTING.md](CONTRIBUTING.md) for authoring standards and how to add new actions. -## Reusable Workflows - -Call these from any Sparkgeo repo by referencing the workflow file at a pinned SHA. +## Workflow | Workflow | File | Triggers | Purpose | |---|---|---|---| -| CI | [`ci.yml`](.github/workflows/ci.yml) | `push` to `main`, `pull_request`, `workflow_dispatch` | Dogfoods this repo's own workflows and composite actions; serves as a live reference implementation for consuming repos | -| Actions Quality Gate | [`workflow-lint.yml`](.github/workflows/workflow-lint.yml) | `pull_request` on `.github/**`, `workflow_call` | Runs `actionlint` and `zizmor` against all workflow and composite action YAML files; posts annotations via GitHub Checks and uploads SARIF to the Security tab | -| OpenSSF Scorecard | [`scorecard.yml`](.github/workflows/scorecard.yml) | `schedule` (weekly Monday 06:00 UTC), `push` to `main`, `workflow_dispatch` | Runs OpenSSF Scorecard security checks; publishes results to the OpenSSF database and uploads SARIF to the GitHub Security tab | -| Dependency Review | [`dependency-review.yml`](.github/workflows/dependency-review.yml) | `pull_request` on lockfiles, `workflow_call` | Blocks PRs that introduce dependencies with known vulnerabilities or denied licenses; posts a summary comment on the PR | +| CI | [`ci.yml`](.github/workflows/ci.yml) | `push` to `main`, `pull_request`, `schedule` (weekly), `workflow_dispatch` | Dogfoods all composite actions in this repo; serves as a live reference implementation | + +## Composite Actions -### Usage +Drop these into any job with a `uses:` step. Pin to a full commit SHA for supply-chain safety. -```yaml -# .github/workflows/lint.yml (in a consuming repo) -on: - pull_request: - paths: - - '.github/workflows/**' - - '.github/actions/**' +```bash +# Find the SHA to pin to +gh api repos/sparkgeo/github-actions/commits/main --jq '.sha' +``` + +| Action | Path | Purpose | Inputs | +|---|---|---|---| +| Actionlint | [`actionlint`](.github/actions/actionlint/action.yml) | Lints workflow and action YAML files using actionlint via reviewdog; posts annotations as GitHub Checks | None | +| Zizmor | [`zizmor`](.github/actions/zizmor/action.yml) | Runs zizmor static security analysis against workflow and action YAML files; uploads findings as SARIF to the Security tab | None | +| OpenSSF Scorecard | [`scorecard`](.github/actions/scorecard/action.yml) | Runs OpenSSF Scorecard checks; uploads SARIF to the Security tab and publishes results to the OpenSSF database | None | +| Dependency Review | [`dependency-review`](.github/actions/dependency-review/action.yml) | Blocks PRs introducing dependencies with known vulnerabilities or denied licenses; posts a summary comment | `fail-on-severity` (default: `high`), `deny-licenses` (default: `GPL-2.0,GPL-3.0,AGPL-3.0`), `comment-summary-in-pr` (default: `on-failure`) | +| Storage Optimizer | [`storage-optimizer`](.github/actions/storage-optimizer/action.yml) | Frees disk space on GitHub-hosted runners by removing unused toolchains (JDK, .NET, Swift, Android SDK, etc.) and pruning Docker | None | +| Terramate + OpenTofu Setup | [`terramate-opentofu-setup`](.github/actions/terramate-opentofu-setup/action.yml) | Installs Terramate and OpenTofu, validates generated files are up to date, initialises changed stacks, and lists changed stacks | `opentofu_version` (default: `1.10.0`), `terramate_version` (default: `0.14.7`) | + +### Actionlint +```yaml jobs: lint: - uses: sparkgeo/github-actions/.github/workflows/workflow-lint.yml@ + runs-on: ubuntu-latest permissions: contents: read checks: write - security-events: write + steps: + - uses: actions/checkout@ + with: + persist-credentials: false + - uses: sparkgeo/github-actions/.github/actions/actionlint@ ``` -Replace `` with the full commit SHA of the version you want to pin to: +### Zizmor -```bash -gh api repos/sparkgeo/github-actions/commits/main --jq '.sha' +```yaml +jobs: + security-scan: + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - uses: actions/checkout@ + with: + persist-credentials: false + - uses: sparkgeo/github-actions/.github/actions/zizmor@ ``` ### OpenSSF Scorecard -Runs automatically on a schedule. Add to any repo that should publish a Scorecard badge: +Requires `id-token: write` for OIDC signing. Only runs on public repos with `publish_results: true`. ```yaml -# .github/workflows/scorecard.yml (in a consuming repo) -on: - schedule: - - cron: '0 6 * * 1' - push: - branches: [main] - workflow_dispatch: - jobs: scorecard: - uses: sparkgeo/github-actions/.github/workflows/scorecard.yml@ + runs-on: ubuntu-latest permissions: contents: read actions: read security-events: write id-token: write + steps: + - uses: actions/checkout@ + with: + persist-credentials: false + - uses: sparkgeo/github-actions/.github/actions/scorecard@ ``` ### Dependency Review -Blocks PRs that introduce vulnerable or denied-license dependencies. Callers can override defaults via `workflow_call` inputs: +Only meaningful on `pull_request` events — requires PR base/head context. ```yaml -# .github/workflows/dependency-review.yml (in a consuming repo) -on: - pull_request: - jobs: dependency-review: - uses: sparkgeo/github-actions/.github/workflows/dependency-review.yml@ + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' permissions: contents: read pull-requests: write - with: - fail-on-severity: high # critical | high | moderate | low - deny-licenses: GPL-2.0,AGPL-3.0 # SPDX identifiers - comment-summary-in-pr: always # always | on-failure | never + steps: + - uses: actions/checkout@ + with: + persist-credentials: false + - uses: sparkgeo/github-actions/.github/actions/dependency-review@ + with: + fail-on-severity: high # critical | high | moderate | low + deny-licenses: GPL-2.0,AGPL-3.0 # SPDX identifiers + comment-summary-in-pr: always # always | on-failure | never ``` -## Composite Actions - -Drop these into any job with a `uses:` step. - -| Action | Path | Purpose | Inputs | -|---|---|---|---| -| Storage Optimizer | [`storage-optimizer`](.github/actions/storage-optimizer/action.yml) | Frees disk space on GitHub-hosted runners by removing unused toolchains (JDK, .NET, Swift, Android SDK, etc.) and pruning Docker | None | -| Terramate + OpenTofu Setup | [`terramate-opentofu-setup`](.github/actions/terramate-opentofu-setup/action.yml) | Installs Terramate and OpenTofu, validates that generated files are up to date, initialises changed stacks, and lists changed stacks | `opentofu_version` (default: `1.10.0`), `terramate_version` (default: `0.14.7`) | - ### Storage Optimizer Reclaims ~30 GB on `ubuntu-latest` runners — useful before large build or scan jobs. From cff42d79fcdf7a2c788a62cf154fbbb56ca2fd8e Mon Sep 17 00:00:00 2001 From: ms280690 Date: Thu, 28 May 2026 13:21:19 -0700 Subject: [PATCH 06/13] Rename actionlint action to github-actionlint; update CONTRIBUTING.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../{actionlint => github-actionlint}/action.yml | 0 .github/workflows/ci.yml | 2 +- CONTRIBUTING.md | 11 ++++++----- README.md | 6 +++--- 4 files changed, 10 insertions(+), 9 deletions(-) rename .github/actions/{actionlint => github-actionlint}/action.yml (100%) diff --git a/.github/actions/actionlint/action.yml b/.github/actions/github-actionlint/action.yml similarity index 100% rename from .github/actions/actionlint/action.yml rename to .github/actions/github-actionlint/action.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d62cf22..a0d49aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - - uses: ./.github/actions/actionlint + - uses: ./.github/actions/github-actionlint # Security-scan workflow and action YAML files with zizmor. zizmor: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1341dab..3ed6d51 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing -This repo is the central library of reusable GitHub Actions workflows for the Sparkgeo organisation. All contributions must meet the security standards below before a PR can be merged. +This repo is the central library of reusable GitHub Actions composite actions for the Sparkgeo organisation. All contributions must meet the security standards below before a PR can be merged. ## Workflow authoring checklist @@ -57,14 +57,15 @@ gh api repos/actions/checkout/commits/v6 --jq '.sha' Once configured, Renovate (issue #8) will keep pinned SHAs current automatically via automated PRs — do not update SHAs manually unless fixing a security incident. -## Adding a new workflow +## Adding a new action 1. Create a GitHub issue (parent + context sub-issues where applicable) 2. Assign to the relevant team or individual, type: Feature, labels: `security`, `enhancement`, `documentation`, `priority: high` 3. Branch from `main`: `git checkout -b issue--` -4. Implement the workflow — satisfy all checklist items above -5. Update the workflow index table in `README.md` -6. Open a PR referencing the issue: `Closes #` +4. Create the composite action under `.github/actions//action.yml` — satisfy all checklist items above +5. Add a job for it in `.github/workflows/ci.yml` with minimum required permissions +6. Update the composite actions table in `README.md` with a usage example +7. Open a PR referencing the issue: `Closes #` ## Security pillars reference diff --git a/README.md b/README.md index 0afc112..57feed2 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,14 @@ gh api repos/sparkgeo/github-actions/commits/main --jq '.sha' | Action | Path | Purpose | Inputs | |---|---|---|---| -| Actionlint | [`actionlint`](.github/actions/actionlint/action.yml) | Lints workflow and action YAML files using actionlint via reviewdog; posts annotations as GitHub Checks | None | +| GitHub Actionlint | [`github-actionlint`](.github/actions/github-actionlint/action.yml) | Lints workflow and action YAML files using actionlint via reviewdog; posts annotations as GitHub Checks | None | | Zizmor | [`zizmor`](.github/actions/zizmor/action.yml) | Runs zizmor static security analysis against workflow and action YAML files; uploads findings as SARIF to the Security tab | None | | OpenSSF Scorecard | [`scorecard`](.github/actions/scorecard/action.yml) | Runs OpenSSF Scorecard checks; uploads SARIF to the Security tab and publishes results to the OpenSSF database | None | | Dependency Review | [`dependency-review`](.github/actions/dependency-review/action.yml) | Blocks PRs introducing dependencies with known vulnerabilities or denied licenses; posts a summary comment | `fail-on-severity` (default: `high`), `deny-licenses` (default: `GPL-2.0,GPL-3.0,AGPL-3.0`), `comment-summary-in-pr` (default: `on-failure`) | | Storage Optimizer | [`storage-optimizer`](.github/actions/storage-optimizer/action.yml) | Frees disk space on GitHub-hosted runners by removing unused toolchains (JDK, .NET, Swift, Android SDK, etc.) and pruning Docker | None | | Terramate + OpenTofu Setup | [`terramate-opentofu-setup`](.github/actions/terramate-opentofu-setup/action.yml) | Installs Terramate and OpenTofu, validates generated files are up to date, initialises changed stacks, and lists changed stacks | `opentofu_version` (default: `1.10.0`), `terramate_version` (default: `0.14.7`) | -### Actionlint +### GitHub Actionlint ```yaml jobs: @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@ with: persist-credentials: false - - uses: sparkgeo/github-actions/.github/actions/actionlint@ + - uses: sparkgeo/github-actions/.github/actions/github-actionlint@ ``` ### Zizmor From 8bc8db70060fb287e4392f4c8ffad1675ee838b6 Mon Sep 17 00:00:00 2001 From: ms280690 Date: Thu, 28 May 2026 13:24:07 -0700 Subject: [PATCH 07/13] Add terramate-opentofu-setup job to ci.yml 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 --- .github/workflows/ci.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0d49aa..b84d3fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,6 +76,23 @@ jobs: - uses: ./.github/actions/dependency-review + # Verify Terramate and OpenTofu are installed correctly. + # Terramate stack steps are no-ops in this repo (no stacks defined). + terramate-opentofu-setup: + name: Terramate + OpenTofu Setup + runs-on: ubuntu-latest + permissions: + contents: read + env: + OPENTOFU_VERSION: "1.10.0" + TERRAMATE_VERSION: "0.14.7" + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: ./.github/actions/terramate-opentofu-setup + # Verify the storage optimizer composite action runs without error. storage-optimizer: name: Storage Optimizer From 31b99f76e78cd952f6d88e265b9f7f3445ce9df8 Mon Sep 17 00:00:00 2001 From: ms280690 Date: Thu, 28 May 2026 13:29:24 -0700 Subject: [PATCH 08/13] Fix terramate-opentofu-setup job: fetch-depth: 0 for --changed flag 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 --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b84d3fa..66d54aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,6 +90,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + fetch-depth: 0 # terramate --changed requires full git history - uses: ./.github/actions/terramate-opentofu-setup From b925c920152b8485279dddb2b28382bff2cfad5b Mon Sep 17 00:00:00 2001 From: ms280690 Date: Thu, 28 May 2026 13:35:47 -0700 Subject: [PATCH 09/13] Fix scorecard job: require refs/heads/main, not just non-pull_request 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 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66d54aa..73e3ea3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,12 +42,12 @@ jobs: - uses: ./.github/actions/zizmor - # Run OpenSSF Scorecard on push to main, schedule, and manual dispatch. - # Skipped on pull_request — publishing partial results to OpenSSF is not useful. + # Run OpenSSF Scorecard on push to main and schedule only. + # Scorecard hard-requires the default branch; skipped on PRs and feature branches. scorecard: name: OpenSSF Scorecard runs-on: ubuntu-latest - if: github.event_name != 'pull_request' + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' permissions: contents: read actions: read From 72072713772e65885c0fbc03c94d420dbb20c5d5 Mon Sep 17 00:00:00 2001 From: ms280690 Date: Thu, 28 May 2026 13:36:21 -0700 Subject: [PATCH 10/13] Fix scorecard action: guard SARIF uploads against missing file 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 --- .github/actions/scorecard/action.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/actions/scorecard/action.yml b/.github/actions/scorecard/action.yml index 5bbb7ef..17af903 100644 --- a/.github/actions/scorecard/action.yml +++ b/.github/actions/scorecard/action.yml @@ -12,14 +12,20 @@ runs: # generates the Scorecard badge. Only works on public repos. publish_results: true + - id: check-sarif + shell: bash + if: always() + run: echo "exists=$(test -f scorecard-results.sarif && echo true || echo false)" >> "$GITHUB_OUTPUT" + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: always() && steps.check-sarif.outputs.exists == 'true' with: name: scorecard-results path: scorecard-results.sarif retention-days: 5 - uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 - if: always() + if: always() && steps.check-sarif.outputs.exists == 'true' with: sarif_file: scorecard-results.sarif category: scorecard From a87a70887875c59da2873b1b24d6773b23822312 Mon Sep 17 00:00:00 2001 From: ms280690 Date: Thu, 28 May 2026 15:15:04 -0700 Subject: [PATCH 11/13] Address PR review findings: inputs, scorecard, dependabot, CODEOWNERS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/CODEOWNERS | 3 ++ .github/actions/scorecard/action.yml | 22 ++++----- .../terramate-opentofu-setup/action.yml | 4 +- .github/dependabot.yml | 10 +++++ .github/workflows/ci.yml | 6 +-- CONTRIBUTING.md | 45 +++++++++++++++++++ README.md | 2 +- 7 files changed, 75 insertions(+), 17 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/dependabot.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..7ff7438 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# Changes to workflow and action files require security team review. +.github/workflows/ @sparkgeo/security-team +.github/actions/ @sparkgeo/security-team diff --git a/.github/actions/scorecard/action.yml b/.github/actions/scorecard/action.yml index 17af903..204f1ec 100644 --- a/.github/actions/scorecard/action.yml +++ b/.github/actions/scorecard/action.yml @@ -1,5 +1,11 @@ name: "OpenSSF Scorecard" -description: "Runs OpenSSF Scorecard checks, uploads SARIF to the GitHub Security tab, and publishes results to the OpenSSF database." +description: | + Runs OpenSSF Scorecard checks, uploads SARIF to the GitHub Security tab, and publishes results to the OpenSSF database. + Required permissions on calling job: id-token: write, security-events: write, actions: read, contents: read +inputs: + publish_results: + description: "Publish results to the public OpenSSF Scorecard database and generate the Scorecard badge. Set to false for private repositories." + default: "true" runs: using: "composite" steps: @@ -8,24 +14,18 @@ runs: results_file: scorecard-results.sarif results_format: sarif repo_token: ${{ github.token }} - # publish_results uploads to the public OpenSSF Scorecard database and - # generates the Scorecard badge. Only works on public repos. - publish_results: true - - - id: check-sarif - shell: bash - if: always() - run: echo "exists=$(test -f scorecard-results.sarif && echo true || echo false)" >> "$GITHUB_OUTPUT" + publish_results: ${{ inputs.publish_results }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - if: always() && steps.check-sarif.outputs.exists == 'true' + if: always() with: name: scorecard-results path: scorecard-results.sarif retention-days: 5 + if-no-files-found: ignore - uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 - if: always() && steps.check-sarif.outputs.exists == 'true' + if: always() && hashFiles('scorecard-results.sarif') != '' with: sarif_file: scorecard-results.sarif category: scorecard diff --git a/.github/actions/terramate-opentofu-setup/action.yml b/.github/actions/terramate-opentofu-setup/action.yml index 8e1ce61..dc9de04 100644 --- a/.github/actions/terramate-opentofu-setup/action.yml +++ b/.github/actions/terramate-opentofu-setup/action.yml @@ -16,13 +16,13 @@ runs: - name: Setup OpenTofu uses: opentofu/setup-opentofu@847eaa4afeb791b06daa46e8eafa8b1b68d7cfb4 # v2.0.1 with: - tofu_version: ${{ env.OPENTOFU_VERSION }} + tofu_version: ${{ inputs.opentofu_version }} tofu_wrapper: false - name: Setup Terramate uses: terramate-io/terramate-action@c5a1375801a3fac44d29fc7e0ebb3757ea0db3e0 # v3.0.0 with: - version: ${{ env.TERRAMATE_VERSION }} + version: ${{ inputs.terramate_version }} - name: Check Terramate configurations are up to date shell: bash diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..bc1d356 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + commit-message: + prefix: "ci" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73e3ea3..3060ea0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,9 +83,6 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - env: - OPENTOFU_VERSION: "1.10.0" - TERRAMATE_VERSION: "0.14.7" steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -95,9 +92,12 @@ jobs: - uses: ./.github/actions/terramate-opentofu-setup # Verify the storage optimizer composite action runs without error. + # Gated to workflow_dispatch: storage-optimizer is only useful when a job does + # heavy disk work on the same runner; running it on a throwaway CI runner wastes minutes. storage-optimizer: name: Storage Optimizer runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' permissions: contents: read steps: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3ed6d51..ce25150 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,6 +67,51 @@ Once configured, Renovate (issue #8) will keep pinned SHAs current automatically 6. Update the composite actions table in `README.md` with a usage example 7. Open a PR referencing the issue: `Closes #` +## Migrating from the reusable workflow pattern + +Prior to issue #29, this repo exposed an `Actions Quality Gate` reusable workflow +(`workflow-lint.yml`) callable via `workflow_call`. That file has been deleted. + +If your repo references it: + +```yaml +# OLD — no longer works +jobs: + lint: + uses: sparkgeo/github-actions/.github/workflows/workflow-lint.yml@ +``` + +Replace with direct composite action calls: + +```yaml +# NEW +jobs: + actionlint: + runs-on: ubuntu-latest + permissions: + contents: read + checks: write + steps: + - uses: actions/checkout@ + with: + persist-credentials: false + - uses: sparkgeo/github-actions/.github/actions/github-actionlint@ + + zizmor: + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - uses: actions/checkout@ + with: + persist-credentials: false + - uses: sparkgeo/github-actions/.github/actions/zizmor@ +``` + +The composite actions are individually callable and require explicit job shells with +correct permissions (see README for full usage examples). + ## Security pillars reference | Pillar | Issue | Summary | diff --git a/README.md b/README.md index 57feed2..3f34c48 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ gh api repos/sparkgeo/github-actions/commits/main --jq '.sha' |---|---|---|---| | GitHub Actionlint | [`github-actionlint`](.github/actions/github-actionlint/action.yml) | Lints workflow and action YAML files using actionlint via reviewdog; posts annotations as GitHub Checks | None | | Zizmor | [`zizmor`](.github/actions/zizmor/action.yml) | Runs zizmor static security analysis against workflow and action YAML files; uploads findings as SARIF to the Security tab | None | -| OpenSSF Scorecard | [`scorecard`](.github/actions/scorecard/action.yml) | Runs OpenSSF Scorecard checks; uploads SARIF to the Security tab and publishes results to the OpenSSF database | None | +| OpenSSF Scorecard | [`scorecard`](.github/actions/scorecard/action.yml) | Runs OpenSSF Scorecard checks; uploads SARIF to the Security tab and publishes results to the OpenSSF database | `publish_results` (default: `true` — set to `false` for private repos) | | Dependency Review | [`dependency-review`](.github/actions/dependency-review/action.yml) | Blocks PRs introducing dependencies with known vulnerabilities or denied licenses; posts a summary comment | `fail-on-severity` (default: `high`), `deny-licenses` (default: `GPL-2.0,GPL-3.0,AGPL-3.0`), `comment-summary-in-pr` (default: `on-failure`) | | Storage Optimizer | [`storage-optimizer`](.github/actions/storage-optimizer/action.yml) | Frees disk space on GitHub-hosted runners by removing unused toolchains (JDK, .NET, Swift, Android SDK, etc.) and pruning Docker | None | | Terramate + OpenTofu Setup | [`terramate-opentofu-setup`](.github/actions/terramate-opentofu-setup/action.yml) | Installs Terramate and OpenTofu, validates generated files are up to date, initialises changed stacks, and lists changed stacks | `opentofu_version` (default: `1.10.0`), `terramate_version` (default: `0.14.7`) | From 63217c00df4644b4e81eff0ecc654aca7e78bfa3 Mon Sep 17 00:00:00 2001 From: ms280690 Date: Fri, 29 May 2026 10:06:19 -0700 Subject: [PATCH 12/13] perf: cancel stale runs, parallelize cleanup, skip push CI for non-workflow changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .github/actions/storage-optimizer/action.yml | 40 ++++++++------------ .github/workflows/ci.yml | 6 +++ 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/.github/actions/storage-optimizer/action.yml b/.github/actions/storage-optimizer/action.yml index 1c7d7aa..1626709 100644 --- a/.github/actions/storage-optimizer/action.yml +++ b/.github/actions/storage-optimizer/action.yml @@ -6,29 +6,19 @@ runs: - name: Cleanup shell: bash run: | - # Remove Java (JDKs) - sudo rm -rf /usr/lib/jvm - # Remove .NET SDKs - sudo rm -rf /usr/share/dotnet - # Remove Swift toolchain - sudo rm -rf /usr/share/swift - # Remove Haskell (GHC) - sudo rm -rf /usr/local/.ghcup - # Remove Julia - sudo rm -rf /usr/local/julia* - # Remove Android SDK - sudo rm -rf /usr/local/lib/android - # Remove Chromium - sudo rm -rf /usr/local/share/chromium - # Remove Microsoft and Google tools - sudo rm -rf /opt/microsoft /opt/google - # Remove Azure CLI - sudo rm -rf /opt/az - # Remove PowerShell - sudo rm -rf /usr/local/share/powershell - # Remove cached tools - sudo rm -rf /opt/hostedtoolcache - # Clean up Docker - docker system prune -af || true - docker builder prune -af || true + sudo rm -rf \ + /usr/lib/jvm \ + /usr/share/dotnet \ + /usr/share/swift \ + /usr/local/.ghcup \ + /usr/local/julia* \ + /usr/local/lib/android \ + /usr/local/share/chromium \ + /opt/microsoft /opt/google \ + /opt/az \ + /usr/local/share/powershell \ + /opt/hostedtoolcache & + docker system prune -af || true & + docker builder prune -af || true & + wait df -h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3060ea0..1ad4783 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,8 @@ name: CI on: push: branches: [main] + paths: + - '.github/**' pull_request: schedule: - cron: '0 6 * * 1' # weekly Monday 06:00 UTC — for scorecard @@ -13,6 +15,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: # Lint workflow and action YAML files with actionlint. actionlint: From 2f3932358c2e48f6c167f6e71366661ac234ee86 Mon Sep 17 00:00:00 2001 From: Mehul Solanki Date: Fri, 29 May 2026 11:30:35 -0700 Subject: [PATCH 13/13] feat(#26): add docs/approved-actions.md and link from README (#36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- README.md | 2 +- docs/approved-actions.md | 79 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 docs/approved-actions.md diff --git a/README.md b/README.md index 3f34c48..6c2d5b8 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ This repo is part of the Sparkgeo GitHub Actions security programme. The pillars | Pillar | Issue | Summary | |---|---|---| | Workflow authoring standards | #25 | SHA pinning policy; `actionlint`/`zizmor` gate | -| Supply chain hardening | #26 | Org allowlist; dependency locking | +| Supply chain hardening | #26 | Org allowlist; dependency locking; [approved actions](docs/approved-actions.md) | | OIDC & secret federation | #27 | No static credentials; OIDC for cloud auth; environment-scoped secrets | | Runner egress control | #28 | `harden-runner` audit → block; self-hosted runner isolation policy | | Enterprise governance & observability | #29 | Org rulesets; OpenSSF Scorecard; audit log → SIEM | diff --git a/docs/approved-actions.md b/docs/approved-actions.md new file mode 100644 index 0000000..a2f35a1 --- /dev/null +++ b/docs/approved-actions.md @@ -0,0 +1,79 @@ +# Approved Third-Party GitHub Actions + +This document is the authoritative list of approved external actions for use across the Sparkgeo GitHub organisation. Any action not on this list requires a security review before use (see [Adding a new action](#adding-a-new-action)). + +## Org-level allowlist policy + +The GitHub organisation is configured to allow only: + +- Actions created by GitHub (`github_owned_allowed: true`) +- Actions on the approved list below (`allowed_actions: selected`) +- Verified Marketplace creators are **not** automatically allowed (`verified_allowed: false`) + +This is enforced at: **Org Settings → Actions → General → Allow selected actions**. + +## Approved actions + +| Action | Publisher | Current pinned version | Used in | Purpose | Review date | +|---|---|---|---|---|---| +| `actions/checkout` | GitHub (org-owned) | `de0fac2e` (v6.0.2) | all composite actions | Checkout repo contents | 2026-05-21 | +| `actions/upload-artifact` | GitHub (org-owned) | `043fb46d` (v7.0.1) | `scorecard` | Upload SARIF as retained artifact | 2026-05-21 | +| `actions/dependency-review-action` | GitHub (org-owned) | `a1d282b3` (v5.0.0) | `dependency-review` | Block PRs with vulnerable/denied-license deps | 2026-05-21 | +| `github/codeql-action/upload-sarif` | GitHub (org-owned) | `9e0d7b8d` (v4.35.5) | `scorecard`, `zizmor` | Upload SARIF to GitHub Security tab | 2026-05-21 | +| `ossf/scorecard-action` | OpenSSF | `4eaacf05` (v2.4.3) | `scorecard` | OpenSSF Scorecard supply-chain checks | 2026-05-21 | +| `reviewdog/action-actionlint` | reviewdog | `6fb7acc9` (v1.72.0) | `github-actionlint` | Actionlint via reviewdog; posts Check annotations | 2026-05-21 | +| `zizmorcore/zizmor-action` | zizmorcore | `5f14fd08` (v0.5.6) | `zizmor` | Zizmor static security analysis; uploads SARIF | 2026-05-21 | +| `opentofu/setup-opentofu` | OpenTofu | `847eaa4a` (v2.0.1) | `terramate-opentofu-setup` | Install OpenTofu CLI | 2026-05-21 | +| `terramate-io/terramate-action` | Terramate | `c5a13758` (v3.0.0) | `terramate-opentofu-setup` | Install Terramate CLI | 2026-05-21 | + +## Security review criteria + +Before approving a new action, verify all of the following: + +``` +[ ] Publisher is the canonical owner of the project (not a fork or impersonator) +[ ] Action is actively maintained — last commit within 12 months, issues responded to +[ ] Source code is publicly auditable — action.yml does not pull opaque binaries without checksum +[ ] Minimum required permissions — does not request write access it does not need +[ ] No outbound network calls to non-registry endpoints (check action source for curl/wget) +[ ] Pin to a commit SHA, not a mutable tag — confirm SHA matches the intended tag +[ ] Add SHA and version to this table; add publisher pattern to org allowlist if new publisher +``` + +## Adding a new action + +1. Open a PR adding the action to a composite action or workflow. +2. Complete the security review checklist above. +3. Add a row to the table above with the pinned SHA, version, and review date. +4. If the publisher is new, add the publisher pattern to the org allowlist via: + ```bash + # Append to selected_actions_allowed in org Actions permissions + gh api --method PUT orgs/sparkgeo/actions/permissions/selected-actions \ + --input - <<'EOF' + { + "github_owned_allowed": true, + "verified_allowed": false, + "patterns_allowed": [ + "ossf/*", "reviewdog/*", "zizmorcore/*", "opentofu/*", "terramate-io/*", + "/*" + ] + } + EOF + ``` +5. CODEOWNERS enforces that `.github/` changes require `@sparkgeo/security-team` review. + +## Renovate SHA update policy + +Renovate (issue #8) is configured with `pinDigests: true` for the `github-actions` manager. When a new version of an approved action is released, Renovate opens a PR that updates both the SHA and the inline version comment. Do not update SHAs manually — let Renovate handle it. The only exception is an emergency security patch: update immediately, then update this table's review date. + +## Org allowlist patterns (current) + +``` +ossf/* +reviewdog/* +zizmorcore/* +opentofu/* +terramate-io/* +``` + +GitHub-owned actions (`actions/*`, `github/*`) are covered by `github_owned_allowed: true` and do not need explicit patterns.