Repository navigation
244 lines (228 loc) · 12.2 KB
/
Copy pathcodeql.yml
File metadata and controls
244 lines (228 loc) · 12.2 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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
name: CodeQL
on:
push:
branches: [main]
pull_request:
# Required for merge-queue entries: queued runs report checks via merge_group.
merge_group:
types: [checks_requested]
workflow_dispatch:
inputs:
public_pool:
description: Canary read-only public analysis on main without enabling the rollout
type: boolean
default: false
schedule:
- cron: "0 6 * * 1" # Weekly Monday 6am UTC
permissions:
actions: read
contents: read
concurrency:
# Cancel superseded first-attempt PR runs. Non-PR runs and manual reruns get
# run-unique groups so a pending or newer run is never cancelled or replaced.
# Pinned by tests/integration/ci/workflow-concurrency.test.ts; see cicd.yml and
# veryfront-studio#5544.
group: ${{ github.event_name == 'pull_request' && github.run_attempt == '1' && format('{0}-{1}', github.workflow, github.ref) || format('{0}-{1}', github.workflow, github.run_id) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
# Enable only after this workflow and the pool's codeql job are on main.
# The called workflow is the runner group's trusted, main-pinned definition.
# Only eligible public runs use the 18m scan; other cohorts keep hosted 25m analysis.
public-analysis:
if: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.repository == 'veryfront/veryfront-code' && vars.CI_RUNNER_PUBLIC == 'veryfront-public' && ((github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && inputs.public_pool == true) || (vars.CI_CODEQL_PUBLIC == 'enabled' && (github.event_name == 'merge_group' || ((github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.user.login != 'dependabot[bot]' && github.actor != 'dependabot[bot]')))) }}
name: Public CodeQL analysis
permissions:
contents: read
uses: veryfront/veryfront-code/.github/workflows/ci-public-pool.yml@main
with:
job: codeql
analyze:
needs: [public-analysis]
if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
# Public scanning (18m) plus hosted upload (5m) leaves 7m for scheduling
# within the queue's 30m check-response window. Hosted-only analysis keeps
# its existing budget when the public scan is skipped.
timeout-minutes: ${{ needs.public-analysis.result == 'skipped' && 25 || 5 }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
id: checkout
continue-on-error: true
with:
persist-credentials: false
- name: Require successful public analysis
id: public-result
if: ${{ needs.public-analysis.result != 'skipped' }}
continue-on-error: true
env:
PUBLIC_ANALYSIS_RESULT: ${{ needs.public-analysis.result }}
PUBLIC_SOURCE_ROOT: ${{ needs.public-analysis.outputs.codeql-source-root }}
PUBLIC_ARTIFACT_NAME: ${{ needs.public-analysis.outputs.codeql-artifact-name }}
run: |
if [ "$PUBLIC_ANALYSIS_RESULT" != "success" ]; then
echo "::error::Public CodeQL analysis finished with ${PUBLIC_ANALYSIS_RESULT}"
exit 1
fi
case "$PUBLIC_SOURCE_ROOT" in
/*) ;;
*) echo "::error::Public CodeQL source root is unavailable"; exit 1 ;;
esac
if [[ ! "$PUBLIC_ARTIFACT_NAME" =~ ^codeql-sarif-${GITHUB_RUN_ID}-[1-9][0-9]*$ ]]; then
echo "::error::Public CodeQL artifact identity is unavailable"
exit 1
fi
- uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
if: ${{ needs.public-analysis.result == 'skipped' && steps.checkout.outcome == 'success' }}
with:
languages: javascript-typescript
queries: security-and-quality
# A merge-queue batch can dissolve while this job is still running. GitHub
# then deletes the ephemeral gh-readonly-queue ref, and the SARIF upload —
# which starts after the analysis itself has already
# succeeded — fails with "ref ... not found in this repository". Nothing is
# being merged at that point, so the run is moot rather than failing, but
# the queue reads the red check as the PR's fault and ejects it.
#
# github/codeql-action offers no supported way to express "this run is
# moot" (github/codeql-action#1572, open since March 2023 with no fix), so
# the outcome is captured here and re-raised by the guard below.
#
# What the guard actually tolerates, stated plainly: ANY analyze failure on
# a merge_group run whose target ref has provably gone away, or has moved
# to a different SHA. That is broader than the upload 404 that motivated it
# — an extractor crash or a query failure landing in the same window is
# swallowed too. It is safe because check runs are bound to a SHA: if the
# queue branch is gone or has moved, nothing can be merged on the strength
# of this run, and the rebuilt entry re-runs CodeQL from scratch. Every
# other case fails the build — a non-merge_group event, a ref still live at
# our SHA, an unparseable response, or any indeterminate API status.
#
# ASSUMPTION — the merge queue's merge_method is SQUASH, which mints a
# fresh SHA for every rebuild, so a moot run's SHA can never recur on a
# later live entry. Under REBASE a no-op rebase could reproduce the same
# SHA, and this run's moot-green could then satisfy a live entry. If the
# queue's merge method ever changes, revisit this guard first.
- id: analyze
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
if: ${{ needs.public-analysis.result == 'skipped' && steps.checkout.outcome == 'success' }}
continue-on-error: true
with:
category: "/language:javascript-typescript"
- name: Download public analysis results
id: public-download
if: ${{ needs.public-analysis.result == 'success' && steps.public-result.outcome == 'success' && steps.checkout.outcome == 'success' }}
continue-on-error: true
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: ${{ needs.public-analysis.outputs.codeql-artifact-name }}
path: dist/codeql-results
- name: Upload public analysis to code scanning
id: public-upload
if: ${{ needs.public-analysis.result == 'success' && steps.public-download.outcome == 'success' }}
continue-on-error: true
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
sarif_file: dist/codeql-results
category: "/language:javascript-typescript"
checkout_path: ${{ needs.public-analysis.outputs.codeql-source-root }}
ref: ${{ github.ref }}
sha: ${{ github.sha }}
- name: Re-raise CodeQL failure unless the target ref is gone
if: ${{ steps.checkout.outcome == 'failure' || steps.analyze.outcome == 'failure' || steps.public-result.outcome == 'failure' || steps.public-download.outcome == 'failure' || steps.public-upload.outcome == 'failure' }}
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
TARGET_REF: ${{ github.ref }}
TARGET_SHA: ${{ github.sha }}
run: |
set -euo pipefail
# Only merge-queue refs are ephemeral. Every other event keeps its ref,
# so a CodeQL failure there is always the build's problem.
if [ "$EVENT_NAME" != "merge_group" ]; then
echo "::error::CodeQL failed on a ${EVENT_NAME} run. Failing the build."
exit 1
fi
body="$(mktemp)"
trap 'rm -f "$body"' EXIT
# This turns a red required check green, so every tolerated path leaves
# an auditable line in the job summary, not only in the raw log.
summary="${GITHUB_STEP_SUMMARY:-/dev/null}"
# curl already emits 000 through -w on a transport failure, so no
# shell-side fallback is wanted: `|| echo 000` would append a second
# sentinel and yield the literal string 000000. The explicit assignment
# below keeps the sentinel exactly three zeroes.
#
# The request is bounded and retried. An unbounded hang against
# api.github.com would hold the job open past the queue's
# check_response_timeout and eject the PR — the exact failure this
# guard exists to prevent. Worst case here is ~2 minutes.
if ! status="$(curl -sS \
--connect-timeout 10 \
--max-time 30 \
--retry 3 \
--retry-delay 2 \
--retry-all-errors \
-o "$body" -w '%{http_code}' \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/git/ref/${TARGET_REF#refs/}")"; then
status="000"
fi
case "$status" in
404)
echo "::notice::${TARGET_REF} no longer exists: the merge-queue batch dissolved" \
"while CodeQL was uploading. Nothing is being merged from this run, so its" \
"result cannot gate anything. Treating it as moot."
{
echo "### CodeQL failure tolerated as moot"
echo
echo "A failing CodeQL run was reported as successful."
echo
echo "- Target ref: \`${TARGET_REF}\`"
echo "- Run SHA: \`${TARGET_SHA}\`"
echo "- Reason: the ref returned HTTP 404 — the merge-queue batch dissolved while this job was running, so this run cannot gate any merge."
} >> "$summary"
;;
200)
# A 200 whose body carries no object SHA leaves us unable to tell a
# live ref from a dead one. That must fail closed: without this
# check an empty value compares unequal to TARGET_SHA and falls
# into the tolerant "batch was rebuilt" path below.
if ! current="$(jq -r '.object.sha // empty' "$body")"; then
current=""
fi
if [ -z "$current" ]; then
echo "::error::${TARGET_REF} returned HTTP 200 but the response carried no object SHA," \
"so whether this run is still live cannot be determined. Failing closed."
exit 1
fi
if [ "$current" = "$TARGET_SHA" ]; then
echo "::error::${TARGET_REF} still points at ${TARGET_SHA}, so this run is live." \
"The CodeQL failure is genuine. Failing the build."
exit 1
fi
echo "::notice::${TARGET_REF} has moved from ${TARGET_SHA} to ${current}: the batch" \
"was rebuilt without this run. Its result cannot gate the new head. Treating it as moot."
{
echo "### CodeQL failure tolerated as moot"
echo
echo "A failing CodeQL run was reported as successful."
echo
echo "- Target ref: \`${TARGET_REF}\`"
echo "- Run SHA: \`${TARGET_SHA}\`"
echo "- Ref now at: \`${current}\`"
echo "- Reason: the batch was rebuilt without this run, so this run cannot gate the new head."
} >> "$summary"
;;
*)
echo "::error::Could not determine whether ${TARGET_REF} still exists (HTTP ${status};" \
"000 means the request failed at the transport level). Failing closed."
exit 1
;;
esac