From d347c1939bfb88e657cdc76da4378af172498ea3 Mon Sep 17 00:00:00 2001 From: Matthew Helmke Date: Mon, 21 Sep 2026 08:11:29 -0500 Subject: [PATCH 1/8] Update Actions overview for GA Add the hardening ruleset and transitive dependency behavior, and lead the setup path with the Guardener GitHub App (DOCS-204). - Document all seven hardening checks with their finding IDs, severities, and scope, matching the approved public rulebook - Add a transitive dependencies section covering what is hardened today and the two limits that remain - Restructure setup into an entitlement step and a Guardener GitHub App step - Correct the re-hardening bullet: a ruleset change re-evaluates every action in the catalog, not only affected ones - Correct the repository contents section. The hardened action lives on the version branches; main holds only metadata. The quick start example used @main, which cannot resolve. - Document the signed SLSA provenance attestation on each version branch - Add a section on referencing by version tag versus commit SHA - Add `chainctl actions discover --recursive` and `chainctl actions list` - Update the catalog size to more than 1,000 actions - Merge the duplicated request and report issue sections Co-Authored-By: Claude Opus 5 --- content/chainguard/actions/overview.md | 131 ++++++++++++++++++++----- 1 file changed, 109 insertions(+), 22 deletions(-) diff --git a/content/chainguard/actions/overview.md b/content/chainguard/actions/overview.md index f6e00c1b4b..4a3d2b1ae7 100644 --- a/content/chainguard/actions/overview.md +++ b/content/chainguard/actions/overview.md @@ -16,18 +16,49 @@ toc: true Chainguard Actions are a set of hardened drop-in replacements for popular GitHub Actions. Each action preserves the same inputs and outputs as the upstream version, but has been examined and revised to better protect your CI/CD pipelines from supply chain attacks. The only change in your workflow configuration is the name of the action in the `uses:` line. -Coverage spans GitHub first-party (`actions/*`), cloud-provider (`aws-actions/*`, `azure/*`, `google-github-actions/*`), Docker, HashiCorp, and security tools actions (Trivy, Grype, CodeQL, Semgrep), as well as a growing catalog of community actions. +The catalog holds more than 1,000 hardened actions. Coverage spans GitHub first-party (`actions/*`), cloud-provider (`aws-actions/*`, `azure/*`, `google-github-actions/*`), Docker, HashiCorp, and security tools actions (Trivy, Grype, CodeQL, Semgrep), as well as a growing catalog of community actions. Each hardened action: - Is built from source and evaluated through a rule-based and AI-powered hardening pipeline - Has every internal `uses:` and container image reference pinned to an immutable SHA digest - Ships with a `HARDENING.md` report documenting exactly what was checked and fixed +- Ships with a signed SLSA provenance attestation recording the upstream source and the ruleset version applied - Is re-reviewed and re-hardened whenever upstream publishes a new version or Chainguard adds a new rule Chainguard Actions protect against common threats including tag hijacking, dependency confusion, `pull_request_target` abuse, and secret exfiltration. -This page provides enough to get you started. Refer to the [Chainguard Actions README](https://github.com/chainguard-actions) in GitHub for deeper technical details and some example migrations. You can also [use Guardener to enable Chainguard Actions](/chainguard/guardener/github/actions-security/). +This page provides enough to get you started. Refer to the [Chainguard Actions README](https://github.com/chainguard-actions) in GitHub for deeper technical details and some example migrations. + +## What hardening checks and fixes + +Every action in the catalog is evaluated against the same ruleset. Each rule has a finding ID that appears in the action's `HARDENING.md` report, so you can trace any change back to the rule that produced it. + +| Check | Finding IDs | Severity | What it catches | +| ----- | ----------- | -------- | --------------- | +| Unpinned uses | `unpinned-uses` | High | `uses:` references and `docker://` image references that point at a mutable tag or branch instead of an immutable commit SHA or image digest. | +| Script injection | `script-injection` | High | Expressions such as `${{ inputs.name }}` interpolated directly into a `run:` block, where the shell can parse attacker-controlled text as commands. | +| Unsafe shell | `unsafe-shell` | High | Remote content piped straight into an interpreter, such as `curl ... \| bash`. | +| Hardcoded credentials | `hardcoded-credentials` | High | Literal secrets assigned to names containing `password`, `secret`, `token`, or `api_key`. | +| GitHub environment injection | `github-env-injection` | High | Untrusted values written to `$GITHUB_ENV`, `$GITHUB_PATH`, or `$GITHUB_OUTPUT` without newline sanitization, which lets an attacker inject variables into later steps. | +| Suspicious run content | `suspicious-run-content` | High | Malicious patterns in `run:` blocks, including obfuscated execution, process memory access, dynamic evaluation, credential file access, outbound exfiltration, reverse shells, persistence, and environment secret scraping. | +| Permissions | `missing-permissions`, `broad-permissions` | Medium | Workflows that leave `GITHUB_TOKEN` at its default permissions, or that set `read-all` or `write-all` instead of specific scopes. | + +The checks read the action definition (`action.yml` or `action.yaml`) and every workflow under `.github/workflows/` in the action's own repository. They don't inspect built or vendored output: `dist/`, `vendor/`, and `node_modules/` are out of scope, as are the action's test fixtures. + +Findings are fixed in place and the pipeline re-evaluates its own work, so a single hardening run can take several iterations before an action passes. Both the findings and the per-iteration notes are recorded in `HARDENING.md`. + +## Transitive dependencies + +Actions rarely run alone. A composite action can call other actions, and an action can fetch container images, language packages, or binaries while it runs. Chainguard hardens the references it can see in the action's source: + +- **Nested action and image references are pinned.** Every `uses:` reference and container image reference inside a hardened action resolves to an immutable commit SHA or image digest, with the original tag preserved as a comment. A moved upstream tag can't change what a hardened action runs. +- **The action's own workflows are hardened too.** The ruleset covers the workflows under `.github/workflows/` in the action's repository, not only the action definition, so the repository that produces the action is held to the same standard. +- **Missing dependencies can be onboarded.** When a hardened action depends on an action that isn't in the catalog yet, [request that action](https://github.com/chainguard-actions/.github/issues/new?template=new-action.yml) and Chainguard hardens and publishes it. + +Two limits are worth knowing. Nested references are pinned to the upstream project's commit SHA, not rewritten to point at the Chainguard hardened equivalent; that rewriting is in development and isn't published yet. Language packages and binaries that an action downloads while it runs fall outside what the ruleset inspects. + +To see the full dependency graph for your own repository, including actions reached through other actions, use the `--recursive` flag described in [View the actions you are currently using](#view-the-actions-you-are-currently-using-in-a-repository). ## Prerequisites @@ -37,9 +68,11 @@ To follow this guide, you need: - An active Chainguard organization. - Owner access on the organization. -## Preliminary steps +## Set up Chainguard Actions -Before using Chainguard Actions, log in to Chainguard and enable the Chainguard Actions entitlement for your organization. +Setting up has two parts: entitle your organization, then choose how you migrate your workflows. + +### Step 1: Create the Actions entitlement Authenticate using `chainctl`: @@ -71,21 +104,48 @@ chainctl actions entitlements list $ENTITLEMENT_ID | 2026-06-18 17:33:24 UTC ``` +### Step 2: Install the Guardener GitHub App + +The [Chainguard Guardener](/chainguard/guardener/github/getting-started/) GitHub App is the recommended way to adopt Chainguard Actions across more than a repository or two. Once you install it and link it to your Chainguard organization, the Guardener: + +- Inventories the actions your workflows use across every repository it can access +- Opens and maintains a pull request that swaps in Chainguard hardened equivalents +- Comments on pull requests that introduce unhardened actions, so your workflows don't drift back + +To set it up: + +1. Install the [Guardener GitHub App](https://github.com/apps/chainguard-guardener) on your GitHub organization. +2. Link your Chainguard organization to your GitHub organization with `chainctl guardener github link`. +3. Add a `.chainguard/actions.yaml` file to each repository you want the Guardener to work on. + +Installing the app doesn't change any repository on its own. Each repository opts in through its configuration file, and you can restrict the app to selected repositories when you install it. + +Refer to [Getting started with Chainguard Guardener](/chainguard/guardener/github/getting-started/) for the installation and linking steps, and to [Hardened Actions](/chainguard/guardener/github/actions-security/) for the configuration reference, the migration options, and the on-demand migration command. The Guardener GitHub App is in beta. + +If you'd rather not install a GitHub App, you can migrate with the [cg-actions](https://github.com/chainguard-dev/cg-skills/tree/main/skills/cg-actions) skill or by hand. Both approaches are covered in [Configure your workflows to use Chainguard Actions](#configure-your-workflows-to-use-chainguard-actions). + ## Basic usage (quick start) To use a Chainguard hardened action, edit your workflow's YAML configuration file and change the `uses:` line to match the location in `chainguard-actions`: ```yaml -- uses: chainguard-actions/@main +- uses: chainguard-actions/@ ``` -Action names often have the upstream organization appended to the action name for clarity, for example, `tj-actions/changed-actions` becomes `tj-actions-changed-actions`. This prevents two different sources of a `changed-actions` action from clashing in the Chainguard Actions repository. +Action names often have the upstream organization appended to the action name for clarity, for example, `tj-actions/changed-files` becomes `tj-actions-changed-files`. This prevents two different sources of a `changed-files` action from clashing in the Chainguard Actions repository. Search the Chainguard Actions repository, find the action you want to use, and then use the name you find there. -> **Note:** This example uses `@main`, a mutable reference, to illustrate the mechanics of switching organizations. For production workflows, pin to an immutable SHA digest instead. The [Configure your workflows](#configure-your-workflows-to-use-chainguard-actions) section covers the full migration. +> **Note:** Don't reference a hardened action with `@main`. The main branch of each repository holds only metadata (`README.md`, `LICENSE_CHAINGUARD`, and `source.json`). The hardened action itself lives on the version branches, so a reference to `@main` fails to resolve. + +## Choose how to reference an action -The rest of this page goes a bit deeper into how to use Chainguard Actions. +You can reference a hardened action by version tag or by commit SHA. The choice determines whether you receive re-hardening automatically, so make it deliberately. + +- **Version tag**, for example `chainguard-actions/actions-checkout@v4`. Chainguard moves the tag when it republishes that version line, so you pick up re-hardening without touching your workflow. Every published build still has its own internal dependencies pinned to immutable SHAs. Tags in the hardened catalog are mutable by design. +- **Commit SHA**, for example `chainguard-actions/actions-checkout@25a1eb5aa40568ec6f8c0e58f2e809ef4270ebfa`. The reference is immutable, so every run executes identical code. You stay on that build until you bump the SHA, which means you don't receive re-hardening until you do. + +If your security policy requires immutable references, pin the SHA and let Dependabot or Renovate open the bump pull requests. Otherwise, a version tag keeps you current with less work. ## Configure your workflows to use Chainguard Actions @@ -101,6 +161,8 @@ Run this from the root of your repository to get a deduplicated list of every `u grep -rhE "uses:\s*[^@]+@" .github/workflows/ | sort -u ``` +For a more thorough inventory that also follows composite actions, use [`chainctl actions discover`](#view-the-actions-you-are-currently-using-in-a-repository). + ### Check the Chainguard Actions catalog for each action. Browse [the Chainguard Actions repository](https://github.com/chainguard-actions) or use the GitHub search UI. Match by organization and action name — for example, if you use `tj-actions/changed-files`, search for `org:chainguard-actions tj-actions-changed-files`. @@ -109,7 +171,7 @@ If the action isn't in the catalog, [open an issue](https://github.com/chainguar ### Replace the `uses:` line in each workflow. -Change the `uses:` line to match the location in `chainguard-actions`. Find and pin to the commit SHA digest and preserve the original tag as a comment so Dependabot, Renovate, and human reviewers can track upgrades: +Change the `uses:` line to match the location in `chainguard-actions`. To pin by SHA digest, preserve the original tag as a comment so Dependabot, Renovate, and human reviewers can track upgrades: ```yaml # Before @@ -145,7 +207,7 @@ gh api repos/chainguard-actions/tj-actions-changed-files/commits/v47 --jq '.sha[ The resulting `uses:` line with the full SHA digest: ```yaml -- uses: chainguard-actions/changed-files@25a1eb5aa40568ec6f8c0e58f2e809ef4270ebfa # v47 +- uses: chainguard-actions/tj-actions-changed-files@25a1eb5aa40568ec6f8c0e58f2e809ef4270ebfa # v47 ``` ### Update your allowed-actions list. @@ -164,7 +226,7 @@ If something breaks, [file an issue](https://github.com/chainguard-actions/.gith ## View the actions you are currently using in a repository -Use `chainctl` to scan every workflow and composite action in a repository and list all dependencies transitively: +Use `chainctl` to scan every workflow and composite action in a repository and list the actions and container images they reference: ```shell chainctl actions discover $GIT_ORGANIZATION/$REPO @@ -181,6 +243,16 @@ chainctl actions discover $GIT_ORGANIZATION/$REPO ``` +The command needs a GitHub token, which it reads from `$GITHUB_TOKEN` or from `gh auth token`. The target can be a local directory (the current directory by default), an `owner/repo` pair, or a single action reference such as `actions/checkout@v4`. + +By default, `discover` lists only the actions your workflows reference directly. Add `--recursive` to follow each referenced action into its own definition and resolve the full transitive dependency graph: + +```shell +chainctl actions discover $GIT_ORGANIZATION/$REPO --recursive +``` + +A recursive scan makes many GitHub API calls, so it caches responses and stops after `--timeout` (five minutes by default). Refer to [`chainctl actions discover`](/platform/chainctl/chainctl-docs/chainctl_actions_discover/) for the full set of flags. + ## View the actions currently available While you can search the [Chainguard Actions repository](https://github.com/chainguard-actions) directly in GitHub, you can also use `chainctl` to find an action. @@ -197,36 +269,51 @@ chainctl actions catalog list --upstream-owner=tj-actions This example returns a list of all actions in the Chainguard Actions repository that originate from the `tj-actions` upstream source. -## Hardened action repository contents +To list the catalog entries available to a specific organization rather than the whole public catalog, use [`chainctl actions list`](/platform/chainctl/chainctl-docs/chainctl_actions_list/): -The main branch of each hardened action repository contains: +```shell +chainctl actions list --parent $ORGANIZATION +``` + +## What ships in each hardened action + +Each hardened action's repository has a main branch and one branch per hardened version. The hardened action lives on the version branches; the main branch holds only metadata. -- `HARDENING.md` — the authoritative, per-action record of what was checked, what was fixed, and how +The main branch of each repository contains: + +- `README.md` — a pointer to the action and its upstream source +- `LICENSE_CHAINGUARD` — the Chainguard license for the hardened variant +- `source.json` — a manifest naming the upstream owner, repository, version, and commit, along with the policy SHAs applied + +Each version branch contains: + +- `HARDENING.md` — the authoritative, per-action record of what was checked, what was fixed, and how, including the policy SHA that pins the exact ruleset applied - `action.yml` or `action.yaml` — the hardened action definition, preserving upstream inputs and outputs with fixes applied +- `attestations/provenance.intoto.jsonl` — a signed [SLSA provenance](https://slsa.dev/provenance/v1) attestation naming the upstream repository and commit, the ruleset version, the build times, and a SHA-256 digest for every file in the hardened action - `LICENSE_CHAINGUARD` — the Chainguard license for the hardened variant -- `source.json` and `published.json` — manifests pointing at the upstream source and the upstream version being tracked (not yet present in all repos; some older repos don't include them) -- Some actions also include documentation from upstream that you can adapt to use with the Chainguard hardened version +- The upstream action's own files, including its license and any documentation you can adapt for the hardened version -Then, the version branches in the hardened action repos contain the hardened actions. +Because `HARDENING.md` and the attestation are per-version, read them on the version branch you plan to use rather than on the main branch. ## The continuous re-hardening process Chainguard Actions are continuously re-hardened: - When upstream publishes a new version, the pipeline re-runs and publishes a new hardened version -- When the hardening ruleset is updated, affected actions are re-reviewed against the new rules +- When the hardening ruleset is updated, every action in the catalog is re-evaluated against the new ruleset and re-hardened as needed - The `HARDENING.md` report is regenerated on every hardening run, with its own policy SHA pinning the exact set of rules that were applied -## Request a new action or report an issue +Because the policy SHA is computed over the ruleset itself, any change to a rule produces a new SHA, which is what triggers the catalog-wide re-evaluation. -To request a new action, [open an issue](https://github.com/chainguard-actions/.github/issues/new?template=new-action.yml). +## Request a new action or report an issue -## Report an issue +To request an action that isn't in the catalog, [open a new action issue](https://github.com/chainguard-actions/.github/issues/new?template=new-action.yml). -If an action isn't working as expected, [open an issue](https://github.com/chainguard-actions/.github/issues/new?template=action-issue.yml) with the action reference, a description of the problem, and steps to reproduce. +If an action isn't working as expected, [open an action issue](https://github.com/chainguard-actions/.github/issues/new?template=action-issue.yml) with the action reference, a description of the problem, and steps to reproduce. ## Learn more - [Chainguard Actions telemetry and privacy](/chainguard/actions/telemetry/) +- [Hardened Actions with Chainguard Guardener](/chainguard/guardener/github/actions-security/) - [Chainguard Actions product page](https://www.chainguard.dev/actions) - For other questions, [contact Chainguard](https://www.chainguard.dev/contact?utm=docs). From 4c08841fce6526e726aa940c279da6273b274e34 Mon Sep 17 00:00:00 2001 From: Matthew Helmke Date: Wed, 23 Sep 2026 07:54:46 -0500 Subject: [PATCH 2/8] Apply ACID review feedback from Carlos Panato Corrects two substantive errors and adds the facts his review surfaced (DOCS-204). - Transitive dependencies: the nested-reference rewriting is implemented and enabled in production, not "in development" as previously written. The Linear states that conclusion rested on were stale. Reframed as rolling out, since no published action references a hardened counterpart yet, and added the three scope limits: composite actions only, exact-commit counterparts only, never when ambiguous. - Document the static pre-pass as its own group of checks. It emits finding IDs that aren't in the policy ruleset, and one of them, `static-inline-injection`, is the most common finding in the catalog, so a reader could previously hit an ID this page couldn't resolve. - State that the rebuild reproduces a JavaScript action's bundle rather than refreshing it, so a hardened release ships the dependency versions upstream shipped. Say plainly that dependency vulnerability management isn't part of hardening today. - Recommend pinning to a commit SHA paired with Dependabot or Renovate, replacing the neutral tradeoff. A commit SHA is the only immutable reference in the catalog; re-hardening moves even fully qualified patch tags. A pin with no bumper is the one configuration that strands you, so the page says so. - Explain that the entitlement doesn't gate consumption and can't, name the telemetry endpoint for egress allowlists, and note that an unreachable Chainguard cannot fail a customer's workflow. - Guardener: automated migration needs `migrate.enabled: true`, which the page previously implied came with installation. Link the permissions table for administrator approval, and clarify that beta means the interface may change, not that it isn't deployed. - Soften the attestation coverage claim and state that no customer-facing verification procedure exists yet. - Fix the `hardcoded-credentials` and `unpinned-uses` rows, which omitted `aws_secret` and understated the image-reference match. - telemetry.md: remove `actor` from the stored-metadata list. The service discards that claim, so the page over-reported what Chainguard keeps. Co-Authored-By: Claude Opus 5 --- content/chainguard/actions/overview.md | 95 ++++++++++++++++++++----- content/chainguard/actions/telemetry.md | 4 +- 2 files changed, 80 insertions(+), 19 deletions(-) diff --git a/content/chainguard/actions/overview.md b/content/chainguard/actions/overview.md index 4a3d2b1ae7..85e2081729 100644 --- a/content/chainguard/actions/overview.md +++ b/content/chainguard/actions/overview.md @@ -20,10 +20,10 @@ The catalog holds more than 1,000 hardened actions. Coverage spans GitHub first- Each hardened action: -- Is built from source and evaluated through a rule-based and AI-powered hardening pipeline +- Is rebuilt from the upstream source at a pinned commit, then reviewed by a static ruleset and an AI-powered analysis pass - Has every internal `uses:` and container image reference pinned to an immutable SHA digest - Ships with a `HARDENING.md` report documenting exactly what was checked and fixed -- Ships with a signed SLSA provenance attestation recording the upstream source and the ruleset version applied +- Ships with a signed SLSA provenance attestation recording the upstream source and the ruleset version applied (releases published before signing began don't carry one) - Is re-reviewed and re-hardened whenever upstream publishes a new version or Chainguard adds a new rule Chainguard Actions protect against common threats including tag hijacking, dependency confusion, `pull_request_target` abuse, and secret exfiltration. @@ -32,19 +32,31 @@ This page provides enough to get you started. Refer to the [Chainguard Actions R ## What hardening checks and fixes -Every action in the catalog is evaluated against the same ruleset. Each rule has a finding ID that appears in the action's `HARDENING.md` report, so you can trace any change back to the rule that produced it. +Every action in the catalog goes through the same two-stage review. A deterministic static pre-pass runs first and catches patterns mechanically. An AI-powered analysis pass then evaluates the action against the policy ruleset. Findings from either stage carry an ID that appears in the action's `HARDENING.md` report, so you can trace any change back to the check that produced it. + +### Policy checks | Check | Finding IDs | Severity | What it catches | | ----- | ----------- | -------- | --------------- | -| Unpinned uses | `unpinned-uses` | High | `uses:` references and `docker://` image references that point at a mutable tag or branch instead of an immutable commit SHA or image digest. | +| Unpinned uses | `unpinned-uses` | High | A `uses:` reference or a `runs.image:` container reference pointing at a mutable tag or branch instead of an immutable commit SHA or image digest. The `docker://` prefix is optional, so `image: ghcr.io/example/tool:latest` is a finding too. | | Script injection | `script-injection` | High | Expressions such as `${{ inputs.name }}` interpolated directly into a `run:` block, where the shell can parse attacker-controlled text as commands. | | Unsafe shell | `unsafe-shell` | High | Remote content piped straight into an interpreter, such as `curl ... \| bash`. | -| Hardcoded credentials | `hardcoded-credentials` | High | Literal secrets assigned to names containing `password`, `secret`, `token`, or `api_key`. | +| Hardcoded credentials | `hardcoded-credentials` | High | Literal secrets assigned to names containing `password`, `secret`, `token`, `api_key`, or `aws_secret`. | | GitHub environment injection | `github-env-injection` | High | Untrusted values written to `$GITHUB_ENV`, `$GITHUB_PATH`, or `$GITHUB_OUTPUT` without newline sanitization, which lets an attacker inject variables into later steps. | | Suspicious run content | `suspicious-run-content` | High | Malicious patterns in `run:` blocks, including obfuscated execution, process memory access, dynamic evaluation, credential file access, outbound exfiltration, reverse shells, persistence, and environment secret scraping. | -| Permissions | `missing-permissions`, `broad-permissions` | Medium | Workflows that leave `GITHUB_TOKEN` at its default permissions, or that set `read-all` or `write-all` instead of specific scopes. | +| Permissions | `permissions`, `missing-permissions`, `broad-permissions` | Medium | Workflows that leave `GITHUB_TOKEN` at its default permissions, or that set `read-all` or `write-all` instead of specific scopes. | + +### Static pre-pass checks + +The static pre-pass complements the analysis pass by catching patterns the analysis may miss. It reports one finding per occurrence, so a report can list the same ID many times, each with its own location. `static-inline-injection` is the most common finding in the catalog for that reason. + +| Finding ID | Severity | What it catches | +| ---------- | -------- | --------------- | +| `static-inline-injection` | High | A single expression interpolated directly into a `run:` block. The finding names the expression and the step it appears in, and the fix moves the value into an `env:` map. | +| `static-unsanitized-env-write` | Medium | An unsanitized write to a GitHub environment file. | +| `invalid-yaml` | High | An action or workflow file that could not be parsed. | -The checks read the action definition (`action.yml` or `action.yaml`) and every workflow under `.github/workflows/` in the action's own repository. They don't inspect built or vendored output: `dist/`, `vendor/`, and `node_modules/` are out of scope, as are the action's test fixtures. +Both stages read the action definition (`action.yml` or `action.yaml`) and every workflow under `.github/workflows/` in the action's own repository. Neither inspects built or vendored output: `dist/`, `vendor/`, and `node_modules/` are out of scope, as are the action's test fixtures. Findings are fixed in place and the pipeline re-evaluates its own work, so a single hardening run can take several iterations before an action passes. Both the findings and the per-iteration notes are recorded in `HARDENING.md`. @@ -53,10 +65,26 @@ Findings are fixed in place and the pipeline re-evaluates its own work, so a sin Actions rarely run alone. A composite action can call other actions, and an action can fetch container images, language packages, or binaries while it runs. Chainguard hardens the references it can see in the action's source: - **Nested action and image references are pinned.** Every `uses:` reference and container image reference inside a hardened action resolves to an immutable commit SHA or image digest, with the original tag preserved as a comment. A moved upstream tag can't change what a hardened action runs. -- **The action's own workflows are hardened too.** The ruleset covers the workflows under `.github/workflows/` in the action's repository, not only the action definition, so the repository that produces the action is held to the same standard. +- **The action's own workflows are reviewed too.** Both stages of the review cover the workflows under `.github/workflows/` in the action's repository, not only the action definition. - **Missing dependencies can be onboarded.** When a hardened action depends on an action that isn't in the catalog yet, [request that action](https://github.com/chainguard-actions/.github/issues/new?template=new-action.yml) and Chainguard hardens and publishes it. -Two limits are worth knowing. Nested references are pinned to the upstream project's commit SHA, not rewritten to point at the Chainguard hardened equivalent; that rewriting is in development and isn't published yet. Language packages and binaries that an action downloads while it runs fall outside what the ruleset inspects. +### Rewriting nested references to hardened equivalents + +Pinning a nested reference to an upstream commit SHA freezes what runs, but the code it freezes is still the upstream project's. Chainguard is rolling out a dependency graph that replaces those references with the Chainguard hardened counterpart instead, also pinned by commit SHA. When a dependency is hardened and published, every action that depends on it returns to the hardening queue so its `uses:` reference can be rewritten. + +The graph is enabled in production and rewriting is rolling out across the catalog, so a given action may not have been rewritten yet. Three limits apply where it does: + +- It covers composite actions only. Node and Docker actions have no `uses:` steps to rewrite. +- It fires only when a hardened counterpart exists for that exact upstream commit. +- It never rewrites a reference when the correct counterpart is ambiguous. + +To see what a particular action references today, read its `action.yml` on the version branch you plan to use. + +### Dependencies an action installs when it runs + +Dependency vulnerability management is not part of hardening today. When Chainguard rebuilds a JavaScript action's bundle, the builder installs exactly what the upstream lockfile pins, so the hardened action ships the same dependency versions the upstream release shipped. The rebuild reproduces the bundle rather than refreshing it: if an upstream release bundled a vulnerable package, so does the hardened release. Language packages and binaries that an action downloads while it runs are likewise outside what the review inspects. + +Dependency handling is an area Chainguard is actively building out, and the nested-reference rewriting described earlier is the first piece of it. To see the full dependency graph for your own repository, including actions reached through other actions, use the `--recursive` flag described in [View the actions you are currently using](#view-the-actions-you-are-currently-using-in-a-repository). @@ -104,23 +132,41 @@ chainctl actions entitlements list $ENTITLEMENT_ID | 2026-06-18 17:33:24 UTC ``` +#### What the entitlement controls + +The entitlement records your organization's access to Chainguard Actions. It does not gate consumption of the actions themselves, and it can't: the hardened action repositories are public, and GitHub provides no mechanism to require authentication to consume a public action. + +Each hardened action runs a `runs.pre` hook that records a usage event to `https://actions.enforce.dev/actions/v1/record`. The hook returns no authorization decision, so there is nothing for the action to act on. It times out after 2 seconds and discards every error, which means Chainguard being slow or unreachable cannot fail your workflow. If your runners use an egress allowlist, add that host so the hook doesn't spend its timeout on every step. + +Refer to [Chainguard Actions telemetry and privacy](/chainguard/actions/telemetry/) for what the hook records and how to limit it. + ### Step 2: Install the Guardener GitHub App The [Chainguard Guardener](/chainguard/guardener/github/getting-started/) GitHub App is the recommended way to adopt Chainguard Actions across more than a repository or two. Once you install it and link it to your Chainguard organization, the Guardener: - Inventories the actions your workflows use across every repository it can access -- Opens and maintains a pull request that swaps in Chainguard hardened equivalents - Comments on pull requests that introduce unhardened actions, so your workflows don't drift back +- Opens and maintains a pull request that swaps in Chainguard hardened equivalents, once you enable migration To set it up: 1. Install the [Guardener GitHub App](https://github.com/apps/chainguard-guardener) on your GitHub organization. 2. Link your Chainguard organization to your GitHub organization with `chainctl guardener github link`. -3. Add a `.chainguard/actions.yaml` file to each repository you want the Guardener to work on. +3. Add a `.chainguard/actions.yaml` file to the root of each repository you want the Guardener to work on. + +Both of the last two steps matter. Installing the app changes no repository on its own, and the Actions feature stays inert until `.chainguard/actions.yaml` exists in the repository. Once it does, pull request recommendations are on by default, but automated migration pull requests need `migrate.enabled: true` set explicitly: -Installing the app doesn't change any repository on its own. Each repository opts in through its configuration file, and you can restrict the app to selected repositories when you install it. +```yaml +enabled: true +migrate: + enabled: true +``` -Refer to [Getting started with Chainguard Guardener](/chainguard/guardener/github/getting-started/) for the installation and linking steps, and to [Hardened Actions](/chainguard/guardener/github/actions-security/) for the configuration reference, the migration options, and the on-demand migration command. The Guardener GitHub App is in beta. +If installing an app in your organization needs an administrator's approval, they will be asked to approve a specific set of GitHub permissions. [Permissions the Guardener requests](/chainguard/guardener/github/getting-started/#permissions-the-guardener-requests) lists each one and why it's needed, so you can take that to them before you start. + +Refer to [Getting started with Chainguard Guardener](/chainguard/guardener/github/getting-started/) for the installation and linking steps, and to [Hardened Actions](/chainguard/guardener/github/actions-security/) for the configuration reference, the migration options, and the on-demand migration command. + +The Guardener GitHub App is in beta. It runs in production and is supported, but its features and configuration may still change. If you'd rather not install a GitHub App, you can migrate with the [cg-actions](https://github.com/chainguard-dev/cg-skills/tree/main/skills/cg-actions) skill or by hand. Both approaches are covered in [Configure your workflows to use Chainguard Actions](#configure-your-workflows-to-use-chainguard-actions). @@ -132,20 +178,29 @@ To use a Chainguard hardened action, edit your workflow's YAML configuration fil - uses: chainguard-actions/@ ``` -Action names often have the upstream organization appended to the action name for clarity, for example, `tj-actions/changed-files` becomes `tj-actions-changed-files`. This prevents two different sources of a `changed-files` action from clashing in the Chainguard Actions repository. +Repository names are prefixed with the upstream organization, so `tj-actions/changed-files` becomes `tj-actions-changed-files`. This keeps two different sources of a `changed-files` action from clashing in the Chainguard Actions organization. Search the Chainguard Actions repository, find the action you want to use, and then use the name you find there. > **Note:** Don't reference a hardened action with `@main`. The main branch of each repository holds only metadata (`README.md`, `LICENSE_CHAINGUARD`, and `source.json`). The hardened action itself lives on the version branches, so a reference to `@main` fails to resolve. +This example uses a version tag to show the mechanic, which is all that changes in your workflow. For any workflow you intend to keep, pin to a commit SHA instead, as described in [Choose how to reference an action](#choose-how-to-reference-an-action). + ## Choose how to reference an action -You can reference a hardened action by version tag or by commit SHA. The choice determines whether you receive re-hardening automatically, so make it deliberately. +Pin to a commit SHA, and pair the pin with Dependabot or Renovate: -- **Version tag**, for example `chainguard-actions/actions-checkout@v4`. Chainguard moves the tag when it republishes that version line, so you pick up re-hardening without touching your workflow. Every published build still has its own internal dependencies pinned to immutable SHAs. Tags in the hardened catalog are mutable by design. -- **Commit SHA**, for example `chainguard-actions/actions-checkout@25a1eb5aa40568ec6f8c0e58f2e809ef4270ebfa`. The reference is immutable, so every run executes identical code. You stay on that build until you bump the SHA, which means you don't receive re-hardening until you do. +```yaml +- uses: chainguard-actions/actions-checkout@ # v4 +``` + +A commit SHA is the only immutable reference in the catalog. Tags are mutable by design, and not just the floating major version. Chainguard re-hardens published versions in place and moves the tag when it does, including fully qualified patch tags, so a single upstream release can be re-hardened several times with the same tag pointing somewhere new each time. Both `@v4` and `@v4.3.1` resolve to whatever was published most recently. + +Pinning on its own isn't enough, though. A pin with no tooling behind it is the one configuration that strands you: you stay on that build, and stop receiving re-hardening, until someone updates the SHA by hand. Dependabot and Renovate both track a pinned SHA against its tag and open a pull request when the tag moves, so you receive every re-hardening as a change your own CI validates before it reaches a live workflow. That gives you more control than a mutable tag does, and costs you nothing in freshness. -If your security policy requires immutable references, pin the SHA and let Dependabot or Renovate open the bump pull requests. Otherwise, a version tag keeps you current with less work. +Pinning is also the standard Chainguard applies to the actions it hardens. The `unpinned-uses` check fails any `uses:` reference on a tag, so if you run an actions linter against your own repository, referencing a hardened action by tag will register a finding. + +Use the canonical repository name in the reference. Some catalog repositories answer to an older name through a GitHub rename redirect — `chainguard-actions/checkout` reaches `chainguard-actions/actions-checkout`, for example — but a redirect isn't something to depend on in a pinned workflow. ## Configure your workflows to use Chainguard Actions @@ -295,6 +350,10 @@ Each version branch contains: Because `HARDENING.md` and the attestation are per-version, read them on the version branch you plan to use rather than on the main branch. +Nearly every version branch in the catalog carries an attestation. A small number of older releases were published before Chainguard began signing them, so if a version branch has no `attestations/` directory, treat that release as unverifiable rather than as verified. + +Chainguard doesn't publish a customer-facing verification procedure yet. Verification requires the signing key's fingerprint, which isn't published, so there is no complete recipe to follow today. Tooling for this is planned. In the meantime the attestation is still useful as a record: it names the upstream commit the release was built from and the ruleset version that was applied. + ## The continuous re-hardening process Chainguard Actions are continuously re-hardened: diff --git a/content/chainguard/actions/telemetry.md b/content/chainguard/actions/telemetry.md index 9c4d14742c..b7dc10139b 100644 --- a/content/chainguard/actions/telemetry.md +++ b/content/chainguard/actions/telemetry.md @@ -28,7 +28,9 @@ We collect this data for two reasons: What we collect depends on whether your workflow grants `id-token: write`: - **Without `id-token: write`**: we record your repository name, a timestamp, and an "unverified" flag. -- **With `id-token: write`**: the hook mints a GitHub OIDC token scoped to the `actions.chainguard.dev` audience and sends it so we can verify the record. From that token we store metadata: repository, actor, ref, sha, workflow path, repository visibility, and run identifiers. +- **With `id-token: write`**: the hook mints a GitHub OIDC token scoped to the `actions.chainguard.dev` audience and sends it so we can verify the record. From that token we store metadata: repository, ref, sha, workflow path, repository visibility, and run identifiers. + +We do not store who triggered the run. The OIDC token identifies the account that started the workflow, and our service discards that claim where it assembles the usage event, so the actor never reaches any of our storage. The hook never grants itself `id-token: write`. It only uses the permission if your workflow already grants it. If you would rather we receive only your repository name, do not grant `id-token: write` to that job. From 50fb34491d352cb6ba72721dfe206da8bf94306b Mon Sep 17 00:00:00 2001 From: Matthew Helmke Date: Wed, 23 Sep 2026 08:24:25 -0500 Subject: [PATCH 3/8] Correct the stale example SHA on the Actions overview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documented output of `gh api ... commits/v47 --jq '.sha'` was `25a1eb5aa…`, captured before that tag moved. The current value is `4b4bd2ed9…`. The value was never wrong, it went stale — re-hardening a published version force-moves its tag, which is the behavior this page now documents. Printing a fixed digest as the answer undercuts that, so the example is updated and a note tells readers to run the command instead of copying the digest. Found while reviewing the catalog org README for DOCS-205, which carries the same stale value. Co-Authored-By: Claude Opus 5 --- content/chainguard/actions/overview.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/content/chainguard/actions/overview.md b/content/chainguard/actions/overview.md index 85e2081729..38bbbe12f7 100644 --- a/content/chainguard/actions/overview.md +++ b/content/chainguard/actions/overview.md @@ -246,7 +246,7 @@ gh api repos/chainguard-actions/tj-actions-changed-files/commits/v47 --jq '.sha' ``` ```output -25a1eb5aa40568ec6f8c0e58f2e809ef4270ebfa +4b4bd2ed96c7629e1c911f97f2390b91e1362735 ``` For the short SHA digest: @@ -256,15 +256,17 @@ gh api repos/chainguard-actions/tj-actions-changed-files/commits/v47 --jq '.sha[ ``` ```output -25a1eb5 +4b4bd2e ``` The resulting `uses:` line with the full SHA digest: ```yaml -- uses: chainguard-actions/tj-actions-changed-files@25a1eb5aa40568ec6f8c0e58f2e809ef4270ebfa # v47 +- uses: chainguard-actions/tj-actions-changed-files@4b4bd2ed96c7629e1c911f97f2390b91e1362735 # v47 ``` +Run the command rather than copying the digest shown here. Because Chainguard re-hardens a published version in place and moves its tag, the SHA a version tag resolves to changes each time that version is re-hardened. + ### Update your allowed-actions list. If your GitHub organization or repository restricts which actions can run (**Settings > Actions > General > Allow select actions**), add `chainguard-actions/*` to the allowed patterns. Without this, workflows fail with a policy error on first run. From 0fec8247aefe52ee10fd6670da8a7bcaf09ddffb Mon Sep 17 00:00:00 2001 From: Matthew Helmke Date: Wed, 23 Sep 2026 08:38:17 -0500 Subject: [PATCH 4/8] Point readers to the catalog org for browsing, not for details The page sent readers to the chainguard-actions README "for deeper technical details and some example migrations." That README is being trimmed to a landing page that points back here (DOCS-205), so the reference would have become circular. It now says what the org is actually good for: finding a specific action and reading its hardening report. Co-Authored-By: Claude Opus 5 --- content/chainguard/actions/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/chainguard/actions/overview.md b/content/chainguard/actions/overview.md index 38bbbe12f7..cb2a8febe4 100644 --- a/content/chainguard/actions/overview.md +++ b/content/chainguard/actions/overview.md @@ -28,7 +28,7 @@ Each hardened action: Chainguard Actions protect against common threats including tag hijacking, dependency confusion, `pull_request_target` abuse, and secret exfiltration. -This page provides enough to get you started. Refer to the [Chainguard Actions README](https://github.com/chainguard-actions) in GitHub for deeper technical details and some example migrations. +This page provides enough to get you started. Browse the [Chainguard Actions organization](https://github.com/chainguard-actions) to find a specific action and read its hardening report. ## What hardening checks and fixes From d70141761a8791f249c8ebe22ad0a05ada7a4dd6 Mon Sep 17 00:00:00 2001 From: Matthew Helmke Date: Wed, 23 Sep 2026 11:57:27 -0500 Subject: [PATCH 5/8] edits from review --- content/chainguard/actions/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/chainguard/actions/overview.md b/content/chainguard/actions/overview.md index cb2a8febe4..0aaf6f3888 100644 --- a/content/chainguard/actions/overview.md +++ b/content/chainguard/actions/overview.md @@ -20,7 +20,7 @@ The catalog holds more than 1,000 hardened actions. Coverage spans GitHub first- Each hardened action: -- Is rebuilt from the upstream source at a pinned commit, then reviewed by a static ruleset and an AI-powered analysis pass +- Pulls the upstream source at a pinned commit, reviews it using a static ruleset and an AI-powered analysis pass to harden - Has every internal `uses:` and container image reference pinned to an immutable SHA digest - Ships with a `HARDENING.md` report documenting exactly what was checked and fixed - Ships with a signed SLSA provenance attestation recording the upstream source and the ruleset version applied (releases published before signing began don't carry one) From c02ba501d497db4d280bea1607a11803bebbf358 Mon Sep 17 00:00:00 2001 From: Matthew Helmke Date: Fri, 25 Sep 2026 12:01:59 -0500 Subject: [PATCH 6/8] Adopt the Guardener naming and anchor from upstream Rebasing onto main brought in two changes that this branch's new text predates: product naming corrected to the marketing style guide (#4067) and internal links pointed at canonical paths (#4068). - "Chainguard Guardener" becomes "Guardener". The style guide is explicit that the name is bare, with no article and no "Chainguard" prefix, and upstream applied that everywhere else. - The link to the Guardener permissions table was pointing at `#permissions-the-guardener-requests`. Upstream renamed that heading to "Permissions Guardener requests", so the anchor no longer resolved. The rebase itself picked up two upstream fixes automatically: the `chainctl` install link is now the canonical `/platform/` path, and "each Chainguard Action" is now "each hardened action", since the name is plural. Co-Authored-By: Claude Opus 5 --- content/chainguard/actions/overview.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/chainguard/actions/overview.md b/content/chainguard/actions/overview.md index 0aaf6f3888..d4eb548f01 100644 --- a/content/chainguard/actions/overview.md +++ b/content/chainguard/actions/overview.md @@ -142,7 +142,7 @@ Refer to [Chainguard Actions telemetry and privacy](/chainguard/actions/telemetr ### Step 2: Install the Guardener GitHub App -The [Chainguard Guardener](/chainguard/guardener/github/getting-started/) GitHub App is the recommended way to adopt Chainguard Actions across more than a repository or two. Once you install it and link it to your Chainguard organization, the Guardener: +The [Guardener](/chainguard/guardener/github/getting-started/) GitHub App is the recommended way to adopt Chainguard Actions across more than a repository or two. Once you install it and link it to your Chainguard organization, Guardener: - Inventories the actions your workflows use across every repository it can access - Comments on pull requests that introduce unhardened actions, so your workflows don't drift back @@ -152,7 +152,7 @@ To set it up: 1. Install the [Guardener GitHub App](https://github.com/apps/chainguard-guardener) on your GitHub organization. 2. Link your Chainguard organization to your GitHub organization with `chainctl guardener github link`. -3. Add a `.chainguard/actions.yaml` file to the root of each repository you want the Guardener to work on. +3. Add a `.chainguard/actions.yaml` file to the root of each repository you want Guardener to work on. Both of the last two steps matter. Installing the app changes no repository on its own, and the Actions feature stays inert until `.chainguard/actions.yaml` exists in the repository. Once it does, pull request recommendations are on by default, but automated migration pull requests need `migrate.enabled: true` set explicitly: @@ -162,9 +162,9 @@ migrate: enabled: true ``` -If installing an app in your organization needs an administrator's approval, they will be asked to approve a specific set of GitHub permissions. [Permissions the Guardener requests](/chainguard/guardener/github/getting-started/#permissions-the-guardener-requests) lists each one and why it's needed, so you can take that to them before you start. +If installing an app in your organization needs an administrator's approval, they will be asked to approve a specific set of GitHub permissions. [Permissions Guardener requests](/chainguard/guardener/github/getting-started/#permissions-guardener-requests) lists each one and why it's needed, so you can take that to them before you start. -Refer to [Getting started with Chainguard Guardener](/chainguard/guardener/github/getting-started/) for the installation and linking steps, and to [Hardened Actions](/chainguard/guardener/github/actions-security/) for the configuration reference, the migration options, and the on-demand migration command. +Refer to [Getting started with Guardener](/chainguard/guardener/github/getting-started/) for the installation and linking steps, and to [Hardened Actions](/chainguard/guardener/github/actions-security/) for the configuration reference, the migration options, and the on-demand migration command. The Guardener GitHub App is in beta. It runs in production and is supported, but its features and configuration may still change. @@ -375,6 +375,6 @@ If an action isn't working as expected, [open an action issue](https://github.co ## Learn more - [Chainguard Actions telemetry and privacy](/chainguard/actions/telemetry/) -- [Hardened Actions with Chainguard Guardener](/chainguard/guardener/github/actions-security/) +- [Hardened Actions with Guardener](/chainguard/guardener/github/actions-security/) - [Chainguard Actions product page](https://www.chainguard.dev/actions) - For other questions, [contact Chainguard](https://www.chainguard.dev/contact?utm=docs). From 50e1242975b664623797049ea15df9a7dd36bccf Mon Sep 17 00:00:00 2001 From: Matthew Helmke Date: Fri, 25 Sep 2026 12:09:55 -0500 Subject: [PATCH 7/8] Fix the list-stem agreement in the hardening bullet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Under "Each hardened action:", the first bullet read "Pulls the upstream source at a pinned commit, reviews it using..." which made the action the thing doing the pulling and reviewing. The pipeline does that. Keeps the point of the earlier edit — not saying the action is "rebuilt from source", which reads as though the rebuild improves the bundle — and restores agreement with the stem and with the final bullet, which is also passive. Co-Authored-By: Claude Opus 5 --- content/chainguard/actions/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/chainguard/actions/overview.md b/content/chainguard/actions/overview.md index d4eb548f01..32681babb3 100644 --- a/content/chainguard/actions/overview.md +++ b/content/chainguard/actions/overview.md @@ -20,7 +20,7 @@ The catalog holds more than 1,000 hardened actions. Coverage spans GitHub first- Each hardened action: -- Pulls the upstream source at a pinned commit, reviews it using a static ruleset and an AI-powered analysis pass to harden +- Is pulled from the upstream source at a pinned commit, then reviewed by a static ruleset and an AI-powered analysis pass - Has every internal `uses:` and container image reference pinned to an immutable SHA digest - Ships with a `HARDENING.md` report documenting exactly what was checked and fixed - Ships with a signed SLSA provenance attestation recording the upstream source and the ruleset version applied (releases published before signing began don't carry one) From 06e9e993c097463d98cb678db04ea0a5806b8148 Mon Sep 17 00:00:00 2001 From: Tyler Paxton Date: Fri, 2 Oct 2026 13:10:40 -0700 Subject: [PATCH 8/8] Correct the telemetry hook description for composite and Docker actions The hook is a runs.pre script only in JavaScript actions. Composite actions run it as their first step, and Docker actions don't include it. Also moves the egress allowlist tip onto the telemetry page. Co-Authored-By: Claude Opus 5.5 --- content/chainguard/actions/overview.md | 2 +- content/chainguard/actions/telemetry.md | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/content/chainguard/actions/overview.md b/content/chainguard/actions/overview.md index 32681babb3..dd3466a388 100644 --- a/content/chainguard/actions/overview.md +++ b/content/chainguard/actions/overview.md @@ -136,7 +136,7 @@ chainctl actions entitlements list The entitlement records your organization's access to Chainguard Actions. It does not gate consumption of the actions themselves, and it can't: the hardened action repositories are public, and GitHub provides no mechanism to require authentication to consume a public action. -Each hardened action runs a `runs.pre` hook that records a usage event to `https://actions.enforce.dev/actions/v1/record`. The hook returns no authorization decision, so there is nothing for the action to act on. It times out after 2 seconds and discards every error, which means Chainguard being slow or unreachable cannot fail your workflow. If your runners use an egress allowlist, add that host so the hook doesn't spend its timeout on every step. +Most hardened actions run a hook that records a usage event to `https://actions.enforce.dev/actions/v1/record`: a `pre` script in JavaScript actions, or the first step in composite actions. Docker actions don't include it. The hook returns no authorization decision, so there is nothing for the action to act on. It times out after 2 seconds and discards every error, which means Chainguard being slow or unreachable cannot fail your workflow. If your runners use an egress allowlist, add that host so the hook doesn't spend its timeout on every step. Refer to [Chainguard Actions telemetry and privacy](/chainguard/actions/telemetry/) for what the hook records and how to limit it. diff --git a/content/chainguard/actions/telemetry.md b/content/chainguard/actions/telemetry.md index b7dc10139b..7079b9abe4 100644 --- a/content/chainguard/actions/telemetry.md +++ b/content/chainguard/actions/telemetry.md @@ -14,7 +14,9 @@ weight: 20 toc: true --- -Every Chainguard hardened action runs a best-effort "phone-home" pre-hook that records a usage event to `https://actions.enforce.dev/actions/v1/record`. The hook is fire-and-forget, with a 2 second timeout that fails open, so it cannot break your build. +Most Chainguard hardened actions run a best-effort "phone-home" hook that records a usage event to `https://actions.enforce.dev/actions/v1/record`. JavaScript actions run it as a `pre` script, and composite actions run it as their first step. Docker actions don't include the hook, and some JavaScript and composite actions don't either. The hook is fire-and-forget, with a 2 second timeout that fails open, so it cannot break your build. + +If your runners use an egress allowlist, add `actions.enforce.dev` so the hook doesn't wait out its timeout on every step that uses a hardened action. ## Why we collect this data