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.
- uses: pnpm/action-setup@v2
- uses: docker://alpine:3.18- uses: pnpm/action-setup@d648c2dd069001a242c621c8306af467f150e99d # v2
- uses: docker://alpine@sha256:51b67269f354137895d43f3b3d810bfacd3945438e94dc5ac55fdac340352f48 # 3.18Resolved 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 host — ghcr.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.jsonExit codes
Exit codes are security-significant:
| Code | Meaning |
|---|---|
0 | Clean. |
1 | Resolvable drift (for example, a tag moved). |
2 | Hard failure (including integrity mismatch); never auto-heal. |
pins apply --constrained only accepts a single-action, three-artifact delta shape:
- source
.actio.ymluses: owner/repo@refbump, - matching
actio.lockdigest update, - 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:
- Native preview target (
github-actions-native-dependencies-preview): Actio resolvesuses:actions to immutable SHAs + integrity and emits a top-leveldependencies:block in generated workflows. - Legacy target (
legacy, default): Actio keeps usingactio.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.lockremains for environments that don't yet support native workflow dependencies.
Set the target in config (or via --target) to choose behavior per repository.