@htekdev/actions-debugger 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +108 -108
- package/errors/_schema.json +89 -89
- package/errors/caching-artifacts/artifact-storage-quota-exceeded.yml +118 -0
- package/errors/caching-artifacts/cache-miss.yml +56 -56
- package/errors/caching-artifacts/cache-save-cancelled-job.yml +82 -0
- package/errors/caching-artifacts/cache-v3-to-v4-breaking-changes.yml +95 -0
- package/errors/caching-artifacts/cross-repo-artifacts-not-supported.yml +102 -0
- package/errors/caching-artifacts/upload-artifact-no-files-found.yml +92 -0
- package/errors/caching-artifacts/upload-artifact-v4-breaking.yml +67 -67
- package/errors/concurrency-timing/cancel-in-progress-deploy-drops.yml +97 -0
- package/errors/concurrency-timing/jobs-cancelled-unexpectedly.yml +60 -60
- package/errors/concurrency-timing/skipped-needs-cascade.yml +103 -0
- package/errors/concurrency-timing/workflow-run-conclusion-unchecked.yml +100 -0
- package/errors/known-unsolved/composite-input-env-vars-missing.yml +91 -0
- package/errors/known-unsolved/composite-nested-outputs-null.yml +101 -0
- package/errors/known-unsolved/no-dynamic-secret-access.yml +111 -0
- package/errors/known-unsolved/no-step-level-rerun.yml +94 -0
- package/errors/known-unsolved/no-step-retry.yml +53 -53
- package/errors/permissions-auth/checkout-submodule-private-auth.yml +91 -0
- package/errors/permissions-auth/fork-pr-secrets-unavailable.yml +97 -0
- package/errors/permissions-auth/github-token-403.yml +64 -64
- package/errors/permissions-auth/github-token-protected-branch-push.yml +109 -0
- package/errors/permissions-auth/oidc-aws-failure.yml +85 -85
- package/errors/permissions-auth/oidc-azure-subject-mismatch.yml +91 -0
- package/errors/runner-environment/disk-space.yml +57 -57
- package/errors/runner-environment/docker-buildx-not-setup.yml +106 -0
- package/errors/runner-environment/macos-homebrew-path.yml +90 -0
- package/errors/runner-environment/node-runtime-deprecation.yml +56 -56
- package/errors/runner-environment/npm-ci-lockfile-mismatch.yml +112 -0
- package/errors/runner-environment/self-hosted-stale-toolcache.yml +73 -0
- package/errors/runner-environment/setup-node-version-file-missing.yml +105 -0
- package/errors/runner-environment/windows-execution-policy.yml +83 -0
- package/errors/silent-failures/add-mask-no-retroactive-masking.yml +75 -0
- package/errors/silent-failures/composite-boolean-inputs-as-strings.yml +110 -0
- package/errors/silent-failures/conditional-output-null-downstream.yml +82 -0
- package/errors/silent-failures/continue-on-error-masks-failure.yml +86 -0
- package/errors/silent-failures/github-token-no-trigger.yml +57 -57
- package/errors/silent-failures/reusable-workflow-env-secrets-empty.yml +90 -0
- package/errors/silent-failures/scheduled-workflow-disabled.yml +59 -59
- package/errors/triggers/cron-schedule-late.yml +59 -59
- package/errors/triggers/pull-request-target-rce-risk.yml +117 -0
- package/errors/triggers/workflow-not-triggering.yml +60 -60
- package/errors/triggers/workflow-run-default-branch-requirement.yml +78 -0
- package/errors/yaml-syntax/anchors-not-supported.yml +95 -0
- package/errors/yaml-syntax/dynamic-matrix-fromjson-failure.yml +99 -0
- package/errors/yaml-syntax/if-always-true.yml +52 -52
- package/errors/yaml-syntax/missing-expression-wrapper.yml +67 -0
- package/errors/yaml-syntax/needs-indirect-outputs.yml +91 -0
- package/errors/yaml-syntax/secrets-in-if.yml +55 -55
- package/errors/yaml-syntax/unexpected-yaml-key.yml +69 -69
- package/errors/yaml-syntax/working-directory-ignored-on-uses.yml +66 -0
- package/package.json +70 -67
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
id: runner-environment-011
|
|
2
|
+
title: "macOS Runner Homebrew Binaries Not on PATH After brew install"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- macos
|
|
7
|
+
- homebrew
|
|
8
|
+
- PATH
|
|
9
|
+
- runner
|
|
10
|
+
- shell
|
|
11
|
+
- binary
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "command not found.*brew"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "brew: command not found"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "/opt/homebrew/bin.*not found"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "zsh: command not found"
|
|
20
|
+
flags: "i"
|
|
21
|
+
error_messages:
|
|
22
|
+
- "zsh: command not found: <tool>"
|
|
23
|
+
- "Error: Process completed with exit code 127."
|
|
24
|
+
- "/usr/bin/env: '<tool>': No such file or directory"
|
|
25
|
+
root_cause: |
|
|
26
|
+
GitHub Actions macOS runners (including `macos-14` and `macos-15` on Apple Silicon)
|
|
27
|
+
use zsh as the default shell. Homebrew installs binaries to `/opt/homebrew/bin` on
|
|
28
|
+
Apple Silicon (`macos-14`+) and `/usr/local/bin` on Intel (`macos-13`), but these
|
|
29
|
+
paths are not always added to `$PATH` for subsequent steps automatically.
|
|
30
|
+
|
|
31
|
+
When a step runs `brew install sometool` and then a **later step** attempts to use
|
|
32
|
+
`sometool`, the binary may not be on `$PATH` because Homebrew's shellenv was never
|
|
33
|
+
sourced into the Actions runner's shell environment.
|
|
34
|
+
|
|
35
|
+
Additionally, `macos-14` moved to Apple Silicon, changing the Homebrew prefix from
|
|
36
|
+
`/usr/local` to `/opt/homebrew`, which breaks hardcoded path assumptions in scripts
|
|
37
|
+
that worked on `macos-13`.
|
|
38
|
+
fix: |
|
|
39
|
+
After installing with Homebrew, explicitly add the binary path to `$GITHUB_PATH`
|
|
40
|
+
(which persists across subsequent steps in the same job). Alternatively, use
|
|
41
|
+
`brew --prefix` to get the correct path regardless of architecture.
|
|
42
|
+
fix_code:
|
|
43
|
+
- language: yaml
|
|
44
|
+
label: "WRONG — tool installed but not accessible in next step"
|
|
45
|
+
code: |
|
|
46
|
+
jobs:
|
|
47
|
+
build:
|
|
48
|
+
runs-on: macos-latest
|
|
49
|
+
steps:
|
|
50
|
+
- name: Install tool
|
|
51
|
+
run: brew install sometool
|
|
52
|
+
|
|
53
|
+
- name: Use tool
|
|
54
|
+
run: sometool --version # Error: zsh: command not found: sometool
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: "CORRECT — add brew prefix to GITHUB_PATH"
|
|
57
|
+
code: |
|
|
58
|
+
jobs:
|
|
59
|
+
build:
|
|
60
|
+
runs-on: macos-latest
|
|
61
|
+
steps:
|
|
62
|
+
- name: Install tool
|
|
63
|
+
run: |
|
|
64
|
+
brew install sometool
|
|
65
|
+
# Add homebrew bin to path for all subsequent steps
|
|
66
|
+
echo "$(brew --prefix)/bin" >> $GITHUB_PATH
|
|
67
|
+
|
|
68
|
+
- name: Use tool
|
|
69
|
+
run: sometool --version # works
|
|
70
|
+
- language: yaml
|
|
71
|
+
label: "CORRECT — source shellenv in a single step"
|
|
72
|
+
code: |
|
|
73
|
+
jobs:
|
|
74
|
+
build:
|
|
75
|
+
runs-on: macos-latest
|
|
76
|
+
steps:
|
|
77
|
+
- name: Install and run in same step
|
|
78
|
+
run: |
|
|
79
|
+
brew install sometool
|
|
80
|
+
eval "$(brew shellenv)"
|
|
81
|
+
sometool --version
|
|
82
|
+
prevention:
|
|
83
|
+
- "After `brew install`, append `$(brew --prefix)/bin` to `$GITHUB_PATH` to persist it across steps."
|
|
84
|
+
- "Use `brew --prefix <formula>` to get formula-specific paths rather than hardcoding `/usr/local` or `/opt/homebrew`."
|
|
85
|
+
- "When upgrading from `macos-13` to `macos-14`+, audit any hardcoded `/usr/local` paths for the Homebrew prefix change."
|
|
86
|
+
docs:
|
|
87
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#adding-a-system-path"
|
|
88
|
+
label: "Adding a system path (GITHUB_PATH)"
|
|
89
|
+
- url: "https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources"
|
|
90
|
+
label: "GitHub-hosted runners — supported environments"
|
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
id: runner-environment-009
|
|
2
|
-
title: "Node.js Runtime Deprecation"
|
|
3
|
-
category: runner-environment
|
|
4
|
-
severity: warning
|
|
5
|
-
tags:
|
|
6
|
-
- node
|
|
7
|
-
- runtime
|
|
8
|
-
- deprecation
|
|
9
|
-
- marketplace-actions
|
|
10
|
-
- compatibility
|
|
11
|
-
patterns:
|
|
12
|
-
- regex: "Node\\.js 16 actions are deprecated"
|
|
13
|
-
flags: "i"
|
|
14
|
-
- regex: "Please update the following actions to use Node\\.js 20"
|
|
15
|
-
flags: "i"
|
|
16
|
-
- regex: "runs using Node 16 are deprecated"
|
|
17
|
-
flags: "i"
|
|
18
|
-
error_messages:
|
|
19
|
-
- "Node.js 16 actions are deprecated."
|
|
20
|
-
- "Please update the following actions to use Node.js 20."
|
|
21
|
-
root_cause: |
|
|
22
|
-
Some marketplace actions bundle a Node.js runtime. When GitHub deprecates an older
|
|
23
|
-
runtime such as Node 16, workflows can start emitting warnings or eventually fail if the
|
|
24
|
-
referenced action version has not been updated.
|
|
25
|
-
|
|
26
|
-
This is usually caused by pinning an old major version of an action long after the runner
|
|
27
|
-
platform has moved on.
|
|
28
|
-
fix: |
|
|
29
|
-
Upgrade the affected action to a maintained version that uses the current supported Node
|
|
30
|
-
runtime. Review pinned SHAs and major versions for checkout, setup, artifact, and cache
|
|
31
|
-
actions first because they are common sources of these warnings.
|
|
32
|
-
fix_code:
|
|
33
|
-
- language: yaml
|
|
34
|
-
label: "Upgrade action versions to Node 20-compatible releases"
|
|
35
|
-
code: |
|
|
36
|
-
steps:
|
|
37
|
-
- uses: actions/checkout@v4
|
|
38
|
-
- uses: actions/setup-node@v4
|
|
39
|
-
with:
|
|
40
|
-
node-version: 20
|
|
41
|
-
- uses: actions/upload-artifact@v4
|
|
42
|
-
with:
|
|
43
|
-
name: build-output
|
|
44
|
-
path: dist/
|
|
45
|
-
prevention:
|
|
46
|
-
- "Review GitHub Actions deprecation notices and keep marketplace action versions current."
|
|
47
|
-
- "Prefer supported major versions from official `actions/*` repositories."
|
|
48
|
-
- "Audit pinned SHAs periodically so old runtimes do not linger unnoticed."
|
|
49
|
-
docs:
|
|
50
|
-
- url: "https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-javascript-actions"
|
|
51
|
-
label: "JavaScript action runtime metadata"
|
|
52
|
-
- url: "https://docs.github.com/en/actions/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions"
|
|
53
|
-
label: "Metadata syntax for GitHub Actions"
|
|
54
|
-
source:
|
|
55
|
-
article: "https://htek.dev/articles/github-actions-debugging-guide"
|
|
56
|
-
section: "Node runtime deprecation warnings"
|
|
1
|
+
id: runner-environment-009
|
|
2
|
+
title: "Node.js Runtime Deprecation"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: warning
|
|
5
|
+
tags:
|
|
6
|
+
- node
|
|
7
|
+
- runtime
|
|
8
|
+
- deprecation
|
|
9
|
+
- marketplace-actions
|
|
10
|
+
- compatibility
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "Node\\.js 16 actions are deprecated"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "Please update the following actions to use Node\\.js 20"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "runs using Node 16 are deprecated"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "Node.js 16 actions are deprecated."
|
|
20
|
+
- "Please update the following actions to use Node.js 20."
|
|
21
|
+
root_cause: |
|
|
22
|
+
Some marketplace actions bundle a Node.js runtime. When GitHub deprecates an older
|
|
23
|
+
runtime such as Node 16, workflows can start emitting warnings or eventually fail if the
|
|
24
|
+
referenced action version has not been updated.
|
|
25
|
+
|
|
26
|
+
This is usually caused by pinning an old major version of an action long after the runner
|
|
27
|
+
platform has moved on.
|
|
28
|
+
fix: |
|
|
29
|
+
Upgrade the affected action to a maintained version that uses the current supported Node
|
|
30
|
+
runtime. Review pinned SHAs and major versions for checkout, setup, artifact, and cache
|
|
31
|
+
actions first because they are common sources of these warnings.
|
|
32
|
+
fix_code:
|
|
33
|
+
- language: yaml
|
|
34
|
+
label: "Upgrade action versions to Node 20-compatible releases"
|
|
35
|
+
code: |
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- uses: actions/setup-node@v4
|
|
39
|
+
with:
|
|
40
|
+
node-version: 20
|
|
41
|
+
- uses: actions/upload-artifact@v4
|
|
42
|
+
with:
|
|
43
|
+
name: build-output
|
|
44
|
+
path: dist/
|
|
45
|
+
prevention:
|
|
46
|
+
- "Review GitHub Actions deprecation notices and keep marketplace action versions current."
|
|
47
|
+
- "Prefer supported major versions from official `actions/*` repositories."
|
|
48
|
+
- "Audit pinned SHAs periodically so old runtimes do not linger unnoticed."
|
|
49
|
+
docs:
|
|
50
|
+
- url: "https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-javascript-actions"
|
|
51
|
+
label: "JavaScript action runtime metadata"
|
|
52
|
+
- url: "https://docs.github.com/en/actions/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions"
|
|
53
|
+
label: "Metadata syntax for GitHub Actions"
|
|
54
|
+
source:
|
|
55
|
+
article: "https://htek.dev/articles/github-actions-debugging-guide"
|
|
56
|
+
section: "Node runtime deprecation warnings"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
id: runner-environment-015
|
|
2
|
+
title: "npm ci Fails with Lockfile Mismatch or Missing package-lock.json"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- npm
|
|
7
|
+
- npm-ci
|
|
8
|
+
- lockfile
|
|
9
|
+
- package-lock.json
|
|
10
|
+
- dependencies
|
|
11
|
+
- node
|
|
12
|
+
- reproducible
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "npm ci.*can only install.*package-lock\\.json"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "Missing script.*ci"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "npm ERR! code EUSAGE"
|
|
19
|
+
flags: "i"
|
|
20
|
+
- regex: "npm warn saveError ENOENT.*package-lock\\.json"
|
|
21
|
+
flags: "i"
|
|
22
|
+
- regex: "npm ERR!.*lock file.*older npm"
|
|
23
|
+
flags: "i"
|
|
24
|
+
- regex: "EBADENGINE"
|
|
25
|
+
flags: "i"
|
|
26
|
+
error_messages:
|
|
27
|
+
- "npm error The `npm ci` command can only install with an existing package-lock.json"
|
|
28
|
+
- "npm warn saveError ENOENT: no such file or directory, open '/home/runner/work/.../package-lock.json'"
|
|
29
|
+
- "npm error `npm ci` can only install packages when your package.json and package-lock.json are in sync."
|
|
30
|
+
- "npm error Missing: <package>@<version> from lock file"
|
|
31
|
+
root_cause: |
|
|
32
|
+
`npm ci` is strict by design — it requires `package-lock.json` to be present and
|
|
33
|
+
exactly in sync with `package.json`. It fails in several common CI scenarios:
|
|
34
|
+
|
|
35
|
+
1. **No lockfile committed** — developers add `package-lock.json` to `.gitignore`
|
|
36
|
+
(common in library repos) — `npm ci` refuses to run.
|
|
37
|
+
|
|
38
|
+
2. **Lockfile out of sync** — a developer ran `npm install` locally which updated
|
|
39
|
+
the lockfile, but committed `package.json` without the updated lockfile, or vice versa.
|
|
40
|
+
`npm ci` reports "Missing: package@version from lock file."
|
|
41
|
+
|
|
42
|
+
3. **Lockfile from incompatible npm version** — npm v6 lockfiles (lockfileVersion 1)
|
|
43
|
+
cannot be used with `npm ci` in npm v7+ projects that use workspaces, and vice versa.
|
|
44
|
+
|
|
45
|
+
4. **Monorepo workspace packages not locked** — `package-lock.json` at root does not
|
|
46
|
+
lock workspace packages listed in `packages/*` if each sub-package has its own
|
|
47
|
+
`package.json` without being part of the root workspaces config.
|
|
48
|
+
fix: |
|
|
49
|
+
Commit `package-lock.json` to the repository (do not gitignore it in apps). Keep
|
|
50
|
+
`package.json` and `package-lock.json` in sync by running `npm install` locally
|
|
51
|
+
after any manifest change and committing both files together.
|
|
52
|
+
fix_code:
|
|
53
|
+
- language: yaml
|
|
54
|
+
label: "CORRECT — npm ci with Node version matrix and lockfile caching"
|
|
55
|
+
code: |
|
|
56
|
+
jobs:
|
|
57
|
+
test:
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
strategy:
|
|
60
|
+
matrix:
|
|
61
|
+
node-version: ['18.x', '20.x']
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v4
|
|
64
|
+
|
|
65
|
+
- uses: actions/setup-node@v4
|
|
66
|
+
with:
|
|
67
|
+
node-version: ${{ matrix.node-version }}
|
|
68
|
+
cache: npm # caches ~/.npm using package-lock.json hash
|
|
69
|
+
|
|
70
|
+
- name: Install dependencies
|
|
71
|
+
run: npm ci # fails fast if lockfile is out of sync
|
|
72
|
+
|
|
73
|
+
- name: Run tests
|
|
74
|
+
run: npm test
|
|
75
|
+
- language: yaml
|
|
76
|
+
label: "Detect lockfile sync issues before CI"
|
|
77
|
+
code: |
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/checkout@v4
|
|
80
|
+
- uses: actions/setup-node@v4
|
|
81
|
+
with:
|
|
82
|
+
node-version: 20
|
|
83
|
+
|
|
84
|
+
- name: Verify lockfile is up to date
|
|
85
|
+
run: |
|
|
86
|
+
npm install --package-lock-only
|
|
87
|
+
git diff --exit-code package-lock.json || {
|
|
88
|
+
echo "::error::package-lock.json is out of sync with package.json"
|
|
89
|
+
echo "Run 'npm install' locally and commit the updated lockfile"
|
|
90
|
+
exit 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
- run: npm ci
|
|
94
|
+
- language: bash
|
|
95
|
+
label: "Fix gitignore — ensure lockfile is tracked"
|
|
96
|
+
code: |
|
|
97
|
+
# If package-lock.json is gitignored, remove it from .gitignore
|
|
98
|
+
grep -v "package-lock.json" .gitignore > .gitignore.tmp && mv .gitignore.tmp .gitignore
|
|
99
|
+
git add package-lock.json
|
|
100
|
+
git commit -m "chore: track package-lock.json for reproducible CI"
|
|
101
|
+
prevention:
|
|
102
|
+
- "Always commit `package-lock.json` to git — never gitignore it in application repos."
|
|
103
|
+
- "Run `npm install` (not just `npm ci`) locally after any `package.json` change and commit both files atomically."
|
|
104
|
+
- "Add a `Verify lockfile` step to CI that runs `npm install --package-lock-only && git diff --exit-code package-lock.json` to catch drift early."
|
|
105
|
+
- "Renovate Bot and Dependabot automatically keep lockfiles in sync when configured correctly."
|
|
106
|
+
docs:
|
|
107
|
+
- url: "https://docs.npmjs.com/cli/v10/commands/npm-ci"
|
|
108
|
+
label: "npm ci (npmjs docs)"
|
|
109
|
+
- url: "https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#caching-npm-packages"
|
|
110
|
+
label: "Caching npm packages"
|
|
111
|
+
- url: "https://github.com/actions/setup-node#caching-global-packages-data"
|
|
112
|
+
label: "actions/setup-node — npm caching"
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
id: runner-environment-013
|
|
2
|
+
title: "Self-Hosted Runner Stale Tool Cache Serves Outdated Versions"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- self-hosted
|
|
7
|
+
- tool-cache
|
|
8
|
+
- setup-node
|
|
9
|
+
- setup-python
|
|
10
|
+
- setup-java
|
|
11
|
+
- outdated
|
|
12
|
+
- toolcache
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "Found in cache"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "Resolved .* from tool-cache"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "Tool cache hit.*[0-9]+\\.[0-9]+"
|
|
19
|
+
flags: "i"
|
|
20
|
+
error_messages:
|
|
21
|
+
- "Found in cache @ /opt/hostedtoolcache/node/18.12.0/x64"
|
|
22
|
+
- "Resolved Node 18.12.0 from tool-cache"
|
|
23
|
+
root_cause: |
|
|
24
|
+
`actions/setup-node`, `actions/setup-python`, `actions/setup-java`, and similar setup
|
|
25
|
+
actions maintain a **tool cache** on the runner host. On GitHub-hosted runners this
|
|
26
|
+
cache is wiped between jobs (fresh VMs), but on **self-hosted runners** the cache
|
|
27
|
+
persists indefinitely across runs.
|
|
28
|
+
|
|
29
|
+
When a self-hosted runner's tool cache has a matching version range (e.g., `node-version: 18.x`)
|
|
30
|
+
it returns the cached version without downloading a fresh copy. If that cached version
|
|
31
|
+
contains a security vulnerability or a bug that was patched in a newer patch release,
|
|
32
|
+
workflows silently continue using the vulnerable version. There is no warning — the log
|
|
33
|
+
shows "Found in cache" and proceeds.
|
|
34
|
+
|
|
35
|
+
This also happens when teams pin to `18.x` intending to get the latest 18 minor but the
|
|
36
|
+
cache serves the version that was first downloaded months ago.
|
|
37
|
+
fix: |
|
|
38
|
+
Pin to exact versions in production workflows. Periodically purge the tool cache on
|
|
39
|
+
self-hosted runners. Use the `check-latest` input to force setup actions to verify
|
|
40
|
+
whether a newer patch release is available.
|
|
41
|
+
fix_code:
|
|
42
|
+
- language: yaml
|
|
43
|
+
label: "WRONG — loose version range silently serves cached old version"
|
|
44
|
+
code: |
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/setup-node@v4
|
|
47
|
+
with:
|
|
48
|
+
node-version: 18 # resolves to whatever 18.x is in cache
|
|
49
|
+
- language: yaml
|
|
50
|
+
label: "CORRECT — pin exact version for reproducibility"
|
|
51
|
+
code: |
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/setup-node@v4
|
|
54
|
+
with:
|
|
55
|
+
node-version: '18.20.4' # exact version, cache miss forces fresh download
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: "CORRECT — use check-latest to force freshness check"
|
|
58
|
+
code: |
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/setup-node@v4
|
|
61
|
+
with:
|
|
62
|
+
node-version: '18.x'
|
|
63
|
+
check-latest: true # verifies cache against upstream, downloads if newer available
|
|
64
|
+
prevention:
|
|
65
|
+
- "Pin to exact tool versions (`18.20.4` not `18.x`) on self-hosted runners to ensure cache hits are intentional."
|
|
66
|
+
- "Schedule periodic purges of `/opt/hostedtoolcache` (or Windows equivalent) on self-hosted runners."
|
|
67
|
+
- "Use `check-latest: true` on self-hosted runners when using semver ranges."
|
|
68
|
+
- "Document your self-hosted runner's tool cache contents and last purge date."
|
|
69
|
+
docs:
|
|
70
|
+
- url: "https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners"
|
|
71
|
+
label: "About self-hosted runners"
|
|
72
|
+
- url: "https://github.com/actions/setup-node#check-latest-version"
|
|
73
|
+
label: "actions/setup-node — check-latest input"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
id: runner-environment-010
|
|
2
|
+
title: "actions/setup-node Fails When .nvmrc or node-version-file Is Missing"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- setup-node
|
|
7
|
+
- nvmrc
|
|
8
|
+
- node-version
|
|
9
|
+
- node-version-file
|
|
10
|
+
- version-resolution
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "The specified node version file at.*does not exist"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "Unable to find Node version '\\$\\{.*\\}'"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "Unable to find Node version '.*' for platform"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "Cache service responded with 422"
|
|
19
|
+
flags: "i"
|
|
20
|
+
error_messages:
|
|
21
|
+
- "The specified node version file at: /__w/actions/actions/.nvmrc does not exist"
|
|
22
|
+
- "##[error]Unable to find Node version '${NVMRC}' for platform linux and architecture x64."
|
|
23
|
+
- "##[error]Unable to find Node version '16.99.0' for platform linux and architecture x64."
|
|
24
|
+
- "Error: Cache service responded with 422"
|
|
25
|
+
root_cause: |
|
|
26
|
+
`actions/setup-node` supports three ways to specify a Node.js version:
|
|
27
|
+
`node-version` (inline), `node-version-file` (reads from a file), or auto-detection.
|
|
28
|
+
These fail in distinct ways:
|
|
29
|
+
|
|
30
|
+
1. **Missing version file** (`node-version-file: '.nvmrc'` or `'.node-version'`):
|
|
31
|
+
If the file referenced by `node-version-file` does not exist on the runner at the
|
|
32
|
+
path provided, setup-node throws "The specified node version file … does not exist"
|
|
33
|
+
and the step fails. This commonly happens when the file exists in the repo root but
|
|
34
|
+
the workflow runs in a subdirectory, or when the checkout step runs after setup-node.
|
|
35
|
+
|
|
36
|
+
2. **Unresolved environment variable** (`node-version: '${{ env.NVMRC }}'`):
|
|
37
|
+
Using a shell env var (e.g., `$NVMRC`) instead of an Actions expression
|
|
38
|
+
(`${{ env.NVMRC }}`) means the value is never substituted — setup-node receives a
|
|
39
|
+
literal `${NVMRC}` string, which is not a valid version, and fails with "Unable to
|
|
40
|
+
find Node version '${NVMRC}'". Similarly, if `env.NVMRC` is not set at the point
|
|
41
|
+
setup-node runs, the expression evaluates to empty string.
|
|
42
|
+
|
|
43
|
+
3. **Non-existent or misspelled version** (`node-version: '16.99.0'`):
|
|
44
|
+
Requesting a version that GitHub's node distribution manifest does not list causes
|
|
45
|
+
"Unable to find Node version." This can happen after pinning to a patch version
|
|
46
|
+
that was later delisted.
|
|
47
|
+
|
|
48
|
+
4. **Old setup-node version + cache service 422**:
|
|
49
|
+
Older setup-node versions (v1/v2) use a deprecated cache service API that now
|
|
50
|
+
returns HTTP 422. The fix is upgrading to setup-node@v3 or v4.
|
|
51
|
+
fix: |
|
|
52
|
+
1. Ensure `actions/checkout` runs **before** any `actions/setup-node` step that reads
|
|
53
|
+
`node-version-file` — the file must exist on the runner when setup-node reads it.
|
|
54
|
+
2. Use Actions expressions (`${{ env.VAR }}`) not shell syntax (`$VAR`) for
|
|
55
|
+
`node-version` values.
|
|
56
|
+
3. For `.nvmrc`, prefer the `node-version-file: '.nvmrc'` parameter directly rather
|
|
57
|
+
than reading the file in a shell step and passing it to `node-version`.
|
|
58
|
+
4. Upgrade to `actions/setup-node@v4` to avoid the deprecated cache service 422 error.
|
|
59
|
+
fix_code:
|
|
60
|
+
- language: yaml
|
|
61
|
+
label: "Correct order: checkout before setup-node with node-version-file"
|
|
62
|
+
code: |
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v4 # Must come BEFORE setup-node
|
|
65
|
+
- uses: actions/setup-node@v4
|
|
66
|
+
with:
|
|
67
|
+
node-version-file: '.nvmrc' # Reads .nvmrc from the checked-out repo
|
|
68
|
+
cache: 'npm'
|
|
69
|
+
- language: yaml
|
|
70
|
+
label: "Inline version — use Actions expression, not shell variable"
|
|
71
|
+
code: |
|
|
72
|
+
env:
|
|
73
|
+
NODE_VERSION: '20'
|
|
74
|
+
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@v4
|
|
77
|
+
- uses: actions/setup-node@v4
|
|
78
|
+
with:
|
|
79
|
+
node-version: ${{ env.NODE_VERSION }} # ✅ Actions expression
|
|
80
|
+
# node-version: $NODE_VERSION # ❌ Shell syntax — not expanded
|
|
81
|
+
- language: yaml
|
|
82
|
+
label: "Read .nvmrc manually when node-version-file is insufficient"
|
|
83
|
+
code: |
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/checkout@v4
|
|
86
|
+
- name: Read .nvmrc
|
|
87
|
+
id: nvmrc
|
|
88
|
+
run: echo "version=$(cat .nvmrc)" >> $GITHUB_OUTPUT
|
|
89
|
+
- uses: actions/setup-node@v4
|
|
90
|
+
with:
|
|
91
|
+
node-version: ${{ steps.nvmrc.outputs.version }}
|
|
92
|
+
prevention:
|
|
93
|
+
- "Always run `actions/checkout` before any step that reads files from the repository, including `node-version-file`."
|
|
94
|
+
- "Use `actions/setup-node@v4` — v1/v2 are deprecated and trigger cache service 422 errors."
|
|
95
|
+
- "Prefer `node-version-file: '.nvmrc'` over reading the file in a shell step to avoid shell escaping issues."
|
|
96
|
+
- "Pin to a Node.js major version (e.g., `20`) rather than a full SemVer patch to avoid version-not-found errors."
|
|
97
|
+
docs:
|
|
98
|
+
- url: "https://github.com/actions/setup-node"
|
|
99
|
+
label: "actions/setup-node — README and usage"
|
|
100
|
+
- url: "https://github.com/actions/setup-node/issues/613"
|
|
101
|
+
label: "setup-node#613 — node-version-file does not exist error"
|
|
102
|
+
- url: "https://github.com/actions/setup-node/issues/32"
|
|
103
|
+
label: "setup-node#32 — Using .nvmrc with env variable substitution"
|
|
104
|
+
- url: "https://github.com/actions/setup-node/issues/1275"
|
|
105
|
+
label: "setup-node#1275 — Cache service 422 — upgrade to v3/v4"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
id: runner-environment-012
|
|
2
|
+
title: "Windows Runner PowerShell Execution Policy Blocks Custom Scripts"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- windows
|
|
7
|
+
- powershell
|
|
8
|
+
- execution-policy
|
|
9
|
+
- scripts
|
|
10
|
+
- runner
|
|
11
|
+
- security
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "cannot be loaded because running scripts is disabled"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "execution policy"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "UnauthorizedAccess.*\\.ps1"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "File .+\\.ps1 cannot be loaded"
|
|
20
|
+
flags: "i"
|
|
21
|
+
error_messages:
|
|
22
|
+
- "File C:\\...\\script.ps1 cannot be loaded because running scripts is disabled on this system."
|
|
23
|
+
- "UnauthorizedAccess: File C:\\...\\script.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies"
|
|
24
|
+
root_cause: |
|
|
25
|
+
Windows GitHub-hosted runners default to the `RemoteSigned` execution policy, which
|
|
26
|
+
requires that downloaded scripts be digitally signed. Scripts created inline during a
|
|
27
|
+
workflow or checked out from the repo can trigger this restriction depending on how
|
|
28
|
+
they are invoked, especially when called via `powershell.exe` directly or from a
|
|
29
|
+
third-party action that spawns a new PowerShell session with a more restrictive default.
|
|
30
|
+
|
|
31
|
+
This is particularly common when a workflow uses `shell: powershell` (legacy
|
|
32
|
+
`powershell.exe` v5) where the execution policy is stricter, or when a script file
|
|
33
|
+
is written to disk by a previous step and then invoked.
|
|
34
|
+
fix: |
|
|
35
|
+
Use `shell: pwsh` (PowerShell 7+) which defaults to `Bypass` execution policy in
|
|
36
|
+
GitHub Actions workflows. For `shell: powershell` sessions, prefix the script
|
|
37
|
+
invocation with `Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass`.
|
|
38
|
+
fix_code:
|
|
39
|
+
- language: yaml
|
|
40
|
+
label: "WRONG — script fails with execution policy error"
|
|
41
|
+
code: |
|
|
42
|
+
jobs:
|
|
43
|
+
build:
|
|
44
|
+
runs-on: windows-latest
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
|
|
48
|
+
- name: Run build script
|
|
49
|
+
shell: powershell
|
|
50
|
+
run: .\scripts\build.ps1 # may fail: execution policy
|
|
51
|
+
- language: yaml
|
|
52
|
+
label: "CORRECT — use pwsh (PowerShell 7) which defaults to Bypass"
|
|
53
|
+
code: |
|
|
54
|
+
jobs:
|
|
55
|
+
build:
|
|
56
|
+
runs-on: windows-latest
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/checkout@v4
|
|
59
|
+
|
|
60
|
+
- name: Run build script
|
|
61
|
+
shell: pwsh # PowerShell 7+ — Bypass policy by default
|
|
62
|
+
run: .\scripts\build.ps1
|
|
63
|
+
- language: yaml
|
|
64
|
+
label: "CORRECT — explicitly bypass in legacy powershell session"
|
|
65
|
+
code: |
|
|
66
|
+
jobs:
|
|
67
|
+
build:
|
|
68
|
+
runs-on: windows-latest
|
|
69
|
+
steps:
|
|
70
|
+
- name: Run legacy PS script
|
|
71
|
+
shell: powershell
|
|
72
|
+
run: |
|
|
73
|
+
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
|
|
74
|
+
.\scripts\build.ps1
|
|
75
|
+
prevention:
|
|
76
|
+
- "Prefer `shell: pwsh` over `shell: powershell` for all new Windows workflows — it's PowerShell 7+ and more permissive."
|
|
77
|
+
- "Avoid calling `.ps1` files from non-PowerShell shells (cmd, bash) — invoke them via `pwsh -File script.ps1` instead."
|
|
78
|
+
- "Test Windows workflows locally with the same shell version (`pwsh` vs `powershell`) before pushing."
|
|
79
|
+
docs:
|
|
80
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell"
|
|
81
|
+
label: "jobs.<job_id>.steps[*].shell"
|
|
82
|
+
- url: "https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-executionpolicy"
|
|
83
|
+
label: "Set-ExecutionPolicy (Microsoft Learn)"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
id: silent-failures-006
|
|
2
|
+
title: "add-mask Does Not Retroactively Redact Values Already Printed"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- secrets
|
|
7
|
+
- masking
|
|
8
|
+
- add-mask
|
|
9
|
+
- security
|
|
10
|
+
- log-redaction
|
|
11
|
+
- sensitive-data
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "::add-mask::"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "add_mask"
|
|
16
|
+
flags: "i"
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Warning: Secret value was printed to log before masking was applied."
|
|
19
|
+
root_cause: |
|
|
20
|
+
The `add-mask` workflow command (`echo "::add-mask::$VALUE"`) tells the runner to
|
|
21
|
+
redact all future occurrences of `$VALUE` from log output. However, it only applies
|
|
22
|
+
**from the point it is issued forward** — any log lines already written before the
|
|
23
|
+
`add-mask` command are not retroactively redacted.
|
|
24
|
+
|
|
25
|
+
This creates a silent security failure when a step prints a sensitive value in an
|
|
26
|
+
earlier `run:` command and only calls `add-mask` later. The value appears in plain
|
|
27
|
+
text in the workflow log for the duration the log is retained (90 days by default).
|
|
28
|
+
|
|
29
|
+
A common pattern that triggers this: computing a token at the start of a long `run:`
|
|
30
|
+
script, printing it for debugging, and only masking it several lines later.
|
|
31
|
+
fix: |
|
|
32
|
+
Issue `add-mask` as the very first operation after obtaining a sensitive value —
|
|
33
|
+
before any `echo`, `cat`, or other command that might emit it. Prefer using
|
|
34
|
+
built-in `secrets.*` context (which is auto-masked) over dynamically computed
|
|
35
|
+
sensitive values wherever possible.
|
|
36
|
+
fix_code:
|
|
37
|
+
- language: yaml
|
|
38
|
+
label: "WRONG — value printed before add-mask is issued"
|
|
39
|
+
code: |
|
|
40
|
+
steps:
|
|
41
|
+
- name: Get dynamic token
|
|
42
|
+
run: |
|
|
43
|
+
TOKEN=$(curl -s https://api.example.com/token | jq -r .access_token)
|
|
44
|
+
echo "Got token: $TOKEN" # PRINTED IN PLAIN TEXT
|
|
45
|
+
echo "::add-mask::$TOKEN" # too late — above line already logged
|
|
46
|
+
echo "TOKEN=$TOKEN" >> $GITHUB_ENV
|
|
47
|
+
- language: yaml
|
|
48
|
+
label: "CORRECT — mask before any output"
|
|
49
|
+
code: |
|
|
50
|
+
steps:
|
|
51
|
+
- name: Get dynamic token
|
|
52
|
+
run: |
|
|
53
|
+
TOKEN=$(curl -s https://api.example.com/token | jq -r .access_token)
|
|
54
|
+
echo "::add-mask::$TOKEN" # mask FIRST
|
|
55
|
+
echo "TOKEN=$TOKEN" >> $GITHUB_ENV
|
|
56
|
+
echo "Token acquired and masked successfully" # no value, safe to log
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: "CORRECT — use secrets context (auto-masked)"
|
|
59
|
+
code: |
|
|
60
|
+
steps:
|
|
61
|
+
- name: Use pre-configured secret
|
|
62
|
+
env:
|
|
63
|
+
API_TOKEN: ${{ secrets.API_TOKEN }} # automatically masked everywhere
|
|
64
|
+
run: |
|
|
65
|
+
curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com/data
|
|
66
|
+
prevention:
|
|
67
|
+
- "Always call `add-mask` as the very first line after obtaining a sensitive value."
|
|
68
|
+
- "Prefer `secrets.*` context values over dynamically fetched tokens where possible."
|
|
69
|
+
- "Never print sensitive values for debugging — use `echo 'Token acquired (masked)'` instead."
|
|
70
|
+
- "Audit workflows that call external APIs and store tokens in env vars for missing `add-mask`."
|
|
71
|
+
docs:
|
|
72
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#masking-a-value-in-a-log"
|
|
73
|
+
label: "Masking a value in a log"
|
|
74
|
+
- url: "https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions"
|
|
75
|
+
label: "Security hardening for GitHub Actions"
|