Skip to content

Commit eab50cd

Browse files
author
FZ2000
committed
ci: fix a pin that could not resolve, and aim CodeQL at the real attack surface
Acting on the CI-hardening research. Four changes, one of which is a bug I shipped two commits ago. dependency-review-action@v4 does not exist. That action publishes no floating `v4` major tag — only v4.7.3 ... v4.9.0 specifics and v5.0.0 — so the reference could never resolve. It never surfaced because the job is gated off while the repo is private, which is exactly how a broken pin survives until the moment you need it. Now @v5. The other actions were three majors behind: checkout, setup-python and upload-artifact are all on v7. Bumped, and CI is what verifies that — the upload-artifact step is already continue-on-error, so the riskiest of the three cannot redden a build. CodeQL now analyses `actions` as well as `python`, one job per language. This is the change with the most value per line. The runtime here is stdlib-only with a single outbound API; the CI is where a public repo is actually attacked, and CodeQL's actions queries cover exactly that — workflow script injection, checkout of untrusted code in a privileged context, cache and artifact poisoning, unpinned action tags. Autobuild is skipped for `actions`, which analyses YAML and has nothing to build. Also switched the suite from security-and-quality to security-extended. security-and-quality adds roughly 122 Python code-quality queries that ruff already enforces, and it disables CodeQL's autofix validation; the redundancy was not buying anything. The `category` is now per-language, which CodeQL requires once a repo uploads more than one SARIF result. I got this wrong on the first attempt — referenced `matrix.language` with no matrix defined — and actionlint did not catch it, because an undefined matrix context is a runtime failure rather than a syntax error. Renovate gains `helpers:pinGitHubActionDigests` rather than me pinning SHAs by hand, deliberately. A pin needs its `# vX.Y.Z` comment to keep being updated: since 2026-04-07 Renovate disables bare-SHA pins by default because it cannot tell what branch or tag the SHA belongs to, so a hand-written bare pin goes stale silently and forever. Renovate writes the comment and maintains it. Worth knowing the tradeoff it buys into: Dependabot does not raise vulnerability alerts for SHA-pinned actions, only for semver ones, so the pins have to be kept current by Renovate rather than by alerting. Also worth recording what SHA pinning does NOT fix, since it is easy to over-trust: it locks the outer reference only. A composite action that internally does `uses: some/action@v1` still resolves a mutable tag the caller cannot reach — that is how the reviewdog compromise propagated to consumers "regardless of version or pinning method". It is a further argument for the existing pattern of curling a pinned CLI (gitleaks, trufflehog, lychee, markdownlint) instead of adopting third-party actions: a binary has no transitive action graph. CITATION.cff: `authors: [{alias: FZ2000}]` is schema-legal but produces no printable author, so GitHub's "Cite this repository" panel rendered an entry with nobody in it. Added a `name`. Also removed `date-released: 2026-04-26`, which asserted a release that has never existed — `git tag` is empty, and CHANGELOG.md and PUBLISHING.md both already say so.
1 parent 4ed410f commit eab50cd

5 files changed

Lines changed: 65 additions & 43 deletions

File tree

.github/workflows/ci.yml

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ jobs:
6969
lint:
7070
runs-on: ubuntu-latest
7171
steps:
72-
- uses: actions/checkout@v4
73-
- uses: actions/setup-python@v5
72+
- uses: actions/checkout@v7
73+
- uses: actions/setup-python@v7
7474
with:
7575
python-version: "3.13"
7676
- name: Install dev deps
@@ -114,8 +114,8 @@ jobs:
114114
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
115115
runs-on: ubuntu-latest
116116
steps:
117-
- uses: actions/checkout@v4
118-
- uses: actions/setup-python@v5
117+
- uses: actions/checkout@v7
118+
- uses: actions/setup-python@v7
119119
with:
120120
python-version: ${{ matrix.python-version }}
121121
- name: Install dev deps
@@ -135,7 +135,7 @@ jobs:
135135
# v4: v3 was retired 2025-01-30 and now fails outright. The old
136136
# v3 pin existed only because Gitea's artifact server implemented
137137
# the v3 API and rejected v4 with GHESNotSupportedError.
138-
uses: actions/upload-artifact@v4
138+
uses: actions/upload-artifact@v7
139139
# Never fail the build on this. The coverage GATE is enforced inside
140140
# the test step by pytest-cov's --cov-fail-under, so by the time we
141141
# get here the number has already passed or failed; this upload only
@@ -153,8 +153,8 @@ jobs:
153153
integration:
154154
runs-on: ubuntu-latest
155155
steps:
156-
- uses: actions/checkout@v4
157-
- uses: actions/setup-python@v5
156+
- uses: actions/checkout@v7
157+
- uses: actions/setup-python@v7
158158
with:
159159
python-version: "3.13"
160160
- name: Install dev deps
@@ -175,8 +175,8 @@ jobs:
175175
smoke:
176176
runs-on: ubuntu-latest
177177
steps:
178-
- uses: actions/checkout@v4
179-
- uses: actions/setup-python@v5
178+
- uses: actions/checkout@v7
179+
- uses: actions/setup-python@v7
180180
with:
181181
python-version: "3.13"
182182
- name: Run `da --version` (stdlib-only — no deps needed)
@@ -230,8 +230,8 @@ jobs:
230230
runs-on: ubuntu-latest
231231
needs: test
232232
steps:
233-
- uses: actions/checkout@v4
234-
- uses: actions/setup-python@v5
233+
- uses: actions/checkout@v7
234+
- uses: actions/setup-python@v7
235235
with:
236236
python-version: "3.13"
237237

