Skip to content

security: weekly audit, release checked as the updater will, tighter tokens - #119

Merged
Wasabules merged 1 commit into
mainfrom
ci-security
Nov 9, 2026
Merged

Wasabules merged 1 commit into
mainfrom
ci-security

Conversation

@Wasabules

@Wasabules Wasabules commented Nov 9, 2026 •

Copy link
Copy Markdown
Owner

Security follow-ups from a review of the workflows. Independent of #118: the two touch different lines of release.yml and ci.yml.

Weekly security audit

The security job (govulncheck, npm audit) moves from ci.yml to its own security.yml, which runs on pull requests and pushes as before, and every Monday without a commit. Dependabot raises Go modules and npm packages but not the Go toolchain in go.mod; a standard-library advisory published during a quiet week waited for the next push to be noticed. A failed scheduled run sends GitHub's notification to whoever last changed the schedule. The steps are moved unchanged, with a time limit added.

The release is judged as the updater will, before it is published

Today the signature is first checked against the key the app embeds by the release skill, after publication. A signing key rotated without internal/updater/verify.go would publish a release that every installed copy refuses.

  • internal/updater: AcceptManifest(manifest, sig, tag, assets) is the updater's acceptance without the network: signature against the embedded key, version line bound to the tag, exactly one entry per asset. The updater's own verifiedChecksum now calls the same code (acceptManifest), so the release check cannot drift from what clients do. UpdateAssets lists the five assets the updater downloads; a test on each system checks it holds that system's names from apply_*.go.
  • tools/updatersign verify <dir> <tag> runs it on the release directory, requires every file there to be listed once and every update asset to be present, and hashes every file.
  • release.yml runs it right after signing, before the attestation and the upload.

Checked on the published v1.11.1 assets (accepted: signature, version line, 8 assets), and on copies of them that are refused:

tampering refused with
wrong tag (v1.11.0) manifest version "v1.11.1" does not match the expected release "v1.11.0"
one byte appended to the tarball SHA-256 … the manifest says …
the .dmg removed SyslogStudio-macos-universal.dmg is listed but missing from the release
a file the manifest does not list no checksum for "extra.txt" in manifest
.sig removed the manifest is not signed
version line edited after signing checksums signature verification failed

Smaller

  • The tag reaches the release builds through GITHUB_REF_NAME, not as an ${{ github.ref_name }} expression pasted into the script: an expression is substituted into the shell's source, so a tag named to look like code would run as code. Only maintainers push tags; this keeps it data either way. The same file already used the variable elsewhere.
  • Pages: pages: write and id-token: write were granted to the whole workflow, including the job that runs tools/check-links.mjs; they now belong to the deploy job only. Both checkouts keep no credentials (the only ones in the repo that did), and both jobs have a time limit.
  • NSIS is pinned to 3.13.0 (published 2026-09-27, the version the last releases used) instead of whatever Chocolatey serves on the day.

Checked

  • go vet ./..., staticcheck, go test -race ./internal/updater (vet for linux and darwin too)
  • actionlint 1.7.12 clean
  • updatersign verify on v1.11.1 and the six tampered copies above
  • CI green; Security audit now comes from the Security workflow
  • Merges cleanly with ci: faster and sturdier workflows #118 in either order; actionlint is clean on the combination (locally, without shellcheck: the scripts security.yml carries are the ones ci: faster and sturdier workflows #118's CI already passed through it)

The release path first runs at the next tag; actionlint covers it statically until then.

…tokens

- The security audit (govulncheck, npm audit) moves to security.yml and
  also runs every week without a commit. Dependabot does not raise the Go
  toolchain, so a standard-library advisory published in a quiet week
  waited for the next push to be seen.
- The release job runs the updater's own acceptance on the signed
  manifest before publishing: `updatersign verify` calls
  updater.AcceptManifest, which the updater now uses itself (signature
  against the embedded key, version line, one entry per asset), then
  hashes every file. A key rotated without verify.go, or a missing
  update asset, stops the job instead of shipping a release installed
  copies refuse. Checked on the published v1.11.1 assets and on tampered
  copies of them.
- The tag reaches the release builds through GITHUB_REF_NAME instead of a
  ${{ github.ref_name }} expression pasted into the script.
- Pages: pages:write and id-token:write belong to the deploy job only, the
  checkouts keep no credentials, and both jobs have a time limit.
- NSIS is pinned to 3.13.0, the version the last releases used.
@Wasabules Wasabules mentioned this pull request Nov 9, 2026
4 tasks done
@Wasabules
Wasabules merged commit 06801dc into main Nov 9, 2026
9 checks passed
@Wasabules
Wasabules deleted the ci-security branch November 9, 2026 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant