Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Dependabot for the one dependency ecosystem this repo has: GitHub Actions.
# Dependabot covers the two ecosystems it can see here: GitHub Actions and pre-commit hooks.
# Every action is SHA-pinned (zizmor enforces hygiene); this keeps the pins patched —
# a bump PR lands when a pinned action ships a fix or a CVE.
# a bump PR lands when a pinned action ships a fix or a CVE. Everything installed by
# curl-with-checksum, pipx or a docker digest is invisible to Dependabot and is watched by
# .github/workflows/tool-bump.yml instead; XMRig by xmrig-bump.yml.
version: 2
updates:
- package-ecosystem: "github-actions"
Expand All @@ -9,3 +11,14 @@ updates:
interval: "weekly"
labels:
- "infra"
# .pre-commit-config.yaml pins hook repos by `rev:` and nothing watched them (#373):
# pre-commit-hooks sat a whole major behind. Dependabot supports this ecosystem natively, which
# beats hand-rolling it in tool-bump.yml. It also gives the gitleaks `rev:` a second opinion —
# tool-bump.yml keeps that one in lockstep with security.yml by sed, and two mechanisms agreeing
# is how you find out when one of them stops.
- package-ecosystem: "pre-commit"
directory: "/"
schedule:
interval: "weekly"
labels:
- "infra"
73 changes: 65 additions & 8 deletions .github/workflows/tool-bump.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
name: CI tool pin bump

# Self-healing pin hygiene, part 2 (#228): the CI tools installed by curl-with-checksum or docker
# digest — shellcheck, shfmt, gitleaks, kcov — are the pins Dependabot can't see. Weekly, each
# stale tool becomes its own bot PR with the version AND the recomputed checksum/digest. The
# dispatched checks on the branch are the verification: a shellcheck bump is linted BY the new
# shellcheck, a gitleaks bump scans with the new gitleaks, a kcov bump measures on the new image.
# Self-healing pin hygiene, part 2 (#228): the CI tools installed by curl-with-checksum, pipx or a
# docker digest are the pins Dependabot can't see. Weekly, each stale tool becomes its own bot PR
# with the version AND the recomputed checksum/digest. The dispatched checks on the branch are the
# verification: a shellcheck bump is linted BY the new shellcheck, a gitleaks bump scans with the
# new gitleaks, a kcov bump measures on the new image.
# Same posture as the xmrig bump (#225): bot proposes, CI gates, a human clicks merge.
#
# Watched here: shellcheck, shfmt, gitleaks, actionlint, lychee, jq, zizmor, diff-cover, kcov.
# Elsewhere: XMRig by xmrig-bump.yml, GitHub Actions and .pre-commit-config.yaml by Dependabot.
# That is every pin in the repo — which it was not before #373, when five of the six unwatched ones
# had gone stale (zizmor by four minors, and it is the tool that audits these very workflows). The
# pins with a watcher were the pins that were current; that is the whole argument for the list above
# being complete. If you add a pin anywhere, add it to one of those three places in the same change.
on:
schedule:
- cron: "30 6 * * 1" # Mondays 06:30 UTC, between the xmrig bump and the zizmor re-audit
Expand Down Expand Up @@ -46,11 +53,18 @@ jobs:
git checkout -q -
}

