Repository navigation
183 lines (155 loc) · 6.51 KB
/
Copy pathcodeql.yml
File metadata and controls
183 lines (155 loc) · 6.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
name: CodeQL - Analyze
on:
pull_request:
branches: [main, nightly, development]
push:
branches: [main, nightly, development]
workflow_dispatch:
schedule:
- cron: '0 3 * * 1' # Mondays 03:00 UTC
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true
env:
GOTOOLCHAIN: auto
permissions:
contents: read
security-events: write
actions: read
pull-requests: read
jobs:
analyze:
name: CodeQL analysis (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
actions: read
pull-requests: read
strategy:
fail-fast: false
matrix:
language: [ 'go', 'javascript-typescript' ]
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@1d88384fd173af6ecb012dfd0264a306b711241a # v2.22.4
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
# Use github.ref (full ref path) instead of github.ref_name:
# - push/schedule: resolves to refs/heads/<branch>, checking out latest HEAD
# - pull_request: resolves to refs/pull/<n>/merge, the correct PR merge ref
# github.ref_name fails for PRs because it yields "<n>/merge" which checkout
# interprets as a branch name (refs/heads/<n>/merge) that does not exist.
ref: ${{ github.ref }}
- name: Verify CodeQL parity guard
if: matrix.language == 'go'
run: bash scripts/ci/check-codeql-parity.sh
- name: Initialize CodeQL
uses: github/codeql-action/init@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3
with:
languages: ${{ matrix.language }}
queries: security-and-quality
# Use CodeQL config to exclude documented false positives
# Go: Excludes go/request-forgery for url_testing.go (has 4-layer SSRF defense)
# See: .github/codeql/codeql-config.yml for full justification
config-file: ./.github/codeql/codeql-config.yml
- name: Setup Go
if: matrix.language == 'go'
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: backend/go.mod
cache-dependency-path: backend/go.sum
- name: Verify Go toolchain and build
if: matrix.language == 'go'
run: |
set -euo pipefail
cd backend
go version
MOD_GO_VERSION="$(awk '/^go / {print $2; exit}' go.mod)"
ACTIVE_GO_VERSION="$(go env GOVERSION | sed 's/^go//')"
case "$ACTIVE_GO_VERSION" in
"$MOD_GO_VERSION"|"$MOD_GO_VERSION".*)
;;
*)
echo "::error::Go toolchain mismatch: go.mod requires ${MOD_GO_VERSION}, active is ${ACTIVE_GO_VERSION}"
exit 1
;;
esac
go build ./...
- name: Prepare SARIF output directory
run: mkdir -p sarif-results
- name: Autobuild
uses: github/codeql-action/autobuild@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3
- name: Perform CodeQL Analysis
id: codeql_analyze
uses: github/codeql-action/analyze@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3
with:
category: "/language:${{ matrix.language }}"
output: sarif-results/${{ matrix.language }}
- name: Check CodeQL Results
if: always() && steps.codeql_analyze.conclusion != 'skipped'
run: |
set -euo pipefail
SARIF_DIR="sarif-results/${{ matrix.language }}"
if [ ! -d "$SARIF_DIR" ]; then
echo "::error::Expected SARIF output directory is missing: $SARIF_DIR"
echo "❌ **ERROR:** SARIF output directory is missing: $SARIF_DIR" >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
SARIF_FILE="$(find "$SARIF_DIR" -maxdepth 1 -type f -name '*.sarif' | head -n 1 || true)"
{
echo "## 🔒 CodeQL Security Analysis Results"
echo ""
echo "**Language:** ${{ matrix.language }}"
echo "**Query Suite:** security-and-quality"
echo ""
} >> "$GITHUB_STEP_SUMMARY"
if [ -z "$SARIF_FILE" ] || [ ! -r "$SARIF_FILE" ]; then
echo "::error::Expected SARIF file is missing or unreadable: $SARIF_FILE"
echo "❌ **ERROR:** SARIF file is missing or unreadable: $SARIF_FILE" >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
echo "Found SARIF file: $SARIF_FILE"
{
echo "## 🔒 CodeQL Findings Gate"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
set +e
bash scripts/security/codeql-findings-gate.sh "$SARIF_FILE" "${{ matrix.language }}" | tee -a "$GITHUB_STEP_SUMMARY"
GATE_EXIT=${PIPESTATUS[0]}
set -e
{
echo '```'
if [ "$GATE_EXIT" -gt 0 ]; then
echo "❌ **BLOCKING:** see findings above (blocking enforcement happens in the next step)"
else
echo "✅ No blocking CodeQL issues found"
fi
echo ""
echo "View full results in the [Security tab](https://github.com/${{ github.repository }}/security/code-scanning)"
} >> "$GITHUB_STEP_SUMMARY"
# Reporting only here — blocking happens in the next step. Do not
# fail this step on a non-zero gate exit.
exit 0
- name: Fail on High-Severity Findings
if: always() && steps.codeql_analyze.conclusion != 'skipped'
run: |
set -euo pipefail
SARIF_DIR="sarif-results/${{ matrix.language }}"
if [ ! -d "$SARIF_DIR" ]; then
echo "::error::Expected SARIF output directory is missing: $SARIF_DIR"
exit 1
fi
SARIF_FILE="$(find "$SARIF_DIR" -maxdepth 1 -type f -name '*.sarif' | head -n 1 || true)"
if [ -z "$SARIF_FILE" ] || [ ! -r "$SARIF_FILE" ]; then
echo "::error::Expected SARIF file is missing or unreadable: $SARIF_FILE"
exit 1
fi
if ! bash scripts/security/codeql-findings-gate.sh "$SARIF_FILE" "${{ matrix.language }}" >/dev/null; then
echo "::error::CodeQL found blocking findings. Fix before merging, or add a documented exception. Policy: .github/security-severity-policy.yml"
exit 1
fi