From 2a0fbb51cdb586c3d58ddbba1ac3c9d6925ca1d9 Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Wed, 30 Sep 2026 15:42:23 -0400 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=94=92=20security(ci):=20harden=20wor?= =?UTF-8?q?kflow=20credentials,=20permissions,=20and=20execution=20control?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Set persist-credentials: false on the GitGuardian, MegaLinter, and PSScriptAnalyzer checkouts; none of these jobs push back to the repository. - Add per-ref concurrency to GitGuardian with cancel-in-progress: false so queued incremental secret scans still cover every pushed commit range. - Add conservative finite job timeouts (20/45/20 minutes). - Add an explicit job-level contents: read permission to the GitGuardian job. - Replace MegaLinter's blanket DISABLE_ERRORS: true with ENABLE_ERRORS_LINTERS: ACTION_ACTIONLINT so findings remain reported while only a verified-clean linter gates the build. Refs #18 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/GitGuardian.yml | 10 ++++++++++ .github/workflows/MegaLinter.yml | 11 ++++++++--- .github/workflows/PSScriptAnalyzer.yml | 3 +++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/GitGuardian.yml b/.github/workflows/GitGuardian.yml index c7293a5..3a2d820 100644 --- a/.github/workflows/GitGuardian.yml +++ b/.github/workflows/GitGuardian.yml @@ -9,10 +9,19 @@ on: permissions: contents: read +# Serialize runs per ref without cancelling: every pushed commit range must still +# be scanned, so queued runs are allowed to finish instead of being superseded. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: scanning: name: GitGuardian Scan runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 @@ -23,6 +32,7 @@ jobs: uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 with: fetch-depth: 0 # fetch all history so multiple commits can be scanned + persist-credentials: false # scan-only job; no git write-back is performed - name: GitGuardian Scan uses: GitGuardian/ggshield/actions/secret@e4f45829b9b6f4664fe70d2a4dcd307a6833f422 # v1.43.0 env: diff --git a/.github/workflows/MegaLinter.yml b/.github/workflows/MegaLinter.yml index 3ecbcfc..ec153b4 100644 --- a/.github/workflows/MegaLinter.yml +++ b/.github/workflows/MegaLinter.yml @@ -26,6 +26,7 @@ jobs: megalinter: name: MegaLinter runs-on: ubuntu-latest + timeout-minutes: 45 permissions: contents: read @@ -44,6 +45,7 @@ jobs: with: # Pull requests need history for diff linting; main pushes validate all code. fetch-depth: ${{ github.event_name == 'pull_request' && '0' || '1' }} + persist-credentials: false # lint-only job; no git write-back is performed # MegaLinter - name: MegaLinter @@ -83,10 +85,13 @@ jobs: # Uncomment to disable copy-paste and spell checks # DISABLE: COPYPASTE,SPELL - DISABLE_ERRORS: true + # Quality gate policy: MegaLinter still reports every finding, but only the + # linters listed in ENABLE_ERRORS_LINTERS fail the build. ACTION_ACTIONLINT + # is enforced because the workflow files it covers are verified clean today. + # Remaining linters stay advisory until a clean baseline is established for + # them; see https://github.com/SamErde/PowerShell/issues/18. + ENABLE_ERRORS_LINTERS: ACTION_ACTIONLINT DISABLE_LINTERS: SPELL_LYCHEE - # Uncomment DISABLE_ERRORS_LINTERS if you want to turn errors back on selectively. - # DISABLE_ERRORS_LINTERS: REPOSITORY_DEVSKIM,REPOSITORY_KICS,REPOSITORY_CHECKOV,POWERSHELL_POWERSHELL,SPELL_CSPELL # Upload MegaLinter artifacts - name: Archive production artifacts diff --git a/.github/workflows/PSScriptAnalyzer.yml b/.github/workflows/PSScriptAnalyzer.yml index e6edd36..403cdf8 100644 --- a/.github/workflows/PSScriptAnalyzer.yml +++ b/.github/workflows/PSScriptAnalyzer.yml @@ -25,6 +25,7 @@ jobs: actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status name: 🕵️‍♂️ PSScriptAnalyzer runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 @@ -32,6 +33,8 @@ jobs: egress-policy: audit - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + with: + persist-credentials: false # analysis-only job; no git write-back is performed - name: 🕵️‍♂️ Run PSScriptAnalyzer uses: microsoft/psscriptanalyzer-action@6b2948b1944407914a58661c49941824d149734f From 49453729dcdc2dad3c62f635bee4327b7a7eb0da Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Wed, 30 Sep 2026 15:48:34 -0400 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=94=92=20security(ci):=20drop=20GitGu?= =?UTF-8?q?ardian=20concurrency=20to=20preserve=20scan=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub Actions retains only one pending run per concurrency group, so a third rapid push evicts the second run even when cancel-in-progress is false. Since each ggshield run scans only its own github.event.before -> head range, the evicted range would never be scanned. Removes the grouping and documents the tradeoff in the workflow. Addresses Codex review feedback on #19. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/GitGuardian.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/GitGuardian.yml b/.github/workflows/GitGuardian.yml index 3a2d820..619581e 100644 --- a/.github/workflows/GitGuardian.yml +++ b/.github/workflows/GitGuardian.yml @@ -9,11 +9,11 @@ on: permissions: contents: read -# Serialize runs per ref without cancelling: every pushed commit range must still -# be scanned, so queued runs are allowed to finish instead of being superseded. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false +# No concurrency group is defined on purpose. Each run scans only its own +# github.event.before -> head push range, and GitHub retains a single pending run +# per concurrency group: a third rapid push would evict the second run's range +# even with cancel-in-progress: false, leaving those commits unscanned. Runner +# time is traded for complete secret-scan coverage. jobs: scanning: From d29d4a1556f0a6f00d0e1d80e979d93396d735ea Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:01:17 -0400 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=93=9A=20docs(ci):=20correct=20GitGua?= =?UTF-8?q?rdian=20scan-range=20comment=20(Refs=20#18)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ggshield v1.43.0 reads GITHUB_PUSH_BASE_SHA (not github.event.before) and falls back to GITHUB_DEFAULT_BRANCH, then GITHUB_SHA~1... Verified against the pinned action source. Comment only; no behavior change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/GitGuardian.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/GitGuardian.yml b/.github/workflows/GitGuardian.yml index 619581e..b72684e 100644 --- a/.github/workflows/GitGuardian.yml +++ b/.github/workflows/GitGuardian.yml @@ -9,11 +9,14 @@ on: permissions: contents: read -# No concurrency group is defined on purpose. Each run scans only its own -# github.event.before -> head push range, and GitHub retains a single pending run -# per concurrency group: a third rapid push would evict the second run's range -# even with cancel-in-progress: false, leaving those commits unscanned. Runner -# time is traded for complete secret-scan coverage. +# No concurrency group is defined on purpose. For push and workflow_dispatch +# runs alike, ggshield selects GITHUB_PUSH_BASE_SHA..GITHUB_SHA, falling back to +# GITHUB_DEFAULT_BRANCH..GITHUB_SHA when the push base is empty and then to +# GITHUB_SHA~1... when that range yields no commits. On the default branch that +# last fallback covers only the head commit, so a run is not guaranteed to +# re-cover an earlier run's commits. GitHub retains a single pending run per +# concurrency group, so a third rapid push would evict the second run even with +# cancel-in-progress: false. Runner time is traded for complete scan coverage. jobs: scanning: From 1ba34e96fdb51ef7a232d60b388e60ec1da791f2 Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:08:19 -0400 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=94=92=20security(ci):=20remove=20pub?= =?UTF-8?q?lic-repo=20SARIF=20actions=20permission=20(Refs=20#18)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/PSScriptAnalyzer.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/PSScriptAnalyzer.yml b/.github/workflows/PSScriptAnalyzer.yml index 403cdf8..418af71 100644 --- a/.github/workflows/PSScriptAnalyzer.yml +++ b/.github/workflows/PSScriptAnalyzer.yml @@ -22,7 +22,6 @@ jobs: permissions: contents: read # for actions/checkout to fetch code security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status name: 🕵️‍♂️ PSScriptAnalyzer runs-on: ubuntu-latest timeout-minutes: 20