# <name> <upstream-repo> <current> -> prints the newer bare version, or nothing
# <name> <upstream-repo> <current> [tag-prefix] -> prints the newer bare version, or nothing
#
# Not every project tags `vX.Y.Z`: lychee tags `lychee-v0.24.2` and jq tags `jq-1.8.2`.
# Stripping only a leading "v" leaves those failing the shape check below, which returns 2
# and turns the whole run red every week on a repo whose pins are fine. The prefix is a
# per-tool fact, so it is a per-tool argument; the default keeps every existing caller
# byte-identical. The strict validation still runs AFTER the strip — normalising the
# spelling must not mean trusting the value (#373).
newer_release() {
local name="$1" repo="$2" current="$3" latest
local name="$1" repo="$2" current="$3" prefix="${4:-v}" latest
latest=$(gh api "repos/${repo}/releases/latest" --jq .tag_name)
latest="${latest#v}"
latest="${latest#"$prefix"}"
if ! printf '%s' "$latest" | grep -qE '^[0-9]+(\.[0-9]+)*$'; then
echo "::error::${name}: upstream tag '${latest}' failed validation" >&2
return 2 # distinct from the benign empty-output paths: the caller records a failure
Expand Down Expand Up @@ -97,6 +111,49 @@ jobs:
open_pr "bot/gitleaks-${new}" "chore(ci): bump gitleaks to ${new} (bot)" /tmp/body.md
else [ $? -gt 1 ] && failed=1; fi

# --- actionlint (ci.yml) ---
cur=$(grep -oE 'ACTIONLINT_VERSION: "[0-9.]+"' .github/workflows/ci.yml | grep -oE '[0-9.]+')
if new=$(newer_release actionlint rhysd/actionlint "$cur") && [ -n "$new" ]; then
sha=$(artifact_sha "https://github.com/rhysd/actionlint/releases/download/v${new}/actionlint_${new}_linux_amd64.tar.gz")
sed -i "s|ACTIONLINT_VERSION: \"${cur}\"|ACTIONLINT_VERSION: \"${new}\"|; s|ACTIONLINT_SHA256: \"[0-9a-f]*\"|ACTIONLINT_SHA256: \"${sha}\"|" .github/workflows/ci.yml
printf 'Automated pin bump (#373): actionlint %s -> %s, checksum recomputed from the release artifact.\n\nThe Lint check on this PR runs the NEW actionlint over these workflows — passing checks are the verification.\n' "$cur" "$new" >/tmp/body.md
open_pr "bot/actionlint-${new}" "chore(ci): bump actionlint to ${new} (bot)" /tmp/body.md
else [ $? -gt 1 ] && failed=1; fi

# --- lychee (links.yml). Tags as `lychee-vX.Y.Z`, hence the prefix argument. ---
cur=$(grep -oE 'LYCHEE_VERSION: "[0-9.]+"' .github/workflows/links.yml | grep -oE '[0-9.]+')
if new=$(newer_release lychee lycheeverse/lychee "$cur" "lychee-v") && [ -n "$new" ]; then
sha=$(artifact_sha "https://github.com/lycheeverse/lychee/releases/download/lychee-v${new}/lychee-x86_64-unknown-linux-gnu.tar.gz")
sed -i "s|LYCHEE_VERSION: \"${cur}\"|LYCHEE_VERSION: \"${new}\"|; s|LYCHEE_SHA256: \"[0-9a-f]*\"|LYCHEE_SHA256: \"${sha}\"|" .github/workflows/links.yml
printf 'Automated pin bump (#373): lychee %s -> %s, checksum recomputed.\n\nThe Links check on this PR runs the NEW lychee — passing checks are the verification.\n' "$cur" "$new" >/tmp/body.md
open_pr "bot/lychee-${new}" "chore(ci): bump lychee to ${new} (bot)" /tmp/body.md
else [ $? -gt 1 ] && failed=1; fi

# --- jq (tests/coverage.sh, static binary + checksum). Tags as `jq-X.Y.Z`. ---
cur=$(grep -oE 'download/jq-[0-9.]+' tests/coverage.sh | grep -oE '[0-9]+(\.[0-9]+)*')
if new=$(newer_release jq jqlang/jq "$cur" "jq-") && [ -n "$new" ]; then
sha=$(artifact_sha "https://github.com/jqlang/jq/releases/download/jq-${new}/jq-linux-amd64")
sed -i "s|download/jq-${cur}/|download/jq-${new}/|; s|JQ_SHA256=\"[0-9a-f]*\"|JQ_SHA256=\"${sha}\"|" tests/coverage.sh
printf 'Automated pin bump (#373): jq %s -> %s in tests/coverage.sh, checksum recomputed.\n\nThe Coverage check on this PR runs the suite with the NEW jq inside the kcov container — passing checks are the verification.\n' "$cur" "$new" >/tmp/body.md
open_pr "bot/jq-${new}" "chore(ci): bump the coverage jq to ${new} (bot)" /tmp/body.md
else [ $? -gt 1 ] && failed=1; fi

# --- zizmor (security.yml). pipx, so there is no artifact checksum to recompute. ---
cur=$(grep -oE 'ZIZMOR_VERSION: "[0-9.]+"' .github/workflows/security.yml | grep -oE '[0-9.]+')
if new=$(newer_release zizmor zizmorcore/zizmor "$cur") && [ -n "$new" ]; then
sed -i "s|ZIZMOR_VERSION: \"${cur}\"|ZIZMOR_VERSION: \"${new}\"|" .github/workflows/security.yml
printf 'Automated pin bump (#373): zizmor %s -> %s.\n\nzizmor is the workflow AUDITOR and it gates the merge, so a stale pin means auditing our own supply chain with missing rules. The Workflow audit check on this PR runs the NEW zizmor over these workflows — passing checks are the verification, and a new finding it raises is the point rather than a nuisance.\n' "$cur" "$new" >/tmp/body.md
open_pr "bot/zizmor-${new}" "chore(ci): bump zizmor to ${new} (bot)" /tmp/body.md
else [ $? -gt 1 ] && failed=1; fi

# --- diff-cover (ci.yml). pipx, no checksum. ---
cur=$(grep -oE 'DIFF_COVER_VERSION: "[0-9.]+"' .github/workflows/ci.yml | grep -oE '[0-9.]+')
if new=$(newer_release diff-cover Bachmann1234/diff_cover "$cur") && [ -n "$new" ]; then
sed -i "s|DIFF_COVER_VERSION: \"${cur}\"|DIFF_COVER_VERSION: \"${new}\"|" .github/workflows/ci.yml
printf 'Automated pin bump (#373): diff-cover %s -> %s.\n\nThe Coverage check on this PR measures patch coverage with the NEW diff-cover — passing checks are the verification.\n' "$cur" "$new" >/tmp/body.md
open_pr "bot/diff-cover-${new}" "chore(ci): bump diff-cover to ${new} (bot)" /tmp/body.md
else [ $? -gt 1 ] && failed=1; fi

# --- kcov (tests/coverage.sh, digest-pinned docker image) ---
cur=$(grep -oE '# kcov v[0-9]+' tests/coverage.sh | grep -oE '[0-9]+')
latest=$(curl -fsS "https://hub.docker.com/v2/repositories/kcov/kcov/tags?page_size=100" | jq -r '.results[].name' | grep -E '^v[0-9]+$' | sort -V | tail -1)
Expand Down