Skip to content

Exempt labd/* actions from pinact min-age - #1

Merged
mvantellingen merged 2 commits into
mainfrom
pinact-exempt-labd
Oct 25, 2026
Merged

mvantellingen merged 2 commits into
mainfrom
pinact-exempt-labd

Conversation

@mvantellingen

@mvantellingen mvantellingen commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Why

The org-wide security workflow runs pinact with min_age: 7, which rejects any pinned action whose commit is younger than 7 days. That also applies to our own actions: e.g. labd/gh-actions-typescript v1.1.0 would be rejected in every repo for a week, blocking adoption of our own fixes.

What

  • Drop the min_age: 7 input. It becomes the --min-age CLI flag, which takes precedence over config rules (precedence: --min-age > rules[].min_age > top-level min_age), so no exemption is possible while it is set.
  • A new "Write pinact config" step builds the effective config in $RUNNER_TEMP/pinact.yaml (this workflow runs in the context of the target repo, so files in .github-workflows are not available) and passes it via the action's config input. verify_min_age: true is set as well.

How a repository's own pinact config is handled

The step looks for the repo's config using pinact's own discovery order (pkg/config findProjectConfigPath in pinact v4.0.0): .pinact.yaml, .github/pinact.yaml, .pinact.yml, .github/pinact.yml.

  • No repo config: org default (version: 3, min_age: {value: 7, always: true}, plus the labd rule).
  • Repo config with version: 3: merged with yq (preinstalled on ubuntu-latest):
    • all repo settings are kept: files, ignore_actions, separator, ghes, rules. files patterns are rewritten to stay relative to the original config's directory, because pinact resolves them relative to the config file.
    • min_age.value = max(repo value, 7); min_age.always = true.
    • repo rules whose min_age is lower than 7 have that override removed (the rule is dropped if nothing else is left in it). A ::warning annotation reports this. Rules that raise min_age are kept.
    • the ActionRepoOwner == "labd" → min_age: 0 rule is appended last.
  • Any other schema version (v2 is abandoned and rejected by pinact v3+; unknown future versions): we don't merge. The repo config is used as-is, PINACT_MIN_AGE=7 is exported so every action, labd included, must be at least 7 days old, and a ::warning is logged. For a v2 config pinact itself then fails, exactly as it does in that repo's own pinact run (and on main today).

Policy choice: repos can't lower the org minimum for specific third-party actions through rules. The org check exists to enforce a cooldown on third-party releases, and allowing per-repo exemptions would make it opt-out. Only first-party labd/* actions are exempt, and that is set centrally. Repos can still make it stricter. Known escape hatch: ignore_actions / rules[].ignore: true are honoured (e.g. terraform-aws-static-site ignores labdigital-main/.*). Those skip pinning altogether and are visible in review.

Current repo configs: nextjs-basic-auth-middleware (.github/pinact.yaml: min_age 7 + its own labd rule) ends up equivalent to the org default. terraform-aws-static-site (.pinact.yaml: ignore_actions) keeps its ignore.

Security note

labd/* actions are first-party: we review and release them ourselves, so the cooldown (meant to protect against compromised third-party releases) adds nothing for them. Third-party actions keep the 7-day minimum, and repos cannot lower it.

The step's script is static: no ${{ }} in run, and values are passed via env. zizmor (CI's pinned image, v1.29.0) reports no findings, and the pedantic findings are the same as on main.

Testing

Local, pinact v4.0.0 (the version pinact-action v3.0.0 installs). The step script was extracted from this workflow with yq and run with simulated RUNNER_TEMP/GITHUB_ENV/GITHUB_OUTPUT, then pinact --config <path> run --fix=false --verify --verify-min-age. The test workflow pins labd/gh-actions-typescript/pnpm-install v1.1.0 (2026-10-25), stefanzweifel/git-auto-commit-action v7.2.1 (2026-10-22) and actions/checkout v7.0.1 (2026-07-17).

Repo config labd v1.1.0 stefanzweifel v7.2.1 checkout repo-specific exit
none ok rejected ok – 2
nextjs-basic-auth-middleware .github/pinact.yaml (real) ok rejected ok its labd rule superseded (warning) 2
terraform-aws-static-site .pinact.yaml (real) + labdigital-main/private-action@main ok rejected ok labdigital-main ignored ✔ 2
synthetic v3 .github/pinact.yml (files, separator " # ", ignore_actions, ignore rule, rule lowering stefanzweifel to 0, rule raising checkout to 200) ok still rejected (lowering rule dropped, warning) rejected (stricter rule kept) file outside files not scanned ✔, separator ✔, ignores ✔ 2
v2 .pinact.yaml – – – warning + pinact "version was abandoned" (same as the repo's own run) 1
same 4 cases without the stefanzweifel line ok – ok (synthetic: rejected by its 200-day rule) – 0 / 0 / 0 / 2

Controls with the repo config ignored (org default only): terraform fixture fails on labdigital-main/private-action@main ("action can't be pinned"); the synthetic fixture also scans excluded.yaml and fails on the ignored actions. Both confirm the merge is what keeps the repo settings. Fallback semantics: PINACT_MIN_AGE=7 --verify-min-age with a plain v3 config rejects both labd and stefanzweifel.

Note: the "Security Checks" check on this PR runs the org-required workflow from main, so it does not exercise this change. It takes effect after merge.

Pass a pinact config (written to RUNNER_TEMP) instead of the min_age
input. The config keeps a 7 day minimum release age for all actions but
sets it to 0 for actions owned by labd, so our own fresh releases are
not rejected org-wide for a week.
Instead of replacing a repository's pinact config, detect it using
pinact's discovery order and merge it with the org policy: repo
settings (files, ignore_actions, separator, ghes, rules) are kept,
min_age is raised to at least 7 days with always: true, repo rules that
lower min_age below that are dropped, and the labd/* exemption is
appended. Configs with a schema version other than 3 are used as-is
with PINACT_MIN_AGE enforcing the org minimum.
@mvantellingen
mvantellingen merged commit 2adb396 into main Oct 25, 2026
1 check passed
@mvantellingen
mvantellingen deleted the pinact-exempt-labd branch October 25, 2026 09:35
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