Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
id: ku-131
title: 'setup-node mirror input is only a download fallback — node pulled from github.com when version is found in manifest'
category: known-unsolved
severity: limitation
tags:
- setup-node
- mirror
- artifactory
- nexus
- self-hosted
- known-limitation
- node-distribution
- air-gapped
patterns:
- regex: 'Downloading node from.*github\.com|node-versions.*releases.*download'
flags: 'i'
- regex: 'Found in cache.*node|Resolved node version.*from manifest'
flags: 'i'
error_messages:
- '(no error — node is silently downloaded from github.com instead of the configured mirror URL)'
- 'Downloading node from https://github.com/actions/node-versions/releases/download/...'
root_cause: |
The `mirror` and `mirror-token` inputs in `actions/setup-node` are intended to
support authenticated artifact mirrors (e.g., JFrog Artifactory, Sonatype Nexus).
However, the action's resolution logic always consults the GitHub-hosted versions
manifest FIRST. When the requested version is found in the manifest, node is
downloaded directly from `github.com/actions/node-versions` — the mirror URL is
never used.

The mirror is only consulted in these cases:
1. The requested version is NOT present in the GitHub versions manifest (e.g., nightly,
RC versions, custom versions).
2. The primary download from GitHub fails (network error, timeout).

This means that for all stable Node.js releases (which are all in the manifest),
setup-node bypasses the configured mirror entirely. Organizations using mirrors
for security compliance, bandwidth optimization, or air-gapped environments cannot
force setup-node to use their mirror as the primary download source.

