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
44 changes: 40 additions & 4 deletions .github/workflows/toolchain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,51 @@ jobs:

# Decisive, and captured once so the reporter's own failure is told
# apart from a stale pin: a failed or silent mise yields an empty
# substitution, which compared straight against {} would raise the
# pin-lag error and so name the wrong cause. Testing it inside `if` is
# what keeps errexit from exiting here with no annotation at all.
# substitution, and an empty result must never be read as a verdict
# about the pins. Testing it inside `if` is what keeps errexit from
# exiting here with no annotation at all.
if ! outdated="$(mise outdated --bump --local --json)" || [ -z "$outdated" ]; then
echo "::error::mise could not report pin status — the reporter failed, not the pins"
exit 1
fi

if [ "$outdated" != "{}" ]; then
# Every jq call is guarded, and each names its own failure. Unguarded,
# jq would exit under errexit with no annotation; sharing one message
# would name the wrong cause. Both are the bare and misattributed
# exits this step exists to never produce.
if ! count="$(jq -r 'length' <<<"$outdated")"; then
echo "::error::jq could not process mise's output — the reporter failed, not the pins"
exit 1
fi

# A verdict is only readable from a payload that describes the pins.
# Reporting without installing means mise emits an entry per pinned
# tool, so nothing to count is the reporter having seen nothing — an
# unreachable or renamed mise.toml, say — rather than a clean bill.
# This is the direction that must fail loudly: a green run notifies
# no one, so a report gone blind would stay that way indefinitely.
if [ "$count" -eq 0 ]; then
echo "::error::mise reported no pinned tools — the reporter failed, not the pins"
exit 1
fi

# Keyed on bump rather than on the payload being empty, because the
# shape of --json depends on installation state: reporting without
# installing yields an entry per tool carrying a null bump, which
# makes an emptiness test true on every run. bump is non-null exactly
# when a pin trails upstream, in either state — verified against the
# mise release pinned above, which is where to re-check it. The count
# guard does not cover a renamed or dropped bump: entries would still
# be present, jq would match none of them, and the report would go
# quiet. So a run that stops failing across a mise upgrade is the
# symptom to distrust.
if ! behind="$(jq -r 'to_entries[] | select(.value.bump != null) | "\(.key) \(.value.requested) -> \(.value.bump)"' <<<"$outdated")"; then
echo "::error::jq could not process mise's output — the reporter failed, not the pins"
exit 1
fi

if [ -n "$behind" ]; then
echo "::error::Toolchain pins are behind upstream — review per SECURITY.md"
printf '%s\n' "$behind"
exit 1
fi
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ field-docket holds no credentials of its own. It authenticates to nothing, reads
- **CI pins its own toolchain; `mise.lock` covers local development.** Tool versions live in `mise.toml` with their resolved download URLs and checksums in the committed `mise.lock`, and `mise install` verifies against it — but no CI job installs through mise, so the lockfile governs a contributor's machine rather than the build. CI pins independently: Go comes from `go.mod` via `actions/setup-go`, golangci-lint and GoReleaser from their actions' explicit `version:` inputs. Read the lockfile as reproducibility for local work, not as a control on the published artifacts.
- **Actions are pinned by commit digest.** Every `uses:` in every workflow names a full commit SHA with the version as a trailing comment — a tag can be repointed, a commit SHA cannot.
- **Update automation covers Go modules and GitHub Actions.** Dependabot watches both weekly, and bumps the action SHAs along with their version comments.
- **The mise toolchain is reviewed manually.** No update bot covers `mise.toml`, so the **Toolchain currency** workflow checks the pins against upstream weekly and **fails when any is behind**. The deliberate failure is what surfaces the report at all, since `mise outdated` exits 0 whether or not a pin has moved. How far that failure reaches is not a property of this repository: GitHub notifies you about workflow runs *you* triggered, on every completed run unless you select the "only notify for failed workflows" option, and for a scheduled run the person notified is the workflow's creator — or whoever last changed its cron or re-enabled it. Keeping this report in its own workflow rather than alongside the scan above is what lets a red result name which of the two fired, since the run list identifies a run by its workflow. It blocks nothing (neither workflow is a required check). `just toolchain-outdated` runs the same query locally, against whatever mise is on your `PATH` rather than the pinned one, and it reports without failing. The pinned version of mise itself lives in that workflow rather than in `mise.toml`, so nothing reports it; it moves only when a maintainer moves it.
- **The mise toolchain is reviewed manually.** No update bot covers `mise.toml`, so the **Toolchain currency** workflow checks the pins against upstream weekly and **fails when any is behind**. The deliberate failure is what surfaces the report at all, since `mise outdated` exits 0 whether or not a pin has moved. A red run therefore means one of three things, and its annotation says which: a pin is behind and is named, or mise could not report, or its output could not be processed. Read the last two as the reporter having failed rather than as a verdict about the pins — and note that the report is not able to detect a change in mise's own output schema, which would make it fall quiet rather than loud, so a run that stops failing across a mise upgrade is worth distrusting. GitHub notifies you about workflow runs *you* triggered, on every completed run unless you select the "only notify for failed workflows" option, and for a scheduled run the person notified is the workflow's creator — or whoever last changed its cron or re-enabled it. Keeping this report in its own workflow rather than alongside the scan above is what lets a red result name which of the two fired, since the run list identifies a run by its workflow. It blocks nothing (neither workflow is a required check). `just toolchain-outdated` runs the same query locally, against whatever mise is on your `PATH` rather than the pinned one, and it reports without failing. The pinned version of mise itself lives in that workflow rather than in `mise.toml`, so nothing reports it; it moves only when a maintainer moves it.
- **Scheduled scanning stops firing in a quiet repository.** In a public repository GitHub disables scheduled workflows after 60 days without repository activity. That is one repository-level clock rather than one per workflow, so **Vulnerability scan** and **Toolchain currency** stop together — read a gap in either one's weekly runs as a symptom, not as a clean result. Re-enabling one moves its notifications, per the bullet above.

This describes a detector paired with a human response, not a project that is continuously free of known advisories. Go patch releases routinely fix reachable standard-library symbols, so the scan going red is expected periodically and is resolved by a maintainer bumping the pinned toolchain.
Expand Down