diff --git a/errors/caching-artifacts/cache-ghes-tls-certificate-verify-failed.yml b/errors/caching-artifacts/cache-ghes-tls-certificate-verify-failed.yml new file mode 100644 index 0000000..d830049 --- /dev/null +++ b/errors/caching-artifacts/cache-ghes-tls-certificate-verify-failed.yml @@ -0,0 +1,116 @@ +id: caching-artifacts-149 +title: 'actions/cache fails on GHES with TLS certificate verification error — unable to verify the first certificate' +category: caching-artifacts +severity: error +tags: + - ghes + - tls + - ssl + - certificate + - self-signed + - enterprise + - node-tls + - cache-restore +patterns: + - regex: 'unable to verify the first certificate' + flags: 'i' + - regex: 'downloadCache failed.*unable to verify' + flags: 'i' + - regex: 'Failed to restore.*certificate.*verify|Failed to save.*certificate.*verify' + flags: 'i' + - regex: 'Error: UNABLE_TO_VERIFY_LEAF_SIGNATURE' + flags: 'i' + - regex: 'RequestError.*certificate.*has expired|RequestError.*self.signed certificate' + flags: 'i' +error_messages: + - 'Warning: Failed to restore: downloadCache failed: unable to verify the first certificate' + - 'Error: UNABLE_TO_VERIFY_LEAF_SIGNATURE' + - 'RequestError: unable to verify the first certificate' + - 'Error: self signed certificate in certificate chain' +root_cause: | + `actions/cache` uses Node.js HTTP clients to communicate with the GitHub Actions cache + service endpoint. On GitHub Enterprise Server (GHES) deployments that use self-signed + TLS certificates or certificates issued by a private Certificate Authority (CA), Node.js + cannot verify the TLS certificate chain by default. + + Node.js does not use the system's native OS certificate store on Linux/macOS — it ships + with its own bundled CA bundle. If the GHES instance's TLS certificate is not signed by + a CA in Node.js's bundle (e.g., it's self-signed, or signed by an internal CA not in + the Mozilla bundle), all HTTP requests from the runner's Node.js process will fail with + "unable to verify the first certificate" or "UNABLE_TO_VERIFY_LEAF_SIGNATURE". + + This affects: + - `actions/cache@v3`, `actions/cache@v4`, `actions/cache@v5` on GHES + - Any JavaScript action that makes HTTPS requests to the GHES instance + - Occurs on GHES versions that use self-signed certificates or internal CAs +fix: | + Set the `NODE_EXTRA_CA_CERTS` environment variable to point to the CA bundle that includes + the private CA certificate chain. Node.js reads this env var to extend its trusted CA list. + + 1. Export the GHES TLS certificate chain (PEM format) from your GHES admin console or + via `openssl s_client -showcerts -connect your-ghes-host:443 < /dev/null` + + 2. Copy the PEM file to the self-hosted runner's filesystem (e.g., `/etc/ssl/ghes-ca.pem`) + + 3. Set NODE_EXTRA_CA_CERTS in the runner's environment before the cache step runs. + + Alternatively, use the `self-signed-certificate` runner configuration option in + actions-runner or set NODE_TLS_REJECT_UNAUTHORIZED=0 (NOT recommended for production + as it disables all TLS verification). +fix_code: + - language: yaml + label: 'Set NODE_EXTRA_CA_CERTS workflow-level to trust GHES self-signed certificate' + code: | + # Configure the custom CA cert at workflow level + env: + # Path to the PEM file containing your GHES private CA certificate + # The file must exist on the self-hosted runner filesystem + NODE_EXTRA_CA_CERTS: /etc/ssl/certs/ghes-ca.pem + + jobs: + build: + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + + - uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + + - name: Install dependencies + run: npm ci + - language: yaml + label: 'Set NODE_EXTRA_CA_CERTS at the runner level via .env file' + code: | + # In the runner's .env file (on the self-hosted runner host): + # NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ghes-ca.pem + # + # Or set it in the runner service environment. On Linux with systemd: + # sudo systemctl edit actions.runner...service + # [Service] + # Environment="NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ghes-ca.pem" + # + # This avoids needing to set it in every workflow file. + + jobs: + build: + runs-on: self-hosted + steps: + # NODE_EXTRA_CA_CERTS is set at the runner level — no workflow change needed + - uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} +prevention: + - 'Set NODE_EXTRA_CA_CERTS globally in the runner service environment so all JavaScript actions (not just cache) trust the GHES private CA' + - 'Use a publicly trusted TLS certificate (e.g., from Let''s Encrypt) on your GHES instance to avoid Node.js TLS verification issues entirely' + - 'Test connectivity from the runner by running `node -e "const https = require(''https''); https.get(''https://your-ghes-host/'', r => console.log(r.statusCode)).on(''error'', e => console.error(e.message))"` to confirm the cert issue before debugging the workflow' + - 'Document the required NODE_EXTRA_CA_CERTS path in your team''s runner setup guide so new runner deployments are pre-configured' +docs: + - url: 'https://github.com/actions/cache/issues/1286' + label: 'actions/cache#1286 — downloadCache failed: unable to verify the first certificate (GHES)' + - url: 'https://nodejs.org/api/cli.html#node_extra_ca_certsfile' + label: 'Node.js CLI: NODE_EXTRA_CA_CERTS documentation' + - url: 'https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server' + label: 'GitHub Enterprise Server documentation' diff --git a/errors/known-unsolved/setup-node-mirror-input-ignored-when-version-in-manifest.yml b/errors/known-unsolved/setup-node-mirror-input-ignored-when-version-in-manifest.yml new file mode 100644 index 0000000..c16dd28 --- /dev/null +++ b/errors/known-unsolved/setup-node-mirror-input-ignored-when-version-in-manifest.yml @@ -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/// + # + # 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' diff --git a/errors/known-unsolved/ubuntu-arm64-chrome-edge-not-preinstalled.yml b/errors/known-unsolved/ubuntu-arm64-chrome-edge-not-preinstalled.yml new file mode 100644 index 0000000..d3a95af --- /dev/null +++ b/errors/known-unsolved/ubuntu-arm64-chrome-edge-not-preinstalled.yml @@ -0,0 +1,125 @@ +id: known-unsolved-141 +title: 'Chrome and Edge are not preinstalled on Ubuntu ARM64 runners (ubuntu-22.04-arm, ubuntu-24.04-arm, ubuntu-26.04-arm)' +category: known-unsolved +severity: limitation +tags: + - arm64 + - chrome + - chromium + - edge + - selenium + - playwright + - ubuntu-arm + - browser-testing +patterns: + - regex: 'cannot find Chrome binary' + flags: 'i' + - regex: 'CHROMEWEBDRIVER.*?is not set|CHROMEWEBDRIVER.*?empty' + flags: 'i' + - regex: 'google-chrome.*?command not found|google-chrome-stable.*?not found' + flags: 'i' + - regex: 'chromedriver.*?not found|no such file.*?chromedriver' + flags: 'i' + - regex: 'ubuntu-(22|24|26)\.04-arm' + flags: 'i' +error_messages: + - 'cannot find Chrome binary' + - 'google-chrome: command not found' + - 'google-chrome-stable: not found' + - 'Error: Failed to launch the browser process! /usr/bin/google-chrome-stable' + - 'WebDriverException: Message: ''chromedriver'' executable needs to be in PATH' + - 'SessionNotCreatedException: Message: chrome not reachable' +root_cause: | + Ubuntu ARM64 GitHub Actions hosted runners (ubuntu-22.04-arm, ubuntu-24.04-arm, + ubuntu-26.04-arm) do NOT include Google Chrome, Chromium, Microsoft Edge, or their + respective WebDrivers (ChromeDriver, EdgeDriver) as preinstalled software. + + On x86-64 Ubuntu runners (ubuntu-22.04, ubuntu-24.04, ubuntu-26.04), Chrome, ChromeDriver, + Edge, and EdgeDriver are all preinstalled and the environment variables + CHROMEWEBDRIVER and EDGEWEBDRIVER point to valid paths. + + On ARM64 Ubuntu runners, the corresponding environment variables are set to empty strings: + CHROMEWEBDRIVER = "" + EDGEWEBDRIVER = "" + + Only Mozilla Firefox (with Geckodriver) is preinstalled on Ubuntu ARM64 runners. + + This is a known, intentional limitation of the Ubuntu ARM64 runner images as of + June 2026. Google does not publish official Chrome or ChromeDriver builds for + Linux ARM64 via their standard Debian package repository. Chromium for Linux ARM64 + is available but is not included in the runner image. + + Workflows that migrate from ubuntu-24.04 (x86) to ubuntu-24.04-arm or ubuntu-26.04-arm + and rely on Chrome-based browser testing will fail silently or with the above errors. +fix: | + There is no fully equivalent fix because Google does not officially support Chrome + for Linux ARM64 via the standard deb package. Available mitigations: + + 1. Use Firefox instead of Chrome for browser tests on ARM64 runners. Firefox is + preinstalled. Update your test framework to target Firefox when running on ARM64. + + 2. Install Chromium (unofficial ARM64 build from apt) and use chromium-driver: + sudo apt-get install -y chromium-browser chromium-driver + Note: Chromium from apt on Ubuntu may be a snap package that does not work in + headless CI environments. Test carefully. + + 3. Use Playwright with its bundled browser download: + npx playwright install chromium + Playwright distributes its own Chromium builds for Linux ARM64. + + 4. Run browser tests only on x86-64 runners and use ARM64 runners for non-browser workloads. + Use a matrix strategy to select the appropriate runner per test type. + + 5. Use a container image that pre-bundles Chrome for ARM64 (e.g., some Selenium + Docker images have ARM64 variants with Chromium). +fix_code: + - language: yaml + label: 'Use matrix to run browser tests on x86 and unit tests on ARM64' + code: | + jobs: + test: + strategy: + matrix: + include: + # Browser tests: x86 only (Chrome available) + - runner: ubuntu-24.04 + test-type: browser + # Unit tests: ARM64 (faster, no Chrome needed) + - runner: ubuntu-24.04-arm + test-type: unit + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v4 + - if: matrix.test-type == 'browser' + name: Run browser tests (Chrome available on x86) + run: npm run test:e2e + - if: matrix.test-type == 'unit' + name: Run unit tests + run: npm run test:unit + - language: yaml + label: 'Install Chromium via Playwright on ARM64 runners' + code: | + jobs: + test: + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - run: npm ci + # Playwright manages its own Chromium builds including Linux ARM64 + - run: npx playwright install chromium --with-deps + - run: npx playwright test +prevention: + - 'Check that your target ARM64 runner image supports the browser you need before migrating; run `echo $CHROMEWEBDRIVER` in a test step to verify' + - 'Use Playwright instead of Selenium/WebDriver for cross-architecture browser tests — Playwright distributes bundled Chromium for Linux ARM64' + - 'Separate browser tests from unit tests in your CI matrix so ARM64 runners handle non-browser workloads only' + - 'Monitor actions/runner-images for announcements about Chrome ARM64 support on GitHub-hosted runners' +docs: + - url: 'https://github.com/actions/runner-images/releases/tag/ubuntu24-arm64%2F20260607.22' + label: 'ubuntu-24.04-arm64 runner image release notes — Browsers and Drivers section' + - url: 'https://github.com/actions/runner-images/releases/tag/ubuntu26-arm64%2F20260604.22' + label: 'ubuntu-26.04-arm64 runner image release notes — CHROMEWEBDRIVER is empty' + - url: 'https://playwright.dev/docs/ci' + label: 'Playwright CI documentation — supports Linux ARM64 via bundled browser download' diff --git a/errors/runner-environment/arc-ubuntu-slim-envsubst-command-not-found-gettext-missing.yml b/errors/runner-environment/arc-ubuntu-slim-envsubst-command-not-found-gettext-missing.yml new file mode 100644 index 0000000..80e59cb --- /dev/null +++ b/errors/runner-environment/arc-ubuntu-slim-envsubst-command-not-found-gettext-missing.yml @@ -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' diff --git a/errors/runner-environment/checkout-ssh-key-not-available-in-docker-container-action.yml b/errors/runner-environment/checkout-ssh-key-not-available-in-docker-container-action.yml new file mode 100644 index 0000000..3182895 --- /dev/null +++ b/errors/runner-environment/checkout-ssh-key-not-available-in-docker-container-action.yml @@ -0,0 +1,120 @@ +id: runner-environment-492 +title: 'actions/checkout ssh-key not available in subsequent Docker container actions' +category: runner-environment +severity: error +tags: + - ssh + - ssh-key + - container-action + - docker + - checkout + - git-push + - private-repo +patterns: + - regex: 'Identity file.*?_temp.*?not accessible: No such file or directory' + flags: 'i' + - regex: 'No RSA host key is known for.*?and you have requested strict checking' + flags: 'i' + - regex: 'Host key verification failed' + flags: 'i' + - regex: 'Warning: Identity file.*?runner.*?work.*?_temp.*?not accessible' + flags: 'i' +error_messages: + - 'Warning: Identity file /home/runner/work/_temp/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx not accessible: No such file or directory.' + - 'No RSA host key is known for github.com and you have requested strict checking.' + - 'Host key verification failed.' + - 'fatal: Could not read from remote repository.' +root_cause: | + When `actions/checkout` is used with the `ssh-key:` input, the runner saves the SSH + private key to a temporary file inside `/home/runner/work/_temp/` and starts an + `ssh-agent` process with the key loaded. The path to the key file and the + `SSH_AUTH_SOCK` socket are set on the HOST runner environment. + + Docker container actions (actions defined with `runs: using: docker`) run inside + isolated Docker containers. The runner does NOT mount `/home/runner/work/_temp/` or + the `ssh-agent` socket into the container. As a result: + + - The SSH identity file path that was configured on the host is not accessible inside + the container. + - The `SSH_AUTH_SOCK` environment variable, even if forwarded, points to a socket + that doesn't exist inside the container. + - Any git operation that requires SSH authentication inside the container (git push, + git fetch to private repos, git submodule update) will fail with "Host key + verification failed" or "Identity file not accessible". + + This is a structural limitation of how Docker container actions are sandboxed: they + have access to the workspace (`/github/workspace`) and specific environment variables, + but do NOT have access to the host runner's SSH agent or temp file directory. + + Notably, this does NOT affect JavaScript actions (`runs: using: node20`) or + composite actions with `run:` steps — those run directly on the host and inherit + the SSH agent socket correctly. +fix: | + The SSH key set up by `actions/checkout` is not available inside Docker container + actions. Use one of these approaches: + + 1. **Avoid Docker container actions that need SSH**: Prefer JavaScript actions or + composite actions with `run:` steps for tasks that need SSH, as these inherit + the host SSH agent. + + 2. **Explicitly pass the SSH key to the container via env**: Set the SSH private key + as an environment variable and reconfigure SSH inside the container step. Note: + this only works in composite `run:` steps, not inside Docker container actions. + + 3. **Use HTTPS with token instead of SSH**: Replace SSH-based git operations with + HTTPS using the GITHUB_TOKEN. This works in any environment. + + 4. **Run git operations in a `run:` step before/after the container action**: Perform + any SSH-requiring git operations in plain shell steps which run on the host. +fix_code: + - language: yaml + label: 'Use HTTPS instead of SSH for git operations in Docker container actions' + code: | + jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Instead of ssh-key, configure git to use the GITHUB_TOKEN over HTTPS + with: + token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: true + + # A Docker container action can use HTTPS-based git (no SSH needed) + - uses: some-org/some-docker-action@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - language: yaml + label: 'Perform git operations in run: steps (not Docker actions) when SSH is needed' + code: | + jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ssh-key: ${{ secrets.DEPLOY_KEY }} + + # SSH agent IS available here (run: step on host) + - name: Git push via SSH + run: | + git config user.email "bot@example.com" + git config user.name "CI Bot" + git commit -am "update files" + git push # SSH works in run: steps + + # Docker container action AFTER the SSH operations are done + - uses: some-org/some-docker-action@v1 + # This action doesn't need SSH — it just reads the workspace +prevention: + - 'Use `token:` (HTTPS) with `actions/checkout` instead of `ssh-key:` when subsequent steps include Docker container actions that need to push/fetch' + - 'Perform all SSH-requiring git operations in `run:` shell steps, which execute on the host and inherit the SSH agent; place these before or after Docker container action steps' + - 'Check if the Docker container action you are using offers an alternative HTTPS/token-based mode for git operations' + - 'JavaScript actions and composite `run:` steps DO inherit the SSH agent correctly — prefer these over Docker container actions when SSH is needed' +docs: + - url: 'https://github.com/actions/checkout/issues/297' + label: 'actions/checkout#297 — SSH key not available in container actions' + - url: 'https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-docker-container-action' + label: 'GitHub Docs: Creating a Docker container action' + - url: 'https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions' + label: 'GitHub Docs: Workflow commands — environment files' diff --git a/errors/runner-environment/ubuntu-slim-git-version-downgraded-243-instead-of-252.yml b/errors/runner-environment/ubuntu-slim-git-version-downgraded-243-instead-of-252.yml new file mode 100644 index 0000000..6f18b3d --- /dev/null +++ b/errors/runner-environment/ubuntu-slim-git-version-downgraded-243-instead-of-252.yml @@ -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' diff --git a/errors/silent-failures/secret-dollar-sign-expands-in-double-quoted-shell-string.yml b/errors/silent-failures/secret-dollar-sign-expands-in-double-quoted-shell-string.yml new file mode 100644 index 0000000..db9f635 --- /dev/null +++ b/errors/silent-failures/secret-dollar-sign-expands-in-double-quoted-shell-string.yml @@ -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'