Actio

Supply-chain pinning

Freeze mutable action refs to immutable digests with the pins surface.

Actio freezes mutable action and image refs to immutable digests at build time, and ships a pins management surface for auditing and updating those locks — all without introducing a foreign runtime. For action dependencies, the pins surface now serves as a legacy bridge while GitHub's native workflow dependencies: lock model rolls out.

Automatic pinning at build time

actio build resolves every mutable uses: ref to an immutable commit SHA and every docker://image:tag to a digest, keeping the human-readable tag as a trailing comment. The emitted workflow becomes a lockfile — the SHA is what runs, the comment is what you read, and actio check stops the two drifting.

in.actio.yml
- uses: pnpm/action-setup@v2
- uses: docker://alpine:3.18
.github/workflows/out.yml
- uses: pnpm/action-setup@d648c2dd069001a242c621c8306af467f150e99d # v2
- uses: docker://alpine@sha256:51b67269f354137895d43f3b3d810bfacd3945438e94dc5ac55fdac340352f48 # 3.18

Resolved digests are cached in actio.lock, so repeat builds are deterministic and --offline works without touching the network; re-pinning an already-pinned ref is a no-op. Pinning is on by default for third-party actions and docker:// images; first-party actions/* and github/* refs are left on their tag. Tune it with the pin config block or the --pin/--no-pin/--pin-github/--offline flags.

Only Docker Hub registry images auto-resolve. A docker:// image on an unsupported registry hostghcr.io, a private/internal registry, anything with a host segment we can't auto-resolve — is left on its tag with a pin-unresolvable-registry warning rather than hard-failing, so a private registry never becomes an exit-1 cliff while you're still told what wasn't locked. That skip is narrow: a failure to resolve on a registry we do support (a Docker Hub auth error, missing tag, rate-limit 429, or 5xx) is a real failure and hard-fails (exit 1), never silently shipped unpinned. --offline likewise stays fail-closed (exit 2) when the lock can't satisfy a ref.

Pinning config

The pin config key accepts "all", "off", or a PinConfig object for per-class control:

Prop

Type

The pins command

# verify pin state
npx actio-cli pins check .github/actio/ci.actio.yml

# mechanical pin rewrite only (no build/config/custom-pass execution)
npx actio-cli pins update .github/actio/ci.actio.yml --no-exec --delta-out .actio/pins-delta.json

# privileged apply of a precomputed delta with strict allowlist checks
npx actio-cli pins apply --constrained .actio/pins-delta.json

Exit codes

Exit codes are security-significant:

CodeMeaning
0Clean.
1Resolvable drift (for example, a tag moved).
2Hard failure (including integrity mismatch); never auto-heal.

pins apply --constrained only accepts a single-action, three-artifact delta shape:

  1. source .actio.yml uses: owner/repo@ref bump,
  2. matching actio.lock digest update,
  3. matching generated uses: owner/repo@<digest> # <ref> substitution.

Anything outside that shape is rejected with exit 2.

Dependabot reconcile (safe two-phase pattern)

Use a two-phase workflow split:

Phase 1 — pull_request, read-only token

Run pins update --no-exec on the PR head and upload the delta artifact.

Phase 2 — workflow_run, write token

Download the trusted artifact and run pins apply --constrained; never run build or pins update in this privileged phase.

This avoids executing PR-controlled config/passes in a write-privileged context while still keeping source, lockfile, and generated pins aligned.

Relationship to GitHub native dependencies:

Actio has two action-locking modes selected by target capability:

  1. Native preview target (github-actions-native-dependencies-preview): Actio resolves uses: actions to immutable SHAs + integrity and emits a top-level dependencies: block in generated workflows.
  2. Legacy target (legacy, default): Actio keeps using actio.lock + pins (check / update --no-exec / apply --constrained) as a transitional bridge.

Why both exist:

  • Native dependencies: is the long-term destination because runner-side pre-execution verification is stronger than compile-time-only pinning.
  • actio.lock remains for environments that don't yet support native workflow dependencies.

Set the target in config (or via --target) to choose behavior per repository.

On this page