-
Notifications
You must be signed in to change notification settings - Fork 0
156 lines (147 loc) · 6.8 KB
/
Copy pathcodeql.yml
File metadata and controls
156 lines (147 loc) · 6.8 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
# SPDX-License-Identifier: Apache-2.0
#
# CodeQL SAST for Flowatch (JS / JSX). Standalone workflow — independent
# of ci.yml so Dependabot can bump github/codeql-action without churning
# the four required-check contexts that branch protection pins by name
# (.github/protection/required_checks.json). The job name
# `Analyze (javascript-typescript)` IS in required_checks.json as of the
# 2026-05-17 quality-gates tightening — the bake-in period has concluded.
# A post-analysis `Gate on alerts ≥ medium severity` step polls the Code
# Scanning API for this ref and fails the job if any open alert has
# security_severity_level >= medium. Renaming the job (or this step)
# silently breaks branch protection.
#
# Triggers:
# - push to main / develop → catch regressions on the trunk branches
# - PR to main / develop → surface findings before merge
# - weekly cron → catch new CVEs in CodeQL packs themselves
# - workflow_dispatch → on-demand re-run
#
# Reproduce locally: `make codeql` (uses the `gh codeql` CLI extension
# and writes codeql.sarif to the repo root; both are gitignored).
#
# All third-party actions are pinned to a full-length commit SHA per
# NFR-26. Dependabot's `github-actions` ecosystem (.github/dependabot.yml)
# bumps the SHAs weekly and keeps the trailing `# vX.Y.Z` comment in
# sync — never strip that comment, it documents the human-readable
# version pin.
name: CodeQL
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
schedule:
# Monday 06:17 UTC — offset from the top of the hour to dodge the
# GitHub Actions cron-rush at :00.
- cron: "17 6 * * 1"
workflow_dispatch: {}
permissions:
contents: read
concurrency:
group: codeql-${{ github.ref }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
security-events: write # upload SARIF to the Security tab
contents: read
actions: read # required for CodeQL to read workflow metadata
strategy:
fail-fast: false
matrix:
# `javascript-typescript` is the combined pack (CodeQL >= 2.16) — one
# entry covers .js, .jsx, and any future .ts/.tsx without re-wiring
# the matrix. Per CLAUDE.md the project is JS/JSX only, but the
# extractor doesn't care about absence of .ts files.
language: [javascript-typescript]
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Initialize CodeQL
uses: github/codeql-action/init@f411752efdf656cb71aa17b755b22c890960da1d # v3.35.5
with:
languages: ${{ matrix.language }}
# `security-extended` is richer than the GitHub default suite —
# Flowatch's source surface is small enough to triage the extra
# findings. Downgrade to `default` only if signal-to-noise
# proves untenable; do NOT silently drop queries with a custom
# config until that decision is on the record.
queries: security-extended
- name: Perform CodeQL Analysis
id: analyze
uses: github/codeql-action/analyze@f411752efdf656cb71aa17b755b22c890960da1d # v3.35.5
with:
category: "/language:${{ matrix.language }}"
# Severity gate. The CodeQL action uploads SARIF to the Security
# tab but does NOT fail the job on findings — that's our policy
# decision. We poll the Code Scanning API for analyses tied to
# this commit, then count open alerts at security_severity_level
# in {medium, high, critical} and fail the job if any exist.
#
# Threshold MEDIUM matches the cleanup bar the 2026-05-17 quality-
# gates tightening set. Raising it to HIGH would let medium-grade
# smells (e.g. js/missing-origin-check, js/indirect-command-line-
# injection) accumulate again; lowering to LOW pulls in too much
# noise. Adjust here if the policy moves — also update
# docs/repo-settings.md §"CodeQL & Trivy".
#
# On PRs from forks GITHUB_TOKEN is read-only and the Code
# Scanning API may not be queryable for the head ref. The step
# tolerates this via `|| echo 0` fallbacks; in that case the
# workflow effectively becomes advisory for the fork PR. Trusted
# PRs (same-repo branches) get the full gate.
- name: Gate on alerts ≥ medium severity
if: github.event_name == 'push' || github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REF: ${{ github.event.pull_request.head.sha || github.sha }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
# Wait up to 5 min for SARIF processing to surface an analysis
# for this exact commit. Without this, the alerts API may
# return stale state from a parent commit.
for i in $(seq 1 30); do
analyses=$(gh api "/repos/${REPO}/code-scanning/analyses?tool_name=CodeQL&per_page=20" \
--jq "[.[] | select(.commit_sha==\"${REF}\")] | length" 2>/dev/null || echo 0)
if [ "$analyses" -gt 0 ]; then
echo "✓ CodeQL analysis indexed for ${REF} after ${i} poll(s)"
break
fi
sleep 10
done
blocking=0
for sev in medium high critical; do
n=$(gh api "/repos/${REPO}/code-scanning/alerts?state=open&ref=${REF}&severity=${sev}" \
--jq "length" 2>/dev/null || echo 0)
blocking=$((blocking + n))
done
{
echo "### CodeQL severity gate"
echo ""
echo "- Ref: \`${REF}\`"
echo "- Threshold: severity ≥ medium"
echo "- Open blocking alerts: **${blocking}**"
} >> "$GITHUB_STEP_SUMMARY"
if [ "$blocking" -gt 0 ]; then
echo "::error::CodeQL gate failed — ${blocking} open alert(s) at severity ≥ medium."
gh api "/repos/${REPO}/code-scanning/alerts?state=open&ref=${REF}" \
--jq '.[] | " - #\(.number) [\(.rule.security_severity_level // .rule.severity)] \(.rule.id) at \(.most_recent_instance.location.path):\(.most_recent_instance.location.start_line)"' \
| tee -a "$GITHUB_STEP_SUMMARY"
exit 1
fi
echo "✓ No open alerts at severity ≥ medium on ${REF}."
- name: Summary
if: always()
run: |
{
echo "### CodeQL"
echo ""
echo "- Language: ${{ matrix.language }}"
echo "- Suite: security-extended"
echo "- Outcome: ${{ steps.analyze.outcome }}"
echo "- Findings: see the Security tab → Code scanning alerts (category /language:${{ matrix.language }})"
} >> "$GITHUB_STEP_SUMMARY"