diff --git a/.github/workflows/action-security-scan.yaml b/.github/workflows/action-security-scan.yaml new file mode 100644 index 00000000..181041c3 --- /dev/null +++ b/.github/workflows/action-security-scan.yaml @@ -0,0 +1,94 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Runs the same CodeQL `actions` security query suite as Alphabet's Enterprise +# `Google GitHub Admin: Actions Workflow Security Scan` workflow on stacked PRs +# and backport PRs (`base != default_branch`), which the Enterprise ruleset +# does not trigger on unless the PR targets `main` (or belongs to a native +# GitHub Stack targeting `main`). + +name: Actions Workflow Security Scan +on: + pull_request: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + scan-pr: + name: Scan workflow files (stacked & backport PRs) + runs-on: ubuntu-latest + timeout-minutes: 10 + if: github.base_ref != github.event.repository.default_branch + steps: + - name: Checkout .github directory + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 1 + sparse-checkout: .github + persist-credentials: false + + - name: Create CodeQL query suite + run: | + cat <<'EOF' > actions-suite.qls + - qlpack: codeql/actions-queries + - include: + id: actions/envvar-injection/critical + - include: + id: actions/envpath-injection/critical + - include: + id: actions/cache-poisoning/poisonable-step + - include: + id: actions/artifact-poisoning/critical + - include: + id: actions/untrusted-checkout/critical + - include: + id: actions/untrusted-checkout/high + EOF + + - name: Initialize CodeQL + uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 + with: + languages: actions + config: | + name: "Custom Action Scan" + disable-default-queries: true + queries: + - uses: ./actions-suite.qls + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 + with: + output: ../results + upload: never + + - name: Check for vulnerabilities + run: | + SARIF_FILE="${RUNNER_WORKSPACE}/results/actions.sarif" + if [[ ! -f "$SARIF_FILE" ]]; then + echo "::error::SARIF file not found at ${SARIF_FILE}" + exit 1 + fi + RESULT_COUNT=$(jq '.runs[0].results | length' "$SARIF_FILE") + if [[ "$RESULT_COUNT" -gt 0 ]]; then + echo "::error::CodeQL found ${RESULT_COUNT} potential workflow security vulnerabilities." + echo "---" + jq -r '.runs[0].results[] | ("Rule ID: " + .ruleId + "\nMessage: " + .message.text + "\nFile: " + .locations[0].physicalLocation.artifactLocation.uri + "\nLine: " + (.locations[0].physicalLocation.region.startLine | tostring) + "\n---")' "$SARIF_FILE" + exit 1 + fi + echo "No vulnerabilities found. Check passed."