Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

Lock your workflow dependencies.

> [!WARNING]
> **Technical Preview.** gh-actions-lock is pre-1.0 and under active development. The
> [!NOTE]
> **Public preview.** gh-actions-lock is pre-1.0 and under active development. The
> lockfile format, command flags, and behavior may change without notice between
> releases. Use it, file issues, and expect rough edges.

Expand Down Expand Up @@ -84,6 +84,12 @@ Workflows that are onboarded to the lockfile enforce that all dependencies are p

Finally, locked actions must have a branch that the commit being locked exists within. This is to make impostor commit style attacks harder.

## Documentation

- [Keeping a repository's Actions lockfile current](./docs/repository-developer-experience.md) — examples of a Copilot skill and workflow that run the CLI when dependencies change.
- [Dependabot and the Actions lockfile](./docs/dependabot.md) — how Dependabot regenerates lockfile entries when it bumps an action, and how cooldowns fit in.
- [Rolling out lockfiles across an organization or enterprise](./docs/organization-and-enterprise-rollout.md) — opening lockfile pull requests at scale, tracking them to merge, and enabling the Require lockfile policy.

## Limitations

There are currently eligibility limitations for workflows that can be onboarded to lockfiles:
Expand Down
168 changes: 168 additions & 0 deletions docs/dependabot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Dependabot and the Actions lockfile

Dependabot updates action references in workflow YAML and invokes the CLI to
regenerate the corresponding lockfile entries in the same pull request.

> [!NOTE]
> gh-actions-lock is in public preview. Flags, findings JSON, and lockfile
> schema may change between releases, and Dependabot pins a specific CLI version
> and lockfile schema. See [RELEASING.md](../RELEASING.md).

## Lockfile updates

When Dependabot opens a version update for a GitHub Actions dependency in a
workflow that is **already onboarded** to lockfile pinning, it also regenerates
the corresponding lockfile entry, so the pinned commit SHA always matches the
updated ref in your workflow YAML.

The two tools have separate responsibilities:

- **Dependabot owns the workflow YAML.** It decides the new ref, exactly as it
does today.
- **The `gh-actions-lock` CLI owns the lockfile, exclusively.** Dependabot
invokes the CLI rather than writing lockfile entries itself, so pinning has a
single implementation.

If you already use Dependabot for GitHub Actions and you have onboarded
workflows, this needs no configuration.

## Onboarding

Dependabot invokes the CLI with `--no-onboard`, which refuses to add lockfile
entries for workflows or actions that do not already have them. A dependency

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
entries for workflows or actions that do not already have them. A dependency
entries for workflows that do not already have them. A dependency

Dependabot bumps in a workflow you never locked produces a non-blocking
`onboarding-required` finding and no lockfile write:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't surfaced to the user from the Dependabot flow. Silently skipped. The user would see this if they inspected output locally.


```json
{
"category": "onboarding-required",
"severity": "info",
"detail": "actions/checkout@v6 has no lockfile entry; --no-onboard refuses to add new workflows or actions"
}
```

Onboard workflows with the CLI before relying on Dependabot to maintain their
lockfile entries:

```bash
gh actions-lock
```

Dependabot also passes `--no-narrow`, so the CLI does not rewrite the ref
Dependabot just chose. Dependabot picked `v7`; the lockfile records `v7` and its
commit, and the YAML is left alone.

## Reading the update

A Dependabot pull request touching an onboarded workflow changes two files. The
lockfile diff is the security-relevant half:

```diff
workflows:
'.github/workflows/ci.yml':
- - 'actions/checkout@v6.1.0'
+ - 'actions/checkout@v7'
dependencies:
- 'actions/checkout@v6.1.0':
- ref: 'v6.1.0'
- commit: 'sha1-d23441a48e516b6c34aea4fa41551a30e30af803'
+ 'actions/checkout@v7':
+ ref: 'v7'
+ commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1'
owner_id: 44036562
repo_id: 197814629
```

Review the changed commit SHA the way you would review any dependency change. A

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

repo_id move suggests a transfer, owner_id change suggests namespace squatting attack. The CLI isn't aware of the transfers being okay yet. There's a draft PR that I'm getting to for that here #118 but owner_id change will raise a message to the operator as this is really something that should not happen ever. This will also continue to fail at runtime, whereas repo transfers will not fail.

moved `owner_id` or `repo_id` is a stronger signal still: it means the
repository behind that name is not the one you locked.

Review it, but do not edit it. The lockfile is generated, and the CLI is the
only thing that writes it. If a Dependabot pull request's lockfile looks wrong,
close it or fix the workflow and let the lockfile be regenerated.

## Cooldowns

Dependabot's [`cooldown`
option](https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference#cooldown)
delays updates until a release has had time to settle.

```yaml
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
```

`gh actions-lock` reads this file too, so one setting governs both tools.

### What the CLI actually uses it for

Cooldown does **not** hold back ref narrowing. When the CLI narrows
`actions/checkout@v6` to `v6.1.0`, it only considers tags that already point at
the commit `v6` resolves to. Narrowing renames the commit you already have; it
never moves you to a newer release. There is nothing for a cooldown to delay.

What cooldown does control:

- **The fresh-tag nudge.** With no cooldown configured, pinning a tag released
within the last 3 days emits an informational finding suggesting you configure
one. Any positive cooldown skips that check for the action.
- **The interactive tag picker**, which hides tags younger than the cooldown
unless one matches your current pin.

Precedence is Dependabot config first, then
`~/.config/gh-actions-lock/config.yml`. A Dependabot `default-days: 0` does not
count as configured, so it cannot silently downgrade a stricter setting in your
own config file.

### Only `default-days` is honored today

The CLI parses the rest of the cooldown block and reports the keys it ignores:

```
Dependabot cooldown semver-major/minor/patch-days are not supported and were ignored
Dependabot cooldown include/exclude filters are not supported and were ignored
```

These are non-blocking `cooldown-config-ignored` findings. Dependabot still
honors those keys for its own scheduling — the gap is only in what the CLI reads
when deciding whether to nudge you about a fresh tag.

## Automation interaction

If you also run the [lockfile automation
workflow](./repository-developer-experience.md), its `verify` job checks
Dependabot's pull requests like any other. When the lockfile entry is present
and correct, the check passes and nothing else happens.

The `update` job needs more thought. It only runs on `push` to a branch in your
repository, and Dependabot branches live in your repository, so a Dependabot
pull request whose lockfile is stale gets a bot commit fixing it. That means a
Dependabot pull request can gain a second commit. To prevent that, scope the
update job away from Dependabot branches:

```yaml
if: github.event_name == 'push' && github.ref_type == 'branch' && !startsWith(github.ref_name, 'dependabot/')
```

## A note on exit codes

On a fix run the findings JSON reports the state the CLI *diagnosed*, before it
fixed anything. A stale lockfile that the CLI then repaired reports
`"valid": false` with `ref-changed` findings and still exits `0`, because the
run succeeded in fixing it. Re-running reports `"valid": true` with no findings.

Use `--verify` for a read-only answer that writes nothing and exits non-zero if
the lockfile is stale. That is the form to use in a required check.

## Related

- [Setting up lockfile pinning](../README.md)
- [Keeping a repository's Actions lockfile current](./repository-developer-experience.md)
- [Dependabot cooldown options reference](https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference#cooldown)
67 changes: 67 additions & 0 deletions docs/examples/actions-lock-SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
name: actions-lock
description: Keep GitHub Actions dependencies locked whenever workflows or local actions are created, modified, upgraded, or removed.
---

<!--
Example skill. Install as .github/skills/actions-lock/SKILL.md.
See docs/repository-developer-experience.md for the rationale.
-->

When changing files under `.github/workflows/`, or changing an `action.yml` or
`action.yaml` used by those workflows:

1. Generate `.github/workflows/actions.lock` only with the CLI; do not create or
edit it manually. Manual changes bypass dependency resolution and can leave
refs, commits, or repository IDs inconsistent. Verification or workflow
startup can fail, and a later CLI run may overwrite the edits.
2. Ensure the `github/gh-actions-lock` CLI extension is installed. This is safe
to run when it is already present:

```bash
gh extension install github/gh-actions-lock
```

3. After editing workflows or local actions, update the lockfile:

```bash
gh actions-lock --no-interactive
```

4. Expect the command to edit source files, not just the lockfile. It narrows
`uses:` refs to a full semver tag and rewrites same-repo `./…` action
references to `$/…`. Both are intended; do not revert them.
5. Include every generated workflow, local action, and
`.github/workflows/actions.lock` change in the resulting change set.
6. Verify the result without modifying files:

```bash
gh actions-lock --verify
```

Do not consider the task complete unless verification succeeds. If locking
Comment thread
Steve-Glass marked this conversation as resolved.
fails, report the exact finding instead of leaving a stale or incomplete
lockfile. Use the finding's detail and remediation to identify the next step;
do not delete lockfile entries or accept moved pins just to make a check pass.
Comment on lines +44 to +45

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point, escalate it to the human. AI can suggest a path forward, but this is a human decision IMO.


For `local path actions are not yet supported`, check that each `./…` path
resolves from the repository root to an `action.yml` or `action.yaml`. Fix an
incorrect path and re-run the CLI. If the action is generated, checked out from
another repository, or otherwise unavailable for inspection, defer onboarding
that workflow and report the limitation. Other workflows can still be locked.
If the affected workflow is already onboarded, report the blocking finding
rather than removing its lockfile entry.
Comment on lines +47 to +53

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use this skill to attempt to migrate to $/.


Other failures include missing or changed refs, invalid `$/…` paths, unreachable
pins, misleading SHA-shaped refs, and API or authentication failures. See
[Troubleshooting](https://github.com/github/gh-actions-lock/blob/main/docs/repository-developer-experience.md#troubleshooting)
for common findings and recovery steps.

Handling of job-level reusable workflow calls
(`jobs.<id>.uses: owner/repo/.github/workflows/x.yml@ref`) is still under
review. If this repository calls remote reusable workflows, review `git diff` on
the lockfile and report anything that looks wrong. Do not hand-edit the lockfile
to correct it.

Background on how locking is automated in this repository:
`docs/repository-developer-experience.md`.
96 changes: 96 additions & 0 deletions docs/examples/actions-lock-workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Example: install this as `.github/workflows/actions-lock.yml`
#
# Regenerates the Actions lockfile on push and verifies it on pull requests.
# See docs/repository-developer-experience.md for the rationale.
#
# NOTE: handling of job-level reusable workflow calls
# (jobs.<id>.uses: owner/repo/.github/workflows/x.yml@ref) is still under
# review. Until that is settled, if this repository calls remote reusable
# workflows, delete the `update` job below and keep only `verify`.

name: Maintain Actions lockfile

on:
push:
paths:
- ".github/workflows/**"
- "**/action.yml"
- "**/action.yaml"
pull_request:
paths:
- ".github/workflows/**"
- "**/action.yml"
- "**/action.yaml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: actions-lock-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true

jobs:
update:
name: Update lockfile
# Tag pushes are excluded: the commit step pushes back to a branch.
if: github.event_name == 'push' && github.ref_type == 'branch'
runs-on: ubuntu-latest
permissions:
actions: write

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't the same as the workflow scope permission. And the workflow scope permission is not one that we'll issue to a job. The options there are a PAT with the workflow scope, or to just have this be CI that doesn't auto-fix things for you, and just blocks bad changes from merging.

contents: write

steps:
- name: Checkout repository
uses: actions/checkout@v7.0.1

- name: Install gh-actions-lock
env:
GH_TOKEN: ${{ github.token }}
# During public preview, add --pin <tag> to hold a tested release:
# gh extension install github/gh-actions-lock --pin v0.1.6
run: gh extension install github/gh-actions-lock

# Also rewrites workflow refs and migrates same-repo `./…` actions to
# `$/…`, so the commit step stages more than just the lockfile.
- name: Update lockfile
env:
GH_TOKEN: ${{ github.token }}
run: gh actions-lock --no-interactive

- name: Commit lockfile updates
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ -z "$(git status --porcelain)" ]; then
echo "Lockfile is already up to date."
exit 0
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add --all
git commit -m "Update GitHub Actions lockfile"
git push origin "HEAD:${GITHUB_REF_NAME}"
# GITHUB_TOKEN pushes do not retrigger workflows, so verify the bot's
# own commit explicitly.
gh workflow run actions-lock.yml --ref "${GITHUB_REF_NAME}"

verify:
name: Verify lockfile
if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v7.0.1

- name: Install gh-actions-lock
env:
GH_TOKEN: ${{ github.token }}
run: gh extension install github/gh-actions-lock

- name: Verify lockfile
env:
GH_TOKEN: ${{ github.token }}
run: gh actions-lock --verify
Loading
Loading