Skip to content

CI hardening: pin uses: by commit SHA, declare permissions: on all workflows, unify the two API-key secret names, fix the duplicate R-CMD-check workflow names #402

Description

@docxology

Summary

While reviewing the CI configuration I found a handful of hardening gaps that are individually ecosystem-typical but add up: no workflow pins any uses: by commit SHA (all mutable tags, including a third-party Netlify action inside a comment-triggered workflow), five of the six workflows declare no permissions: block so jobs inherit the repo's default token permissions, two different secret names supply the same Epidata API key (exported globally in workflows that never run live tests), and R-CMD-check-full.yaml duplicates R-CMD-check.yaml's workflow name while carrying matrix legs that cannot pass on current runners. None of these is urgent on its own; together they widen the blast radius of any workflow-level mistake. I'd suggest the standard fixes below.

Evidence (dev tip 87b10e1; all six workflows are identical on main)

  1. No SHA pinning anywhere. All uses: are tag-pinned, e.g. R-CMD-check.yaml:42, R-CMD-check-full.yaml:50, lint.yaml:26, test-coverage.yml:27/:58, pkgdown.yaml:37 — and notably the third-party nwtgck/actions-netlify@v3.0 in the comment-triggered preview job (pr-commands.yaml:125). A retag/compromise of any referenced action repo runs attacker code in this repo's CI. Dependabot or pinact can automate the SHA pinning.
  2. Missing permissions: blocks. R-CMD-check.yaml, R-CMD-check-full.yaml, lint.yaml, pkgdown.yaml, test-coverage.yml declare no permissions: at all (only pr-commands.yaml:13 declares one, correctly read-all at top level). If the repository's default GITHUB_TOKEN permissions are read/write, every check/lint/coverage run holds a write-capable token. grep -c "permissions:" .github/workflows/* confirms only pr-commands matches.
  3. Two secret names for the same key, exported globally where CI never uses it. R-CMD-check.yaml:39, R-CMD-check-full.yaml:47, lint.yaml:24, test-coverage.yml:24 map secrets.DELPHI_GITHUB_ACTIONS_EPIDATA_API_KEY → env DELPHI_EPIDATA_KEY, while pkgdown.yaml:35 maps secrets.SECRET_EPIDATR_GHACTIONS_DELPHI_EPIDATA_KEY to the same env var. Two names means two keys to rotate and easy partial updates. The key is exported at job level in check/lint/coverage although the suite only runs live tests when EPIDATR_LIVE_TEST is set (tests/testthat/helper-live.R:11-12), which CI never sets — so the key rides in every step's environment for nothing. (Fork PRs are protected by GitHub withholding custom secrets, to be clear.)
  4. Duplicate workflow names + impossible matrix legs. R-CMD-check-full.yaml:10 declares name: R-CMD-check, identical to R-CMD-check.yaml's name, which makes status checks ambiguous. Its matrix includes R 3.5 on macos/windows *-latest (:27-35) and R 4.1 on windows *-latest, which cannot pass on current runner images — permanently red legs.

Suggested fix

  1. Pin every uses: to a full commit SHA (Dependabot/pinact automate the update flow), starting with the third-party Netlify action.
  2. Add top-level permissions: read-all to the five workflows lacking it, and per-job scopes where needed (pr-commands.yaml already follows this shape).
  3. Standardize on one secret name for the Epidata API key and attach it only to the step(s) that need it.
  4. Rename R-CMD-check-full.yaml's workflow (e.g. R-CMD-check-full) and drop or pin runner images for the R 3.5/4.1 legs.

I checked for prior reports (open/closed issues/PRs on CI hardening) and found none covering this.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions