From 7985ef4653aa5d08ca1fa91106a451907b8ba4a9 Mon Sep 17 00:00:00 2001 From: jmeridth Date: Sat, 20 Jun 2026 03:23:13 -0500 Subject: [PATCH] ci: harden GHA workflows with least-privilege permissions, concurrency, and action updates ## What/Why Apply GHA security and operational standards across all 9 workflows: least-privilege permissions, proper concurrency groups, SHA-pinned actions, harden-runner coverage, and .yaml extension consistency. ## Proof it works actionlint passes with zero errors on all 9 workflow files. ## Risk + AI role low -- CI-only changes, no application code affected. Fully AI-generated (Claude Opus 4.6). actions/checkout v6->v7 is the largest change; v7 blocks fork PR checkouts on pull_request_target and workflow_run triggers, which does not affect these workflows. ## Review focus - actions/checkout v7 upgrade: confirm no workflow relies on checking out fork PR code via workflow_run - concurrency groups: verify cancel-in-progress conditions match each workflow's trigger mix Signed-off-by: jmeridth --- .github/workflows/auto-labeler.yaml | 13 +++++++++---- .../workflows/{gh-pages.yml => gh-pages.yaml} | 9 ++++----- .../workflows/{htmltest.yml => htmltest.yaml} | 11 ++++++----- ...n-ready.yml => mark-ready-when-ready.yaml} | 13 ++++++------- .../{markdownlint.yml => markdownlint.yaml} | 13 +++++++------ .../workflows/osps-security-assessment.yaml | 19 +++++++++++++------ .github/workflows/pr-title.yaml | 15 ++++++++++----- ...ty-insights.yml => security-insights.yaml} | 11 ++++++++--- .github/workflows/stale.yaml | 7 +++---- 9 files changed, 66 insertions(+), 45 deletions(-) rename .github/workflows/{gh-pages.yml => gh-pages.yaml} (85%) rename .github/workflows/{htmltest.yml => htmltest.yaml} (81%) rename .github/workflows/{mark-ready-when-ready.yml => mark-ready-when-ready.yaml} (76%) rename .github/workflows/{markdownlint.yml => markdownlint.yaml} (68%) rename .github/workflows/{security-insights.yml => security-insights.yaml} (69%) diff --git a/.github/workflows/auto-labeler.yaml b/.github/workflows/auto-labeler.yaml index 8a0d16b0..f3affca1 100644 --- a/.github/workflows/auto-labeler.yaml +++ b/.github/workflows/auto-labeler.yaml @@ -3,13 +3,18 @@ name: Auto Labeler on: pull_request: types: [opened, reopened, edited, synchronize] -permissions: - contents: read + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: main: permissions: - contents: read - pull-requests: write + contents: read # Required by reusable workflow + pull-requests: write # Apply labels to PRs uses: github-community-projects/ospo-reusable-workflows/.github/workflows/auto-labeler.yaml@6d7a83e6fc8275128984b0ed3defa4b8cdc40f85 with: config-name: release-drafter.yml diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yaml similarity index 85% rename from .github/workflows/gh-pages.yml rename to .github/workflows/gh-pages.yaml index f76889a5..5222cde1 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yaml @@ -7,17 +7,16 @@ on: - main workflow_dispatch: -permissions: - contents: read +permissions: {} concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: false jobs: deploy: permissions: - contents: write + contents: write # Push to gh-pages branch runs-on: ubuntu-latest steps: - name: Harden the runner @@ -25,7 +24,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: submodules: true fetch-depth: 0 diff --git a/.github/workflows/htmltest.yml b/.github/workflows/htmltest.yaml similarity index 81% rename from .github/workflows/htmltest.yml rename to .github/workflows/htmltest.yaml index 9a553321..6f101946 100644 --- a/.github/workflows/htmltest.yml +++ b/.github/workflows/htmltest.yaml @@ -11,15 +11,16 @@ on: - completed workflow_dispatch: -permissions: - contents: read +permissions: {} concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: htmltest: + permissions: + contents: read # Clone the repository runs-on: ubuntu-latest steps: - name: Harden the runner @@ -27,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: submodules: true fetch-depth: 0 diff --git a/.github/workflows/mark-ready-when-ready.yml b/.github/workflows/mark-ready-when-ready.yaml similarity index 76% rename from .github/workflows/mark-ready-when-ready.yml rename to .github/workflows/mark-ready-when-ready.yaml index 2230a3f7..344c770b 100644 --- a/.github/workflows/mark-ready-when-ready.yml +++ b/.github/workflows/mark-ready-when-ready.yaml @@ -5,11 +5,10 @@ on: pull_request: types: [opened, edited, labeled, unlabeled, synchronize] -permissions: - contents: read +permissions: {} concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} cancel-in-progress: true jobs: @@ -17,10 +16,10 @@ jobs: name: Mark as ready after successful checks runs-on: ubuntu-latest permissions: - checks: read - contents: write - pull-requests: write - statuses: read + checks: read # Read check suite results + contents: write # Update PR branch + pull-requests: write # Mark PR as ready for review + statuses: read # Read commit statuses if: | contains(github.event.pull_request.labels.*.name, 'Mark Ready When Ready') && github.event.pull_request.draft == true && diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yaml similarity index 68% rename from .github/workflows/markdownlint.yml rename to .github/workflows/markdownlint.yaml index adc0f62b..8440cbaf 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yaml @@ -13,15 +13,16 @@ on: paths: - "content/**/*.md" -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true +permissions: {} -permissions: - contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: lint: + permissions: + contents: read # Clone the repository runs-on: ubuntu-latest steps: - name: Harden the runner @@ -29,7 +30,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false diff --git a/.github/workflows/osps-security-assessment.yaml b/.github/workflows/osps-security-assessment.yaml index 6fb21306..1e4cf3d7 100644 --- a/.github/workflows/osps-security-assessment.yaml +++ b/.github/workflows/osps-security-assessment.yaml @@ -6,19 +6,26 @@ on: - cron: "0 9 * * 1" workflow_dispatch: # Allow manual triggering +permissions: {} + jobs: osps-assessment: runs-on: ubuntu-latest name: OSPS Security Assessment permissions: - contents: read - id-token: write - security-events: write # Required for SARIF upload + contents: read # Clone the repository + id-token: write # Federate via octo-sts + security-events: write # Upload SARIF results steps: + - name: Harden the runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false @@ -29,7 +36,7 @@ jobs: identity: jmeridth.github.io - name: Open Source Project Security Baseline Scanner - uses: revanite-io/osps-baseline-action@ace75cc1fb748be898275f16f59f78363405bc0a + uses: revanite-io/osps-baseline-action@ace75cc1fb748be898275f16f59f78363405bc0a # v1.3.3 with: owner: ${{ github.repository_owner }} repo: ${{ github.event.repository.name }} @@ -39,7 +46,7 @@ jobs: - name: Upload Assessment Results if: always() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: osps-assessment-results-${{ github.run_number }} path: evaluation_results/ diff --git a/.github/workflows/pr-title.yaml b/.github/workflows/pr-title.yaml index feb4aa89..42732bc1 100644 --- a/.github/workflows/pr-title.yaml +++ b/.github/workflows/pr-title.yaml @@ -4,14 +4,19 @@ name: "Lint PR Title" on: pull_request: types: [opened, reopened, edited, synchronize] -permissions: - contents: read + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: main: permissions: - contents: read - pull-requests: read - statuses: write + contents: read # Required by reusable workflow + pull-requests: read # Read PR title + statuses: write # Report lint status uses: github-community-projects/ospo-reusable-workflows/.github/workflows/pr-title.yaml@6d7a83e6fc8275128984b0ed3defa4b8cdc40f85 secrets: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/security-insights.yml b/.github/workflows/security-insights.yaml similarity index 69% rename from .github/workflows/security-insights.yml rename to .github/workflows/security-insights.yaml index 9d5146f2..95064c61 100644 --- a/.github/workflows/security-insights.yml +++ b/.github/workflows/security-insights.yaml @@ -13,11 +13,16 @@ on: paths: - ".github/security-insights.yml" -permissions: - contents: read +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: validate: + permissions: + contents: read # Clone the repository runs-on: ubuntu-latest steps: - name: Harden the runner @@ -25,7 +30,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 6021501e..cef4a1eb 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -3,15 +3,14 @@ on: schedule: - cron: "30 1 * * *" -permissions: - contents: read +permissions: {} jobs: stale: runs-on: ubuntu-latest permissions: - issues: write - pull-requests: read + issues: write # Label and close stale issues + pull-requests: read # Check PR activity steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4