From 25c41de3fc2520440f087cff0a85fcc7182a2c5b Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Tue, 12 May 2026 12:41:34 -0400 Subject: [PATCH 1/3] Add PSScriptAnalyzer and OpenGrep security workflows Adds two new code scanning workflows to close gaps identified in the open Scorecard SAST alert and the lack of PowerShell static analysis: - psscriptanalyzer.yml (active): Runs PSScriptAnalyzer on push/PR to main when PowerShell files (*.ps1, *.psm1, *.psd1, *.ps1xml) change, on a weekly schedule, and on demand. CodeQL does not support PowerShell, so this fills the gap for the module's core code under powershell/, tools/, and build/. - opengrep.yml (manual eval only): Runs OpenGrep (LGPL fork of Semgrep CE) via workflow_dispatch with configurable rule packs and version. Workflow is disabled from automatic triggers while we evaluate signal vs. noise; switching to Semgrep is a one-line change if needed (rule-compatible). Both workflows follow existing repo conventions: SHA-pinned actions with version comments, 'permissions: {}' default-deny with per-job grants, and concurrency groups. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/opengrep.yml | 83 ++++++++++++++++++++++++++ .github/workflows/psscriptanalyzer.yml | 59 ++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 .github/workflows/opengrep.yml create mode 100644 .github/workflows/psscriptanalyzer.yml diff --git a/.github/workflows/opengrep.yml b/.github/workflows/opengrep.yml new file mode 100644 index 000000000..c38581731 --- /dev/null +++ b/.github/workflows/opengrep.yml @@ -0,0 +1,83 @@ +name: "OpenGrep" + +# OpenGrep is an LGPL 2.1 fork of Semgrep CE, backed by a consortium of +# AppSec vendors. It is rule-compatible with Semgrep, so existing rule packs +# (p/security-audit, p/javascript, p/typescript, p/github-actions) work +# unchanged. See https://opengrep.dev for project details. +# +# This workflow currently runs on workflow_dispatch only while we evaluate +# the tool's effectiveness for the Maester codebase. If results are valuable, +# add push/pull_request triggers in a follow-up PR. + +permissions: {} + +on: + workflow_dispatch: + inputs: + opengrep_version: + description: 'OpenGrep release tag to install (e.g. v1.21.0). Leave blank for latest.' + required: false + default: 'v1.21.0' + config: + description: 'Rule config(s) to run (space- or comma-separated).' + required: false + default: 'p/security-audit p/javascript p/typescript p/github-actions' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + scan: + name: Scan with OpenGrep + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + actions: read + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install OpenGrep + env: + OPENGREP_VERSION: ${{ inputs.opengrep_version }} + run: | + set -euo pipefail + if [ -n "${OPENGREP_VERSION}" ]; then + echo "Installing OpenGrep ${OPENGREP_VERSION}" + curl -fsSL https://raw.githubusercontent.com/opengrep/opengrep/main/install.sh \ + | bash -s -- --version "${OPENGREP_VERSION}" + else + echo "Installing latest OpenGrep" + curl -fsSL https://raw.githubusercontent.com/opengrep/opengrep/main/install.sh | bash + fi + opengrep --version + + - name: Run OpenGrep scan + env: + OPENGREP_CONFIG: ${{ inputs.config }} + run: | + set -euo pipefail + # Build --config flags from the space/comma-separated input + CONFIG_ARGS="" + for cfg in $(echo "${OPENGREP_CONFIG}" | tr ',' ' '); do + CONFIG_ARGS="${CONFIG_ARGS} --config ${cfg}" + done + opengrep scan \ + ${CONFIG_ARGS} \ + --sarif \ + --sarif-output=results.sarif \ + --error \ + --no-suppress-errors \ + . || true + # `|| true` keeps the workflow green during evaluation phase so the + # SARIF upload step still runs and findings are visible in the + # Security tab. Remove this once we are confident in the ruleset. + + - name: Upload SARIF results + uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 + with: + sarif_file: results.sarif + category: opengrep diff --git a/.github/workflows/psscriptanalyzer.yml b/.github/workflows/psscriptanalyzer.yml new file mode 100644 index 000000000..bc7adb3b3 --- /dev/null +++ b/.github/workflows/psscriptanalyzer.yml @@ -0,0 +1,59 @@ +name: "PSScriptAnalyzer" + +permissions: {} + +on: + push: + branches: [ "main" ] + paths: + - '**/*.ps1' + - '**/*.psm1' + - '**/*.psd1' + - '**/*.ps1xml' + - '.github/workflows/psscriptanalyzer.yml' + pull_request: + branches: [ "main" ] + paths: + - '**/*.ps1' + - '**/*.psm1' + - '**/*.psd1' + - '**/*.ps1xml' + - '.github/workflows/psscriptanalyzer.yml' + schedule: + - cron: '28 13 * * 2' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + analyze: + name: Analyze PowerShell + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + actions: read + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + # Scans the entire repository recursively. PSScriptAnalyzer only + # analyzes files with PowerShell extensions (.ps1, .psm1, .psd1, .ps1xml), + # so non-PowerShell content under powershell/, tools/, build/, etc. is + # automatically ignored. This avoids per-directory invocations and + # transparently picks up any future PowerShell files added to the repo. + - name: Run PSScriptAnalyzer + uses: microsoft/psscriptanalyzer-action@6b2948b1944407914a58661c49941824d149734f # v1.1 + with: + path: .\ + recurse: true + output: results.sarif + + - name: Upload SARIF results + uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 + with: + sarif_file: results.sarif + category: psscriptanalyzer From f71de275f90825b8ae1a38d427fb412830e0393b Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Tue, 12 May 2026 13:51:42 -0400 Subject: [PATCH 2/3] Address Copilot PR review feedback on security workflows - psscriptanalyzer.yml: use POSIX path '.' (not '.\') since job runs on ubuntu-latest - opengrep.yml: replace 'curl | bash' installer with pinned release binary download from GitHub Releases (better supply-chain integrity) - opengrep.yml: use bash array for --config args (avoid word-splitting) - opengrep.yml: replace '|| true' with continue-on-error + if: always() and hashFiles guard on SARIF upload Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/opengrep.yml | 39 +++++++++++++++----------- .github/workflows/psscriptanalyzer.yml | 2 +- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/.github/workflows/opengrep.yml b/.github/workflows/opengrep.yml index c38581731..21b92bf9b 100644 --- a/.github/workflows/opengrep.yml +++ b/.github/workflows/opengrep.yml @@ -40,43 +40,48 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + # Install OpenGrep by downloading the signed release binary directly + # from GitHub Releases (pinned by tag). Avoids piping a moving + # `install.sh` into bash, which would weaken supply-chain integrity. - name: Install OpenGrep env: OPENGREP_VERSION: ${{ inputs.opengrep_version }} run: | set -euo pipefail - if [ -n "${OPENGREP_VERSION}" ]; then - echo "Installing OpenGrep ${OPENGREP_VERSION}" - curl -fsSL https://raw.githubusercontent.com/opengrep/opengrep/main/install.sh \ - | bash -s -- --version "${OPENGREP_VERSION}" - else - echo "Installing latest OpenGrep" - curl -fsSL https://raw.githubusercontent.com/opengrep/opengrep/main/install.sh | bash - fi + version="${OPENGREP_VERSION:-v1.21.0}" + asset="opengrep_manylinux_x86" + base="https://github.com/opengrep/opengrep/releases/download/${version}" + echo "Downloading OpenGrep ${version} (${asset})" + curl -fsSL --retry 3 -o /tmp/opengrep "${base}/${asset}" + chmod +x /tmp/opengrep + sudo mv /tmp/opengrep /usr/local/bin/opengrep opengrep --version - name: Run OpenGrep scan + id: scan + continue-on-error: true env: OPENGREP_CONFIG: ${{ inputs.config }} run: | set -euo pipefail - # Build --config flags from the space/comma-separated input - CONFIG_ARGS="" - for cfg in $(echo "${OPENGREP_CONFIG}" | tr ',' ' '); do - CONFIG_ARGS="${CONFIG_ARGS} --config ${cfg}" + # Build a bash array of --config flags from the space/comma-separated + # input so values are passed as discrete argv entries (no + # word-splitting surprises). + args=() + normalized="${OPENGREP_CONFIG//,/ }" + for cfg in ${normalized}; do + args+=(--config "${cfg}") done opengrep scan \ - ${CONFIG_ARGS} \ + "${args[@]}" \ --sarif \ --sarif-output=results.sarif \ --error \ --no-suppress-errors \ - . || true - # `|| true` keeps the workflow green during evaluation phase so the - # SARIF upload step still runs and findings are visible in the - # Security tab. Remove this once we are confident in the ruleset. + . - name: Upload SARIF results + if: always() && hashFiles('results.sarif') != '' uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: sarif_file: results.sarif diff --git a/.github/workflows/psscriptanalyzer.yml b/.github/workflows/psscriptanalyzer.yml index bc7adb3b3..3eb1d0316 100644 --- a/.github/workflows/psscriptanalyzer.yml +++ b/.github/workflows/psscriptanalyzer.yml @@ -48,7 +48,7 @@ jobs: - name: Run PSScriptAnalyzer uses: microsoft/psscriptanalyzer-action@6b2948b1944407914a58661c49941824d149734f # v1.1 with: - path: .\ + path: . recurse: true output: results.sarif From e4e5f8c5043d5d454e51c13dacc53e5e9ff606c8 Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Tue, 12 May 2026 14:06:57 -0400 Subject: [PATCH 3/3] Address fresh Copilot review: clarify version input and verify SHA256 - Update opengrep_version description to reflect the pinned default rather than incorrectly suggesting that a blank value installs latest. - Add opengrep_sha256 input and a built-in checksum for the default v1.21.0 release. The install step now verifies the downloaded binary with sha256sum before executing it, and refuses to install an overridden version unless the caller also supplies a matching SHA256. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/opengrep.yml | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/opengrep.yml b/.github/workflows/opengrep.yml index 21b92bf9b..ec13254c8 100644 --- a/.github/workflows/opengrep.yml +++ b/.github/workflows/opengrep.yml @@ -15,9 +15,13 @@ on: workflow_dispatch: inputs: opengrep_version: - description: 'OpenGrep release tag to install (e.g. v1.21.0). Leave blank for latest.' + description: 'OpenGrep release tag to install. Defaults to the pinned, checksum-verified version below; override only if you also provide a matching opengrep_sha256.' required: false default: 'v1.21.0' + opengrep_sha256: + description: 'SHA256 of opengrep_manylinux_x86 for the chosen version. Leave blank to use the built-in checksum for the default version.' + required: false + default: '' config: description: 'Rule config(s) to run (space- or comma-separated).' required: false @@ -40,19 +44,35 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - # Install OpenGrep by downloading the signed release binary directly - # from GitHub Releases (pinned by tag). Avoids piping a moving - # `install.sh` into bash, which would weaken supply-chain integrity. + # Install OpenGrep by downloading the release binary directly from + # GitHub Releases (pinned by tag) and verifying its SHA256 before + # executing it. Avoids piping a moving `install.sh` into bash and + # closes the integrity gap left by trusting a mutable release tag. - name: Install OpenGrep env: OPENGREP_VERSION: ${{ inputs.opengrep_version }} + OPENGREP_SHA256_INPUT: ${{ inputs.opengrep_sha256 }} + # Pinned checksum for opengrep_manylinux_x86 at the default version + # (v1.21.0). Update both the version default above and this value + # in lockstep when bumping the pinned release. + DEFAULT_OPENGREP_VERSION: 'v1.21.0' + DEFAULT_OPENGREP_SHA256: '9ed0ceee4a3a406d27d40894bcce85ea151be21e6d4b180689689224faff2a3e' run: | set -euo pipefail - version="${OPENGREP_VERSION:-v1.21.0}" + version="${OPENGREP_VERSION:-${DEFAULT_OPENGREP_VERSION}}" + expected_sha="${OPENGREP_SHA256_INPUT}" + if [[ -z "${expected_sha}" ]]; then + if [[ "${version}" != "${DEFAULT_OPENGREP_VERSION}" ]]; then + echo "::error::Custom opengrep_version '${version}' supplied without opengrep_sha256. Refusing to install an unverified binary." >&2 + exit 1 + fi + expected_sha="${DEFAULT_OPENGREP_SHA256}" + fi asset="opengrep_manylinux_x86" base="https://github.com/opengrep/opengrep/releases/download/${version}" echo "Downloading OpenGrep ${version} (${asset})" curl -fsSL --retry 3 -o /tmp/opengrep "${base}/${asset}" + echo "${expected_sha} /tmp/opengrep" | sha256sum --check --status chmod +x /tmp/opengrep sudo mv /tmp/opengrep /usr/local/bin/opengrep opengrep --version