Skip to content

Add hardening option for workflow generation - #932

Open
exoego wants to merge 2 commits into
typelevel:mainfrom
exoego:harden-workflows
Open

exoego wants to merge 2 commits into
typelevel:mainfrom
exoego:harden-workflows

Conversation

@exoego

@exoego exoego commented Oct 28, 2026 •

Copy link
Copy Markdown

Motivation

GH Workflow Linters such as jactionlint and zizmor report the below problems in the workflows that sbt-typelevel generates.
Fixing them today requires overriding githubWorkflowJobSetup, or maintaining a copy by hand (see scala-steward-org/scala-steward#4040).

Finding Rule Where
1. actions/checkout keeps the GITHUB_TOKEN in .git/config, where any later step or an artifact upload can read it artipacked every generated checkout step
2. actions/setup-java restores the sbt cache in runs that publish, so a poisoned cache entry can end up in the published artifacts cache-poisoning every generated setup-java step when githubWorkflowTargetTags is set, which sbt-typelevel-ci-release does by default
3. Unquoted variables, no permissions:, no timeout-minutes: shellcheck, missing-permissions, missing-timeout clean.yml

Have you relied on LLM-based tools in this contribution?

Yes, I guided LLM on design, coding and verification.

I wrote this PR description per contribution guideline.

Changes

Adds options for findings 1 and 2.

Finding 3 will be addressed in #933 separately since no configuration

1. githubWorkflowCheckoutPersistCredentials: Boolean (default true)

For finding 1

When false, every generated actions/checkout step gets persist-credentials: false.

This covers the build, publish, dependency-submission, site and validate-steward jobs.

I think Boolean is enough.
Explicit use of GITHUB_TOKEN with appropriate permissions or OIDC should be preferred over the implicitly persisted credential.

2. githubWorkflowCacheOn: RunPredicate (default RunPredicate.Always)

For finding 2

This let configure when to use sbt dependency cache, so users can harden workflow from cache poisoning.

RunPredicate is a new predicate on a workflow run.
It has three 3 leaves, 2 constants and combinators.
And RunPredicate.Ref.anyOf lifts a Seq[RefPredicate], so the publish refs can be reused directly.
RunPredicate is not specific to caching and may be used on other conditions later.

The predicate compiles into the cache input of setup-java, which disables both the restore and the save when the input is empty:

cache: ${{ !(startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') && 'sbt' || '' }}

WorkflowStep.SetupJava gets an overload with cacheCond: Option[String] for builds that assemble the steps themselves.
The existing overload keeps its signature.

Example

// Keep the cache out of tag builds.
ThisBuild / githubWorkflowCacheOn := !RunPredicate.Ref(RefPredicate.StartsWith(Ref.Tag("v")))

// Keep the cache out of every run that publishes.
ThisBuild / githubWorkflowCacheOn := !RunPredicate.Ref.anyOf(githubWorkflowPublishTargetBranches.value)

// Cache only in pull requests.
ThisBuild / githubWorkflowCacheOn := RunPredicate.Event("pull_request")

Compatibility

  • Both defaults keep the generated ci.yml, so nothing changes for existing builds until they opt in.
  • WorkflowStep keep their signatures.

Verification

Generated this repository's ci.yml with githubWorkflowCheckoutPersistCredentials := false and githubWorkflowCacheOn := !RunPredicate.Ref.anyOf(githubWorkflowPublishTargetBranches.value), then ran jactionlint 2.3.0 with the default profile.
The artipacked and cache-poisoning findings disappear.

@mergify

mergify Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@exoego
exoego marked this pull request as ready for review October 28, 2026 01:32

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant