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)
- 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.
- 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.
- 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.)
- 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
- Pin every
uses: to a full commit SHA (Dependabot/pinact automate the update flow), starting with the third-party Netlify action.
- 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).
- Standardize on one secret name for the Epidata API key and attach it only to the step(s) that need it.
- 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.
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 nopermissions: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), andR-CMD-check-full.yamlduplicatesR-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)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-partynwtgck/actions-netlify@v3.0in 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.permissions:blocks.R-CMD-check.yaml,R-CMD-check-full.yaml,lint.yaml,pkgdown.yaml,test-coverage.ymldeclare nopermissions:at all (onlypr-commands.yaml:13declares one, correctlyread-allat top level). If the repository's defaultGITHUB_TOKENpermissions are read/write, every check/lint/coverage run holds a write-capable token.grep -c "permissions:" .github/workflows/*confirms only pr-commands matches.R-CMD-check.yaml:39,R-CMD-check-full.yaml:47,lint.yaml:24,test-coverage.yml:24mapsecrets.DELPHI_GITHUB_ACTIONS_EPIDATA_API_KEY→ envDELPHI_EPIDATA_KEY, whilepkgdown.yaml:35mapssecrets.SECRET_EPIDATR_GHACTIONS_DELPHI_EPIDATA_KEYto 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 whenEPIDATR_LIVE_TESTis 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.)R-CMD-check-full.yaml:10declaresname: R-CMD-check, identical toR-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
uses:to a full commit SHA (Dependabot/pinact automate the update flow), starting with the third-party Netlify action.permissions: read-allto the five workflows lacking it, and per-job scopes where needed (pr-commands.yaml already follows this shape).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.