@@ -322,7 +322,7 @@ jobs:
322322
secret-scan:
323323
runs-on: ubuntu-latest
324324
steps:
325-
- uses: actions/checkout@v4
325+
- uses: actions/checkout@v7
326326
with:
327327
# gitleaks needs full history to scan past commits, not just HEAD.
328328
fetch-depth: 0
@@ -354,7 +354,7 @@ jobs:
354354
# reportable finding, which gates CI.
355355
runs-on: ubuntu-latest
356356
steps:
357-
- uses: actions/checkout@v4
357+
- uses: actions/checkout@v7
358358
with:
359359
fetch-depth: 0
360360
- name: TruffleHog (verified/unknown findings only)
@@ -370,7 +370,7 @@ jobs:
370370
codespell:
371371
runs-on: ubuntu-latest
372372
steps:
373-
- uses: actions/checkout@v4
373+
- uses: actions/checkout@v7
374374
- name: Install codespell
375375
# Into a venv: the runner image's system Python is externally
376376
# managed (PEP 668) and rejects a bare `pip install`.
@@ -398,8 +398,8 @@ jobs:
398398
runs-on: ubuntu-latest
399399
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
400400
steps:
401-
- uses: actions/checkout@v4
402-
- uses: actions/setup-python@v5
401+
- uses: actions/checkout@v7
402+
- uses: actions/setup-python@v7
403403
with:
404404
python-version: "3.13"
405405
- name: Install dev deps
@@ -422,7 +422,7 @@ jobs:
422422
# so "configured" and "enforced" mean the same thing.
423423
runs-on: ubuntu-latest
424424
steps:
425-
- uses: actions/checkout@v4
425+
- uses: actions/checkout@v7
426426
- name: markdownlint
427427
# Pinned to the same version .pre-commit-config.yaml uses, so
428428
# local and CI agree. Newer versions add rules (e.g. MD060) that
@@ -435,7 +435,7 @@ jobs:
435435
# covered by link-check-external below.
436436
runs-on: ubuntu-latest
437437
steps:
438-
- uses: actions/checkout@v4
438+
- uses: actions/checkout@v7
439439
- name: Install lychee
440440
run: |
441441
case "$(uname -m)" in
@@ -455,7 +455,7 @@ jobs:
455455
runs-on: ubuntu-latest
456456
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
457457
steps:
458-
- uses: actions/checkout@v4
458+
- uses: actions/checkout@v7
459459
- name: Install lychee
460460
run: |
461461
case "$(uname -m)" in
@@ -473,8 +473,8 @@ jobs:
473473
# what actually catches a DA response-shape change.
474474
runs-on: ubuntu-latest
475475
steps:
476-
- uses: actions/checkout@v4
477-
- uses: actions/setup-python@v5
476+
- uses: actions/checkout@v7
477+
- uses: actions/setup-python@v7
478478
with:
479479
python-version: "3.13"
480480
- name: Install dev + integration deps
@@ -518,8 +518,8 @@ jobs:
518518
runs-on: ubuntu-latest
519519
needs: test # don't bother if unit tests are broken
520520
steps:
521-
- uses: actions/checkout@v4
522-
- uses: actions/setup-python@v5
521+
- uses: actions/checkout@v7
522+
- uses: actions/setup-python@v7
523523
with:
524524
python-version: "3.13"
525525
- name: Install dev + integration deps
@@ -548,12 +548,12 @@ jobs:
548548
runs-on: ubuntu-latest
549549
timeout-minutes: 5
550550
steps:
551-
- uses: actions/checkout@v4
551+
- uses: actions/checkout@v7
552552
with:
553553
# No token in the environment: this job runs no privileged step and
554554
# a checkout that leaves credentials behind is a needless one.
555555
persist-credentials: false
556-
- uses: actions/setup-python@v5
556+
- uses: actions/setup-python@v7
557557
with:
558558
python-version: "3.13"
559559
- name: Discoverability and metadata checks

.github/workflows/codeql.yml

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,37 +46,52 @@ concurrency:
4646

4747
jobs:
4848
analyze:
49-
name: Analyze Python
49+
name: Analyze ${{ matrix.language }}
5050
# Skips entirely while the repo is private. See the header.
5151
if: ${{ github.event.repository.private == false }}
5252
runs-on: ubuntu-latest
53+
strategy:
54+
# One job per language so a failure names the language, and so the
55+
# `category` below is distinct per analysis — CodeQL requires that when
56+
# a repo uploads more than one SARIF result.
57+
fail-fast: false
58+
matrix:
59+
language: [python, actions]
5360
permissions:
5461
# Raised above the workflow default: the upload step writes results
5562
# to the Security tab. `contents: read` alone is not enough.
5663
security-events: write
5764
contents: read
5865

5966
steps:
60-
- uses: actions/checkout@v4
67+
- uses: actions/checkout@v7
6168

6269
- name: Initialize CodeQL
6370
uses: github/codeql-action/init@v3
6471
with:
65-
languages: python
66-
# security-and-quality is a superset of the default suite. The
67-
# extra quality queries are noisier, but this is a small codebase
68-
# with a 92% coverage gate — the signal is worth the noise.
69-
queries: security-and-quality
72+
# `actions` matters at least as much as `python` here: the runtime
73+
# is stdlib-only, while the CI is where a public repo is actually
74+
# attacked. Its queries cover workflow script injection, checkout of
75+
# untrusted code in a privileged context, cache/artifact poisoning
76+
# and unpinned action tags.
77+
languages: ${{ matrix.language }}
78+
# security-extended, not security-and-quality: the latter adds
79+
# ~122 Python code-quality queries that ruff already enforces,
80+
# and it disables CodeQL's autofix validation. Extended keeps the
81+
# security queries without the redundancy.
82+
queries: security-extended
7083

7184
- name: Autobuild
72-
# Python needs no compilation; autobuild resolves the import graph
73-
# so CodeQL can follow calls across dacli/ submodules. That matters
74-
# here because the package deliberately re-exports names and reads
75-
# them back through the package at call time (see ADR 0007), which
76-
# a file-at-a-time scan would not connect.
85+
# Python needs no compilation, but autobuild resolves the import graph
86+
# so CodeQL can follow calls across dacli/ submodules — which matters
87+
# because the package deliberately re-exports names and reads them
88+
# back through the package at call time (ADR 0007), something a
89+
# file-at-a-time scan would not connect. Skipped for `actions`, which
90+
# analyses YAML and has nothing to build.
91+
if: matrix.language == 'python'
7792
uses: github/codeql-action/autobuild@v3
7893

7994
- name: Perform CodeQL Analysis
8095
uses: github/codeql-action/analyze@v3
8196
with:
82-
category: "/language:python"
97+
category: "/language:${{ matrix.language }}"

.github/workflows/dependency-review.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ jobs:
4040
# burying the result in the job log.
4141
pull-requests: write
4242
steps:
43-
- uses: actions/checkout@v4
44-
- uses: actions/dependency-review-action@v4
43+
- uses: actions/checkout@v7
44+
- # @v5, not @v4: this action publishes no `v4` floating major tag,
45+
# only v4.x.y specifics and v5.0.0, so `@v4` fails to resolve.
46+
uses: actions/dependency-review-action@v5
4547
with:
4648
# Fail on a moderate finding rather than the default `low`. The
4749
# dev graph is large and low-severity advisories in test tooling

CITATION.cff

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ abstract: >-
77
to a local destination directory, and exposes search / browse / metadata
88
helpers.
99
authors:
10-
- alias: FZ2000
10+
- name: "FZ2000"
11+
alias: FZ2000
1112
type: software
1213
license: MIT
1314
version: "0.3.0"
14-
date-released: "2026-04-26"
15+
# date-released is deliberately omitted: it is not a required CFF field
16+
# and no release has ever been tagged (`git tag` is empty), so a date
17+
# here would assert a release that does not exist. Add it with the
18+
# first real tag — see CONTRIBUTING.md#releases.
1519
repository-code: "https://github.com/FZ2000/da-cli"
1620
url: "https://github.com/FZ2000/da-cli"
1721
keywords:

renovate.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
{
22
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
3-
"extends": ["config:recommended"],
3+
"extends": ["config:recommended", "helpers:pinGitHubActionDigests"],
44
"schedule": ["before 6am on Monday"],
55
"labels": ["dependencies"],
66
"commitMessagePrefix": "chore(deps)",
77
"commitMessageTopic": "{{depName}}",
88
"prConcurrentLimit": 5,
99
"prHourlyLimit": 2,
1010
"rangeStrategy": "bump",
11+
"configMigration": true,
1112
"pre-commit": {
1213
"enabled": true
1314
},

0 commit comments

Comments
 (0)