|
1 | | -### Required actions to scan GitHub action workflows for security issues. |
2 | | -name: 'Scan GitHub Action workflows files for security issues' |
| 1 | +name: 'Google GitHub Admin: Actions Workflow Security Scan' |
| 2 | + |
3 | 3 | on: |
4 | | - pull_request: {} |
| 4 | + pull_request: |
| 5 | + paths: |
| 6 | + - '.github/workflows/**/*.yml' |
| 7 | + - '.github/workflows/**/*.yaml' |
| 8 | + - '.github/actions/**/*.yml' |
| 9 | + - '.github/actions/**/*.yaml' |
| 10 | +env: |
| 11 | + ACTIONS_SUITE_CONTENT: | |
| 12 | + - qlpack: codeql/actions-queries |
| 13 | + - include: |
| 14 | + id: actions/envvar-injection/critical |
| 15 | + - include: |
| 16 | + id: actions/envpath-injection/critical |
| 17 | + - include: |
| 18 | + id: actions/cache-poisoning/poisonable-step |
| 19 | + - include: |
| 20 | + id: actions/artifact-poisoning/critical |
| 21 | + - include: |
| 22 | + id: actions/untrusted-checkout/critical |
| 23 | + - include: |
| 24 | + id: actions/untrusted-checkout/high |
| 25 | +
|
5 | 26 | permissions: |
6 | 27 | contents: 'read' |
7 | | - security-events: 'write' |
8 | | - actions: 'read' |
| 28 | + actions: 'write' # Upload artifact |
| 29 | + |
9 | 30 | jobs: |
10 | | - semgrep: |
11 | | - name: 'semgrep-oss/scan' |
| 31 | + scan-pr: |
| 32 | + permissions: |
| 33 | + contents: 'read' |
| 34 | + if: "github.event_name == 'pull_request'" |
12 | 35 | runs-on: 'ubuntu-latest' |
13 | | - container: |
14 | | - image: 'index.docker.io/semgrep/semgrep@sha256:85782eaf09692e6dfb684cd3bad87ef315775814b01f76b4d15582e4ca7c1c89' # ratchet:semgrep/semgrep |
15 | | - # Skip any PR created by dependabot to avoid permission issues: |
16 | | - if: (github.actor != 'dependabot[bot]') |
17 | 36 | steps: |
18 | | - - name: 'Checkout Code' |
19 | | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ratchet:actions/checkout@v4 |
20 | | - - name: 'Checkout Workflow Config' |
21 | | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ratchet:actions/checkout@v4 |
22 | | - env: |
23 | | - GH_REPO_OWNER: ${{ github.repository_owner }} |
24 | | - with: |
25 | | - repository: 'google/github-team' |
26 | | - path: action_scanning |
27 | | - - name: 'Run Actions semgrep scan' |
28 | | - run: 'semgrep scan --sarif --config action_scanning/semgrep-rules --config "p/github-actions" |
29 | | - --sarif-output semgrep-results-actions.sarif || true' |
30 | | - - name: 'Save Actions SARIF results as artifact' |
31 | | - uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4 |
32 | | - with: |
33 | | - name: 'semgrep-scan-results-actions' |
34 | | - path: 'semgrep-results-actions.sarif' |
35 | | - - name: 'Upload Actions SARIF result to the GitHub Security Dashboard' |
36 | | - uses: 'github/codeql-action/upload-sarif@1b549b9259bda1cb5ddde3b41741a82a2d15a841' # ratchet:github/codeql-action/upload-sarif@v3 |
37 | | - with: |
38 | | - sarif_file: 'semgrep-results-actions.sarif' |
39 | | - if: always() |
| 37 | + - name: 'Checkout PR Code' |
| 38 | + uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 |
| 39 | + |
| 40 | + - name: 'Check for Workflow Files' |
| 41 | + id: 'check_files' |
| 42 | + run: | |
| 43 | + FOUND_FILES=$(find . -type f -regextype posix-extended -regex '\./\.github/(workflows|actions)/.*\.ya?ml' | head -n 1) |
| 44 | + if [ -n "$FOUND_FILES" ]; then |
| 45 | + echo "workflow_files_found=true" >> "$GITHUB_OUTPUT" |
| 46 | + else |
| 47 | + echo "workflow_files_found=false" >> "$GITHUB_OUTPUT" |
| 48 | + fi |
| 49 | +
|
| 50 | + - name: 'Create CodeQL Query Suite' |
| 51 | + if: "steps.check_files.outputs.workflow_files_found == 'true'" |
| 52 | + run: 'echo "${{ env.ACTIONS_SUITE_CONTENT }}" > actions-suite.qls' |
| 53 | + |
| 54 | + - name: 'Initialize CodeQL' |
| 55 | + if: "steps.check_files.outputs.workflow_files_found == 'true'" |
| 56 | + uses: 'google/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db' # ratchet:google/codeql-action/init@v4 |
| 57 | + with: |
| 58 | + languages: 'actions' |
| 59 | + config: | |
| 60 | + name: 'Custom Action Scan' |
| 61 | + disable-default-queries: true |
| 62 | + queries: |
| 63 | + - uses: ./actions-suite.qls |
| 64 | +
|
| 65 | + - name: 'Perform CodeQL Analysis' |
| 66 | + if: "steps.check_files.outputs.workflow_files_found == 'true'" |
| 67 | + id: 'codeql_analysis' |
| 68 | + uses: 'google/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db' # ratchet:google/codeql-action/analyze@v4 |
| 69 | + with: |
| 70 | + upload: 'never' |
| 71 | + |
| 72 | + - name: 'Check for Vulnerabilities and Set Status' |
| 73 | + id: 'vuln_check' |
| 74 | + if: "steps.check_files.outputs.workflow_files_found == 'true'" |
| 75 | + run: | |
| 76 | + SARIF_FILE="${{ steps.codeql_analysis.outputs.sarif-output }}/actions.sarif" |
| 77 | + if [ ! -f "$SARIF_FILE" ]; then |
| 78 | + echo "SARIF file not found at $SARIF_FILE" |
| 79 | + exit 1 |
| 80 | + fi |
| 81 | + RESULT_COUNT=$(jq '.runs[0].results | length' "$SARIF_FILE") |
| 82 | + if [ "$RESULT_COUNT" -gt 0 ]; then |
| 83 | + echo "::error::CodeQL found $RESULT_COUNT potential vulnerabilities." |
| 84 | + echo "---" |
| 85 | + 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" |
| 86 | + exit 1 |
| 87 | + else |
| 88 | + echo "No vulnerabilities found. Check passed." |
| 89 | + fi |
| 90 | +
|
| 91 | + - name: 'Upload SARIF file on failure' |
| 92 | + if: "failure() && steps.vuln_check.conclusion == 'failure'" |
| 93 | + uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4 |
| 94 | + with: |
| 95 | + name: 'sarif-report' |
| 96 | + path: '${{ steps.codeql_analysis.outputs.sarif-output }}/actions.sarif' |
| 97 | + retention-days: 1 |
| 98 | + overwrite: 'true' |
0 commit comments