This is by design per setup-node maintainers (confirmed in issue #1412): "The mirror
input is intended as an alternative source and is used only as a fallback when the
required version is not present in the manifest or cannot be downloaded."
fix: |
There is no supported way to force setup-node to always download from a mirror for
versions present in the GitHub manifest. The following workarounds exist:

Option A — Block github.com at the network level (forces mirror fallback):
When the primary download fails due to network policy, setup-node falls back to
the mirror automatically. Requires network-level controls outside of GitHub Actions.

Option B — Pre-install node in a custom runner image:
Bundle the required Node.js version into your self-hosted runner or ARC container
image; setup-node will detect the cached version and skip the download entirely.

Option C — Use runner cache with a pre-populated tool cache:
Place node binaries in the runner's RUNNER_TOOL_CACHE directory; setup-node skips
download when the exact version is found in the tool cache.

Track upstream: actions/setup-node#1412 — there is no current ETA for mirror-first support.
fix_code:
- language: yaml
label: 'Current behavior — mirror is configured but node still downloads from github.com'
code: |
- uses: actions/setup-node@v4
with:
node-version: '22.20.0'
mirror: 'https://artifactory.example.com/nodejs-remote'
mirror-token: ${{ secrets.MIRROR_TOKEN }}
# NOTE: For stable versions in the manifest, this still downloads from
# github.com/actions/node-versions — the mirror is NOT used as primary source.
- language: yaml
label: 'Workaround — pre-populate runner tool cache to skip download entirely'
code: |
# On your self-hosted runner or ARC image, pre-download node binaries to:
# $RUNNER_TOOL_CACHE/node/<version>/<arch>/
#
# setup-node checks this directory first and skips network download
# if the exact version+arch is found, regardless of mirror configuration.
prevention:
- 'Do not rely on the mirror input for compliance enforcement — it is a fallback only'
- 'For true mirror-first enforcement, block outbound access to github.com at the network level'
- 'Use pre-built runner images with node bundled in RUNNER_TOOL_CACHE for air-gapped environments'
- 'Track actions/setup-node#1412 for upstream progress on mirror-as-primary support'
docs:
- url: 'https://github.com/actions/setup-node/issues/1412'
label: 'setup-node#1412 — Node distributions not pulled from authenticated mirrors'
- url: 'https://github.com/actions/setup-node#usage'
label: 'setup-node usage documentation — mirror input'
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
id: re-480
title: 'envsubst: command not found on ARC and ubuntu-slim runners — gettext package not pre-installed'
category: runner-environment
severity: error
tags:
- arc
- ubuntu-slim
- envsubst
- gettext
- self-hosted
- container-runner
- cosign-installer
patterns:
- regex: 'envsubst: command not found'
flags: 'i'
- regex: 'line \d+: envsubst: not found|envsubst.*No such file or directory'
flags: 'i'
error_messages:
- '/home/runner/_work/_temp/0de125e0-863d-4a86-bd04-b315399dc938.sh: line 3: envsubst: command not found'
- 'envsubst: command not found'
- 'line 3: envsubst: not found'
root_cause: |
The `envsubst` binary (part of the GNU `gettext` package) substitutes environment
variable references in text templates. It is pre-installed on standard GitHub-hosted
runners (ubuntu-22.04, ubuntu-24.04) but is absent from:

- GitHub Actions Runner Controller (ARC) container images
(`ghcr.io/actions/actions-runner`) — minimal container with no `gettext`
- `ubuntu-slim` GitHub-hosted runner — stripped Ubuntu 24.04 image that omits
many packages present in the standard ubuntu-24.04 runner

Several popular GitHub Actions invoke `envsubst` internally during their setup
steps, including `sigstore/cosign-installer@v4`. When any such action runs on ARC
or ubuntu-slim, the step immediately fails with "command not found" even though the
workflow configuration looks correct and works on standard runners.

The `gettext-base` Debian package (which provides `envsubst`) is a ~2 MB install
with no complex dependencies — it is straightforward to add as a pre-step.
fix: |
Install the `gettext-base` apt package as an early workflow step before any action
that requires `envsubst`. For ARC, consider building a custom runner image with
`gettext-base` pre-installed to avoid the per-job overhead.
fix_code:
- language: yaml
label: 'Add gettext-base install step before actions that use envsubst (ARC or ubuntu-slim)'
code: |
jobs:
build:
runs-on: ubuntu-slim # or your ARC runner label
steps:
- name: Install gettext-base (provides envsubst)
run: sudo apt-get install -y --no-install-recommends gettext-base

- name: Install cosign
uses: sigstore/cosign-installer@v4

- name: Checkout
uses: actions/checkout@v4
- language: yaml
label: 'Alternative — use a custom ARC base image with gettext pre-installed'
code: |
# In your ARC RunnerDeployment spec:
# spec:
# template:
# spec:
# containers:
# - name: runner
# image: your-org/custom-runner:latest # built with gettext-base
#
# Dockerfile:
# FROM ghcr.io/actions/actions-runner:latest
# RUN sudo apt-get update && sudo apt-get install -y --no-install-recommends gettext-base
prevention:
- 'Before using ubuntu-slim or ARC runners, audit the ubuntu-slim-Readme.md to verify all required tools are pre-installed'
- 'Add a workflow-level check step: command -v envsubst || sudo apt-get install -y gettext-base'
- 'Build custom ARC runner images with gettext-base, jq, curl, and other commonly-needed tools'
- 'File issues upstream with actions that call envsubst without checking for its presence'
docs:
- url: 'https://github.com/sigstore/cosign-installer/issues/222'
label: 'cosign-installer#222 — envsubst not found on ARC and ubuntu-slim'
- url: 'https://github.com/actions/runner-images/blob/main/images/ubuntu-slim/ubuntu-slim-Readme.md'
label: 'ubuntu-slim software manifest — check pre-installed packages'
- url: 'https://manpages.debian.org/unstable/gettext-base/envsubst.1.en.html'
label: 'envsubst man page — GNU gettext-base package'
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
id: re-481
title: 'ubuntu-slim runner ships git 2.43.0 instead of documented 2.52+ due to missing PPA pin'
category: runner-environment
severity: warning
tags:
- ubuntu-slim
- git
- version-mismatch
- apt
- ppa
- container-runner
- regression
patterns:
- regex: 'git version 2\.43\.'
flags: 'i'
- regex: 'fatal: unknown option|error: unknown switch|unrecognized argument'
flags: 'i'
error_messages:
- 'git version 2.43.0'
- 'fatal: unknown option'
- 'error: unknown switch'
root_cause: |
The `ubuntu-slim` runner image is built from a minimal `ubuntu:24.04` Docker base.
Unlike the standard `ubuntu-24.04` GitHub-hosted runner, ubuntu-slim's git install
script does not consistently pin to the `git-core` Launchpad PPA. When the apt
resolver falls back to the Ubuntu 24.04 LTS base repository, it installs git 2.43.0
— the LTS-pinned version in the Ubuntu focal/noble archive — rather than the 2.52+
version distributed via the PPA and documented in the ubuntu-slim README.

The ubuntu-slim README states git 2.52.0 is installed, but the actual installed
version since image 20260504.56.2 is 2.43.0. This regression was introduced between:
- 20260413.54.1 — git 2.53.0 (from PPA, matches README)
- 20260504.56.2 — git 2.43.0 (from Ubuntu base apt, README out of date)

Workflows that use git features introduced between 2.44 and 2.52 fail with opaque
"unknown option" or "unknown switch" errors on ubuntu-slim, while the same workflow
succeeds on ubuntu-24.04 standard runners. As of June 2026, the issue is unresolved
and the ubuntu-slim image README is inaccurate.
fix: |
Install git explicitly from the git-core PPA as an early workflow step on ubuntu-slim.
This overrides the downgraded system git (2.43.0) with the current PPA version (2.52+).
fix_code:
- language: yaml
label: 'Upgrade git from PPA on ubuntu-slim to get 2.52+'
code: |
jobs:
build:
runs-on: ubuntu-slim
steps:
- name: Upgrade git from PPA (ubuntu-slim ships 2.43.0, not 2.52+)
run: |
sudo add-apt-repository ppa:git-core/ppa -y
sudo apt-get update -q
sudo apt-get install -y --no-install-recommends git
git --version # verify: expect 2.52+ or later

- uses: actions/checkout@v4
# ... rest of workflow
- language: yaml
label: 'Diagnostic — detect git version mismatch at start of workflow'
code: |
- name: Check git version
run: |
GIT_VER=$(git --version | awk '{print $3}')
echo "Git version: $GIT_VER"
# ubuntu-slim may report 2.43.0 instead of expected 2.52+
# If you need git >= 2.44, install from PPA before using git features
prevention:
- 'Always run git --version early in ubuntu-slim workflows to detect the downgraded version'
- 'Do not assume ubuntu-slim has the same git version as ubuntu-24.04 standard runners'
- 'Pin git from git-core/ppa if your workflow uses any feature added in git 2.44 or later'
- 'Monitor actions/runner-images#14014 for the official fix to the ubuntu-slim git version'
- 'Cross-reference the ubuntu-slim README version claims against actual installed versions'
docs:
- url: 'https://github.com/actions/runner-images/issues/14014'
label: 'runner-images#14014 — ubuntu-slim git downgraded to 2.43.0'
- url: 'https://github.com/actions/runner-images/blob/main/images/ubuntu-slim/ubuntu-slim-Readme.md'
label: 'ubuntu-slim software manifest (check actual vs documented versions)'
- url: 'https://launchpad.net/~git-core/+archive/ubuntu/ppa'
label: 'git-core PPA — provides git 2.52+ for Ubuntu'
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
id: sf-216
title: 'Secret containing $ character silently expands in double-quoted shell string, passing wrong value to command'
category: silent-failures
severity: silent-failure
tags:
- secrets
- shell-expansion
- dollar-sign
- double-quotes
- run-step
- shell-injection
patterns:
- regex: 'keystore password was incorrect|keystorepassword was incorrect'
flags: 'i'
- regex: 'password.*incorrect.*IOException|BadPaddingException|UnrecoverableKeyException'
flags: 'i'
- regex: '\$\{\{\s*secrets\.[A-Z_]+\s*\}\}'
flags: 'i'
error_messages:
- 'keytool error: java.io.IOException: keystore password was incorrect'
- 'javax.crypto.BadPaddingException: Given final block not properly padded'
- 'java.security.UnrecoverableKeyException: failed to decrypt safe contents entry'
- 'error: incorrect password'
root_cause: |
When a secret contains a `$` character and is interpolated inline via
`${{ secrets.NAME }}` inside a double-quoted shell string in a `run:` step,
Bash interprets the substring after the `$` as a shell variable name. Because
that shell variable is typically unset, it expands to an empty string, silently
truncating or mutating the secret value. The command then runs with the wrong
credential — no runner error is emitted; the failure only appears as an
authentication or decryption error from the downstream tool.

Example: secret value `UHh&**&$k8748848923jhHH` used as:
-storepass "${{ secrets.KEYSTORE_PASSWORD }}"
becomes:
-storepass "UHh&**&"
because `$k8748848923jhHH` expands to empty string in the shell.

The runner substitutes the literal secret value into the YAML first (replacing
`${{ secrets.KEYSTORE_PASSWORD }}`), and then Bash processes the resulting string
as a normal double-quoted argument — so any `$WORD` inside still triggers shell
variable expansion.
fix: |
Pass secrets to run steps via the `env:` block and reference them as regular
shell environment variables. This prevents the runner-substituted value from
being double-processed by the shell.

Option A — env var pattern (recommended for any secret with special characters):
env:
KEYSTORE_PASS: ${{ secrets.KEYSTORE_PASSWORD }}
run: keytool -storepass "$KEYSTORE_PASS"

Option B — single-quote wrap (prevents shell variable expansion):
run: keytool -storepass '${{ secrets.KEYSTORE_PASSWORD }}'
# Note: single quotes are literal in shell; ${{ ... }} is resolved by the
# runner before the shell sees the string, so this is safe.

Option A (env var) is strongly preferred because it also protects against
glob expansion, word splitting, and other shell metacharacter issues in secrets.
fix_code:
- language: yaml
label: 'Wrong — inline double-quoted interpolation allows $-expansion on secret value'
code: |
- name: Sign release
run: |
keytool -list -v \
-keystore release-key.jks \
-storepass "${{ secrets.KEYSTORE_PASSWORD }}"
# PROBLEM: if KEYSTORE_PASSWORD contains $, Bash expands it silently
- language: yaml
label: 'Correct — pass secret as env var to prevent double shell expansion'
code: |
- name: Sign release
env:
KEYSTORE_PASS: ${{ secrets.KEYSTORE_PASSWORD }}
run: |
keytool -list -v \
-keystore release-key.jks \
-storepass "$KEYSTORE_PASS"
# SAFE: $KEYSTORE_PASS is a plain env var with no nested $ interpretation
prevention:
- 'Never interpolate secrets inline inside double-quoted shell strings — always use env: block'
- 'Audit all run: steps that use ${{ secrets.* }} inside "..." strings and migrate to env var pattern'
- 'Test secrets that contain $, !, ", \, backtick, or space characters with extra care'
- 'Use the GitHub Actions secret scanner / security review to flag direct secret interpolation in run steps'
docs:
- url: 'https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-secrets'
label: 'Security hardening for GitHub Actions — using secrets safely'
- url: 'https://github.com/actions/runner/issues/4500'
label: 'actions/runner#4500 — runner modifies secrets containing $ in double-quoted shell commands'
Loading