Skip to content

action: version defaults to latest, so a SHA-pinned uses: still installs an unpinned binary #169

Description

@laurigates

Summary

A consumer who pins the Action to an immutable commit SHA still gets a floating surf binary, because version defaults to latest:

  • action.ymlinputs.version has default: latest
  • install.shtag="${SURF_VERSION:-latest}", then resolves releases/latest over the API at run time

So this workflow, which looks fully pinned:

- uses: Connorrmcd6/surface@091b937ae34ac81a02386604fed977dd24f1f0cf # v0.8.0
  with:
    args: check

installs whatever the newest release happens to be. A new release can change gate behaviour on a repo whose workflow file and hubs did not change — and a surf check verdict is a merge gate, so a behaviour change lands as a red check nobody can attribute to a local edit. This recurs on every release, for every consumer who pinned the SHA and reasonably assumed that was sufficient.

This is the remaining half of #118. That PR moved the installer to the copy bundled at the pinned ref, motivated by exactly this consumer:

A consumer who pins the action to an immutable commit SHA for supply-chain reasons —

#118 made the script provenance follow the SHA pin. The binary version the script downloads still does not.

Confirmed on a real run: with version: v0.8.0 set explicitly the log reads surf: downloading v0.8.0 (x86_64-unknown-linux-gnu) followed by surf: verifying checksum.... Without it, the default path resolves latest instead.

To preempt the obvious objection: the checksum verification added in #39/#60 is working correctly here and is not in question. It proves the download matches its own published hash — not that it is the version the consumer pinned.

Suggested fix

Have the release job write the tag into a VERSION file alongside the bundled install.sh, and default the binary version to that file instead of the literal latest. Concretely:

  • release workflow writes the tag (e.g. v0.8.0, one line, no trailing newline needed) to VERSION in the repo root, committed as part of the release commit so it exists at the tagged ref

  • action.yml changes inputs.version to default: '', and the install step resolves it:

    env:
      SURF_VERSION: ${{ inputs.version }}
    run: |
      [ -n "$SURF_VERSION" ] || SURF_VERSION="$(cat "${{ github.action_path }}/VERSION")"
      export SURF_VERSION
      sh "${{ github.action_path }}/install.sh"

install.sh needs no change — it already honours SURF_VERSION. Because both files are read at the pinned action ref, a SHA-pinned uses: then transitively pins the binary, completing what #118 started with no change required in consumer workflows. An explicit version: still overrides, and version: latest remains available for anyone who wants the floating behaviour.

A smaller alternative, if the floating default is deliberate: note in the README's Action section that version: must be set alongside a SHA pin, since the pin alone reads as sufficient.


Observed while adopting Surface as a CI docs-drift gate on a Rust workspace; verified against main HEAD and v0.8.0 on 2026-07-28.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions