@htekdev/actions-debugger 1.0.62 → 1.0.64
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/errors/caching-artifacts/cache-key-github-sha-no-restore.yml +72 -0
- package/errors/known-unsolved/service-containers-not-supported-macos-windows.yml +88 -0
- package/errors/permissions-auth/github-token-fork-pr-read-only.yml +114 -0
- package/errors/silent-failures/cache-hit-output-empty-string-not-false.yml +87 -0
- package/errors/silent-failures/workflow-run-github-sha-is-default-branch.yml +78 -0
- package/errors/triggers/push-commits-truncated-20-limit.yml +78 -0
- package/errors/triggers/push-paths-filter-no-effect-workflow-dispatch.yml +81 -0
- package/errors/yaml-syntax/runs-on-env-context-unavailable.yml +82 -0
- package/package.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
id: caching-artifacts-042
|
|
2
|
+
title: "Cache key containing github.sha always misses on restore — cache is saved but never reused"
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- cache-key
|
|
8
|
+
- github-sha
|
|
9
|
+
- cache-miss
|
|
10
|
+
- restore-keys
|
|
11
|
+
- performance
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Cache not found for input keys: .*\b[0-9a-f]{40}\b'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'Warning: Cache not found for.*sha.*\.'
|
|
16
|
+
flags: i
|
|
17
|
+
- regex: 'Cache restored from key: .*\b[0-9a-f]{40}\b'
|
|
18
|
+
flags: i
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Cache not found for input keys: Linux-node-a3f2b1c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9"
|
|
21
|
+
- "Warning: Cache not found for key: ubuntu-latest-npm-abc1234def5678901234567890abcdef12345678"
|
|
22
|
+
root_cause: |
|
|
23
|
+
When a cache key includes ${{ github.sha }}, every commit produces a unique key. The cache is
|
|
24
|
+
saved successfully at the end of the workflow, but on the very next run the sha is different,
|
|
25
|
+
so the restore step finds no matching cache entry and declares a miss. This pattern ensures
|
|
26
|
+
the cache is written once and thrown away — it provides no performance benefit.
|
|
27
|
+
|
|
28
|
+
This mistake is common because github.sha is a readily available context value and its use
|
|
29
|
+
in cache keys looks reasonable at first glance. The cache action's restore-keys fallback
|
|
30
|
+
mechanism would normally rescue a miss, but developers often omit restore-keys when using
|
|
31
|
+
sha-based keys, expecting the primary key to match.
|
|
32
|
+
fix: |
|
|
33
|
+
Replace github.sha with a hash of the files that determine cache validity, such as
|
|
34
|
+
hashFiles('**/package-lock.json') for npm or hashFiles('**/go.sum') for Go. Use github.sha
|
|
35
|
+
only as a restore-keys fallback prefix if you want a per-commit cache layer on top of a
|
|
36
|
+
stable base cache. The cache key should be stable across commits unless the relevant
|
|
37
|
+
dependency manifest changes.
|
|
38
|
+
fix_code:
|
|
39
|
+
- language: yaml
|
|
40
|
+
label: "Correct cache key using hashFiles instead of github.sha"
|
|
41
|
+
code: |
|
|
42
|
+
- name: Cache node modules
|
|
43
|
+
uses: actions/cache@v4
|
|
44
|
+
with:
|
|
45
|
+
path: ~/.npm
|
|
46
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
47
|
+
restore-keys: |
|
|
48
|
+
${{ runner.os }}-node-
|
|
49
|
+
- language: yaml
|
|
50
|
+
label: "Per-commit layer on top of a stable base cache (advanced)"
|
|
51
|
+
code: |
|
|
52
|
+
# Stable key hits on re-runs; sha layer saves per-commit installs
|
|
53
|
+
- name: Cache node modules
|
|
54
|
+
uses: actions/cache@v4
|
|
55
|
+
with:
|
|
56
|
+
path: ~/.npm
|
|
57
|
+
key: ${{ runner.os }}-node-${{ github.sha }}
|
|
58
|
+
restore-keys: |
|
|
59
|
+
${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
60
|
+
${{ runner.os }}-node-
|
|
61
|
+
prevention:
|
|
62
|
+
- "Never use github.sha as a primary cache key unless you explicitly want a per-commit cache that never restores"
|
|
63
|
+
- "Use hashFiles() over the relevant lock file or manifest as the stable portion of the cache key"
|
|
64
|
+
- "Always add restore-keys with progressively broader prefixes to benefit from partial cache hits"
|
|
65
|
+
- "Check the cache hit rate in the Actions UI — 0% hit rate on restore is a sign the key is too unique"
|
|
66
|
+
docs:
|
|
67
|
+
- url: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#using-the-cache-action
|
|
68
|
+
label: "GitHub Docs: Using the cache action"
|
|
69
|
+
- url: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#matching-a-cache-key
|
|
70
|
+
label: "GitHub Docs: Matching a cache key"
|
|
71
|
+
- url: https://stackoverflow.com/questions/59435153/github-actions-cache-miss-every-run
|
|
72
|
+
label: "Stack Overflow: GitHub Actions cache miss every run"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
id: known-unsolved-041
|
|
2
|
+
title: "services: Block Not Supported on macOS and Windows Runners — Silently Ignored"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: limitation
|
|
5
|
+
tags:
|
|
6
|
+
- services
|
|
7
|
+
- service-containers
|
|
8
|
+
- macos
|
|
9
|
+
- windows
|
|
10
|
+
- linux-only
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'services\s*:'
|
|
13
|
+
flags: 'im'
|
|
14
|
+
- regex: 'runs-on\s*:\s*(macos|windows)'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Connection refused 127.0.0.1:5432"
|
|
18
|
+
- "Unable to connect to the database server"
|
|
19
|
+
- "ECONNREFUSED"
|
|
20
|
+
- "connect ECONNREFUSED 127.0.0.1:6379"
|
|
21
|
+
root_cause: |
|
|
22
|
+
GitHub Actions `services:` containers are only supported on Linux (Ubuntu) runners.
|
|
23
|
+
On macOS and Windows GitHub-hosted runners, the `services:` block is silently ignored
|
|
24
|
+
— the job starts without the service containers, and no warning or error is emitted
|
|
25
|
+
at the workflow syntax level.
|
|
26
|
+
|
|
27
|
+
This happens because the runner agent's service container feature requires Docker
|
|
28
|
+
to be integrated at the runner daemon level. GitHub-hosted macOS and Windows runners
|
|
29
|
+
do not have a running Docker daemon available to the runner agent by default, making
|
|
30
|
+
the `services:` lifecycle management impossible.
|
|
31
|
+
|
|
32
|
+
The failure is deceptive: the job starts normally, all steps run, but any step that
|
|
33
|
+
tries to connect to the service (e.g., PostgreSQL on port 5432, Redis on port 6379)
|
|
34
|
+
receives a connection refused error. The `services:` block produces no visible error
|
|
35
|
+
of its own.
|
|
36
|
+
|
|
37
|
+
There is no GitHub Actions built-in mechanism to make `services:` work on macOS or
|
|
38
|
+
Windows runners. This is a permanent platform limitation.
|
|
39
|
+
fix: |
|
|
40
|
+
Use a Linux (ubuntu-*) runner for any job that requires service containers. If macOS
|
|
41
|
+
or Windows testing is required alongside a service dependency, start the service
|
|
42
|
+
manually as workflow steps within the job instead of using the `services:` block.
|
|
43
|
+
fix_code:
|
|
44
|
+
- language: yaml
|
|
45
|
+
label: "services: block — only works on Linux runners"
|
|
46
|
+
code: |
|
|
47
|
+
jobs:
|
|
48
|
+
test:
|
|
49
|
+
runs-on: ubuntu-latest # services: requires Linux
|
|
50
|
+
services:
|
|
51
|
+
postgres:
|
|
52
|
+
image: postgres:15
|
|
53
|
+
env:
|
|
54
|
+
POSTGRES_PASSWORD: postgres
|
|
55
|
+
ports:
|
|
56
|
+
- 5432:5432
|
|
57
|
+
options: >-
|
|
58
|
+
--health-cmd pg_isready
|
|
59
|
+
--health-interval 10s
|
|
60
|
+
--health-timeout 5s
|
|
61
|
+
--health-retries 5
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v4
|
|
64
|
+
- run: npm test
|
|
65
|
+
- language: yaml
|
|
66
|
+
label: "macOS/Windows: start service manually as a workflow step"
|
|
67
|
+
code: |
|
|
68
|
+
jobs:
|
|
69
|
+
test-macos:
|
|
70
|
+
runs-on: macos-latest
|
|
71
|
+
# services: is NOT supported here — start the service manually instead
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@v4
|
|
74
|
+
- name: Start PostgreSQL (services: not supported on macOS)
|
|
75
|
+
run: |
|
|
76
|
+
brew install postgresql@15
|
|
77
|
+
brew services start postgresql@15
|
|
78
|
+
sleep 5
|
|
79
|
+
- run: npm test
|
|
80
|
+
prevention:
|
|
81
|
+
- "Always pair the `services:` block with a Linux (ubuntu-*) runner — treat this as a hard rule in code review"
|
|
82
|
+
- "If cross-platform testing needs a service dependency, isolate the service-dependent tests into a Linux-only job"
|
|
83
|
+
- "Add a comment near the `services:` block to document that this job must run on Linux"
|
|
84
|
+
docs:
|
|
85
|
+
- url: "https://docs.github.com/en/actions/use-cases-and-examples/using-containerized-services/about-service-containers"
|
|
86
|
+
label: "GitHub Docs: About service containers (Linux-only requirement)"
|
|
87
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idservices"
|
|
88
|
+
label: "GitHub Docs: jobs.<job_id>.services syntax reference"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
id: permissions-auth-045
|
|
2
|
+
title: "GITHUB_TOKEN has read-only access in pull_request workflows from forks — write operations return 403"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- github-token
|
|
7
|
+
- fork
|
|
8
|
+
- pull-request
|
|
9
|
+
- read-only
|
|
10
|
+
- permissions
|
|
11
|
+
- resource-not-accessible
|
|
12
|
+
- write-access
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'Resource not accessible by integration'
|
|
15
|
+
flags: i
|
|
16
|
+
- regex: 'HttpError: Resource not accessible by integration'
|
|
17
|
+
flags: i
|
|
18
|
+
- regex: 'Error: HttpError: Not Found.*token.*forked'
|
|
19
|
+
flags: i
|
|
20
|
+
- regex: 'refused.*write.*403.*pull_request.*fork'
|
|
21
|
+
flags: i
|
|
22
|
+
error_messages:
|
|
23
|
+
- "Error: Resource not accessible by integration"
|
|
24
|
+
- "RequestError [HttpError]: Resource not accessible by integration"
|
|
25
|
+
- "Error: HttpError: Resource not accessible by integration"
|
|
26
|
+
root_cause: |
|
|
27
|
+
When a pull_request workflow is triggered by a PR from a forked repository, GitHub
|
|
28
|
+
automatically restricts the GITHUB_TOKEN to read-only permissions. This security measure
|
|
29
|
+
prevents malicious PRs from using the token to modify the base repository — the token
|
|
30
|
+
cannot create issues, post comments, create releases, trigger deployments, or write to
|
|
31
|
+
the repository. Workflows see a token that behaves as if all write permissions were denied.
|
|
32
|
+
|
|
33
|
+
This restriction applies even if the workflow YAML has explicit permissions: write entries.
|
|
34
|
+
The fork security boundary takes precedence. The workflow still runs (unlike pull_request_target
|
|
35
|
+
which runs with base repo permissions), but the token scope is silently reduced.
|
|
36
|
+
|
|
37
|
+
Common victims include: posting PR comments with github-script, uploading artifacts to
|
|
38
|
+
releases, creating check runs, and updating commit statuses.
|
|
39
|
+
fix: |
|
|
40
|
+
Use the pull_request_target event if the workflow must write to the base repo. Be aware
|
|
41
|
+
that pull_request_target checks out the base branch by default (not the PR head) and carries
|
|
42
|
+
security implications — never checkout and execute untrusted PR code with write-permission
|
|
43
|
+
tokens (see pwn request attacks). For safe comment posting, split into two workflows:
|
|
44
|
+
a read-only pull_request workflow that uploads results as an artifact, and a
|
|
45
|
+
pull_request_target or workflow_run workflow with write access that downloads the artifact
|
|
46
|
+
and posts the comment.
|
|
47
|
+
fix_code:
|
|
48
|
+
- language: yaml
|
|
49
|
+
label: "Split pattern — upload results in pull_request, post comment in workflow_run"
|
|
50
|
+
code: |
|
|
51
|
+
# Workflow 1: pr-test.yml — runs on pull_request (fork-safe, read-only token)
|
|
52
|
+
on: pull_request
|
|
53
|
+
|
|
54
|
+
jobs:
|
|
55
|
+
test:
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/checkout@v4
|
|
59
|
+
- name: Run tests and save results
|
|
60
|
+
run: npm test > test-results.txt 2>&1 || true
|
|
61
|
+
- name: Upload PR number for downstream workflow
|
|
62
|
+
uses: actions/upload-artifact@v4
|
|
63
|
+
with:
|
|
64
|
+
name: pr-results
|
|
65
|
+
path: |
|
|
66
|
+
test-results.txt
|
|
67
|
+
- language: yaml
|
|
68
|
+
label: "Workflow 2 — post comment using workflow_run (has write token)"
|
|
69
|
+
code: |
|
|
70
|
+
# Workflow 2: pr-comment.yml — runs after pr-test.yml completes (has write token)
|
|
71
|
+
on:
|
|
72
|
+
workflow_run:
|
|
73
|
+
workflows: ["PR Tests"]
|
|
74
|
+
types: [completed]
|
|
75
|
+
|
|
76
|
+
permissions:
|
|
77
|
+
pull-requests: write
|
|
78
|
+
|
|
79
|
+
jobs:
|
|
80
|
+
comment:
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
steps:
|
|
83
|
+
- name: Download test results artifact
|
|
84
|
+
uses: actions/download-artifact@v4
|
|
85
|
+
with:
|
|
86
|
+
name: pr-results
|
|
87
|
+
run-id: ${{ github.event.workflow_run.id }}
|
|
88
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
89
|
+
- name: Post PR comment
|
|
90
|
+
uses: actions/github-script@v7
|
|
91
|
+
with:
|
|
92
|
+
script: |
|
|
93
|
+
const fs = require('fs');
|
|
94
|
+
const results = fs.readFileSync('test-results.txt', 'utf8');
|
|
95
|
+
await github.rest.issues.createComment({
|
|
96
|
+
owner: context.repo.owner,
|
|
97
|
+
repo: context.repo.repo,
|
|
98
|
+
issue_number: context.payload.workflow_run.pull_requests[0].number,
|
|
99
|
+
body: '## Test Results\n```\n' + results + '\n```'
|
|
100
|
+
});
|
|
101
|
+
prevention:
|
|
102
|
+
- "Know that GITHUB_TOKEN is read-only for PR workflows from forks — this is a security feature, not a misconfiguration"
|
|
103
|
+
- "Never use pull_request_target to run PR head code with write-permission tokens — this creates a pwn request vulnerability"
|
|
104
|
+
- "Use the upload-artifact + workflow_run split pattern for safe cross-fork write operations"
|
|
105
|
+
- "Test workflows with a fork PR specifically if they perform write operations — the token scope difference won't appear in same-repo PRs"
|
|
106
|
+
docs:
|
|
107
|
+
- url: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request
|
|
108
|
+
label: "GitHub Docs: pull_request event — fork permission restrictions"
|
|
109
|
+
- url: https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
|
|
110
|
+
label: "GitHub Docs: GITHUB_TOKEN permissions"
|
|
111
|
+
- url: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
|
|
112
|
+
label: "GitHub Security Lab: Preventing pwn requests (pull_request_target risks)"
|
|
113
|
+
- url: https://stackoverflow.com/questions/70435286/resource-not-accessible-by-integration-on-github-pull-request-event
|
|
114
|
+
label: "Stack Overflow: Resource not accessible by integration on pull_request"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
id: silent-failures-063
|
|
2
|
+
title: "actions/cache cache-hit output is empty string on miss, not 'false' — equality checks silently never match"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- actions-cache
|
|
7
|
+
- cache-hit
|
|
8
|
+
- output
|
|
9
|
+
- empty-string
|
|
10
|
+
- if-condition
|
|
11
|
+
- false-comparison
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'steps\.\w+\.outputs\.cache-hit\s*==\s*[''"]false[''"]'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: "cache-hit.*==.*'false'"
|
|
16
|
+
flags: i
|
|
17
|
+
error_messages:
|
|
18
|
+
- "cache-hit output comparison to 'false' always evaluates to false — use != 'true' instead"
|
|
19
|
+
root_cause: |
|
|
20
|
+
The actions/cache action sets the cache-hit output to the string 'true' when a cache is
|
|
21
|
+
restored from an exact key match, and to an empty string '' when the cache is not found
|
|
22
|
+
(including when restored from a restore-keys fallback, which returns '' for cache-hit in v3
|
|
23
|
+
and 'false' only in some v4 configurations). This means that
|
|
24
|
+
`if: steps.cache.outputs.cache-hit == 'false'` evaluates to false on a cache miss because
|
|
25
|
+
'' != 'false', so the step or job it guards is silently skipped.
|
|
26
|
+
|
|
27
|
+
This is one of the most upvoted GitHub Actions questions on Stack Overflow. Developers
|
|
28
|
+
naturally expect a boolean-like output to be 'true' or 'false', but the cache action uses
|
|
29
|
+
'true' vs '' (empty) semantics. The step appears to run successfully; nothing in the logs
|
|
30
|
+
indicates the if condition did not match as expected.
|
|
31
|
+
fix: |
|
|
32
|
+
Check for cache miss using != 'true' instead of == 'false'. An empty string and 'false'
|
|
33
|
+
both evaluate to not-equal-to-'true', so this pattern correctly handles all cache miss
|
|
34
|
+
cases across cache action versions. Alternatively, use the boolean expression
|
|
35
|
+
`steps.cache.outputs.cache-hit != 'true'` or omit the check and rely on the cache action's
|
|
36
|
+
lookup-only: true parameter combined with a separate save step.
|
|
37
|
+
fix_code:
|
|
38
|
+
- language: yaml
|
|
39
|
+
label: "Use != 'true' to reliably detect cache miss (works across all cache action versions)"
|
|
40
|
+
code: |
|
|
41
|
+
- name: Cache dependencies
|
|
42
|
+
id: cache-deps
|
|
43
|
+
uses: actions/cache@v4
|
|
44
|
+
with:
|
|
45
|
+
path: ~/.npm
|
|
46
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
47
|
+
|
|
48
|
+
# WRONG — silently never runs on cache miss:
|
|
49
|
+
# - name: Install deps
|
|
50
|
+
# if: steps.cache-deps.outputs.cache-hit == 'false'
|
|
51
|
+
|
|
52
|
+
# CORRECT — matches both '' and 'false':
|
|
53
|
+
- name: Install dependencies
|
|
54
|
+
if: steps.cache-deps.outputs.cache-hit != 'true'
|
|
55
|
+
run: npm ci
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: "Lookup-only + explicit save pattern (cache@v4) to avoid output ambiguity"
|
|
58
|
+
code: |
|
|
59
|
+
- name: Restore cache (lookup only, no auto-save)
|
|
60
|
+
id: cache-restore
|
|
61
|
+
uses: actions/cache/restore@v4
|
|
62
|
+
with:
|
|
63
|
+
path: ~/.npm
|
|
64
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
65
|
+
|
|
66
|
+
- name: Install dependencies
|
|
67
|
+
if: steps.cache-restore.outputs.cache-hit != 'true'
|
|
68
|
+
run: npm ci
|
|
69
|
+
|
|
70
|
+
- name: Save cache
|
|
71
|
+
if: steps.cache-restore.outputs.cache-hit != 'true'
|
|
72
|
+
uses: actions/cache/save@v4
|
|
73
|
+
with:
|
|
74
|
+
path: ~/.npm
|
|
75
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
76
|
+
prevention:
|
|
77
|
+
- "Always use != 'true' to check for cache miss — never use == 'false' on cache action outputs"
|
|
78
|
+
- "Review the cache action version changelog: v3 uses '' on miss, v4 behavior depends on exact/partial hit"
|
|
79
|
+
- "Add a debug step that echoes cache-hit output to make cache behavior visible in logs during development"
|
|
80
|
+
- "Consult the cache action README for the exact semantics of cache-hit, cache-primary-key, and cache-matched-key"
|
|
81
|
+
docs:
|
|
82
|
+
- url: https://github.com/actions/cache#outputs
|
|
83
|
+
label: "actions/cache README: Outputs"
|
|
84
|
+
- url: https://stackoverflow.com/questions/62754195/github-actions-cache-hit-is-true-but-id-expect-false
|
|
85
|
+
label: "Stack Overflow: cache-hit is empty string, not 'false'"
|
|
86
|
+
- url: https://github.com/actions/cache/issues/1699
|
|
87
|
+
label: "actions/cache#1699: cache-hit output '' vs 'false' confusion"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
id: silent-failures-064
|
|
2
|
+
title: "workflow_run Trigger: github.sha Points to Default Branch HEAD, Not Triggering Workflow's Commit"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow_run
|
|
7
|
+
- github-sha
|
|
8
|
+
- checkout
|
|
9
|
+
- wrong-commit
|
|
10
|
+
- cross-workflow
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'on\s*:\s*\n\s+workflow_run'
|
|
13
|
+
flags: 'im'
|
|
14
|
+
- regex: 'github\.sha'
|
|
15
|
+
flags: 'i'
|
|
16
|
+
error_messages:
|
|
17
|
+
- "Unexpected checkout SHA — does not match pull request head commit"
|
|
18
|
+
- "Tests passing on default branch but failing on PR commit"
|
|
19
|
+
root_cause: |
|
|
20
|
+
When a workflow is triggered by `on: workflow_run`, the `github.sha` and `github.ref`
|
|
21
|
+
context variables point to the HEAD of the **default branch** at the time the trigger
|
|
22
|
+
fires, NOT to the commit that triggered the upstream workflow. This is by design: the
|
|
23
|
+
`workflow_run` event executes in the context of the base repository's default branch.
|
|
24
|
+
|
|
25
|
+
Workflows that use `actions/checkout` without specifying a `ref` will silently check
|
|
26
|
+
out the default branch — running against entirely different code than the PR or branch
|
|
27
|
+
that triggered the original CI workflow. CI results, security scans, and deployments
|
|
28
|
+
end up referencing the wrong commit with no visible error.
|
|
29
|
+
|
|
30
|
+
The correct commit SHA from the triggering workflow is available in the event payload
|
|
31
|
+
as `github.event.workflow_run.head_sha`.
|
|
32
|
+
fix: |
|
|
33
|
+
Use `github.event.workflow_run.head_sha` as the `ref` in `actions/checkout` within
|
|
34
|
+
any `workflow_run` triggered workflow. When the triggering workflow originates from a
|
|
35
|
+
fork PR, also supply `github.event.workflow_run.head_repository.full_name` as the
|
|
36
|
+
`repository` input so the correct fork is checked out.
|
|
37
|
+
fix_code:
|
|
38
|
+
- language: yaml
|
|
39
|
+
label: "Wrong: default checkout uses github.sha (default branch HEAD)"
|
|
40
|
+
code: |
|
|
41
|
+
on:
|
|
42
|
+
workflow_run:
|
|
43
|
+
workflows: ["CI"]
|
|
44
|
+
types: [completed]
|
|
45
|
+
|
|
46
|
+
jobs:
|
|
47
|
+
process:
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
steps:
|
|
50
|
+
# Silently checks out default branch, NOT the triggering commit
|
|
51
|
+
- uses: actions/checkout@v4
|
|
52
|
+
- language: yaml
|
|
53
|
+
label: "Correct: use workflow_run event payload for the right commit"
|
|
54
|
+
code: |
|
|
55
|
+
on:
|
|
56
|
+
workflow_run:
|
|
57
|
+
workflows: ["CI"]
|
|
58
|
+
types: [completed]
|
|
59
|
+
|
|
60
|
+
jobs:
|
|
61
|
+
process:
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v4
|
|
65
|
+
with:
|
|
66
|
+
repository: ${{ github.event.workflow_run.head_repository.full_name }}
|
|
67
|
+
ref: ${{ github.event.workflow_run.head_sha }}
|
|
68
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
69
|
+
prevention:
|
|
70
|
+
- "Always set `ref: ${{ github.event.workflow_run.head_sha }}` in checkout steps inside workflow_run triggered workflows"
|
|
71
|
+
- "Log both `github.sha` and `github.event.workflow_run.head_sha` at workflow start to verify they match when expected"
|
|
72
|
+
- "For fork-originated PRs, also set `repository: ${{ github.event.workflow_run.head_repository.full_name }}`"
|
|
73
|
+
- "Add a comment in the workflow explaining why head_sha is used instead of github.sha to avoid future confusion"
|
|
74
|
+
docs:
|
|
75
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run"
|
|
76
|
+
label: "GitHub Docs: workflow_run event (context behavior)"
|
|
77
|
+
- url: "https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/"
|
|
78
|
+
label: "GitHub Security Lab: Preventing pwn requests (workflow_run checkout patterns)"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
id: triggers-046
|
|
2
|
+
title: "github.event.commits Array Truncated to 20 Commits on Large Pushes"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- push
|
|
7
|
+
- commits
|
|
8
|
+
- github-event
|
|
9
|
+
- truncated
|
|
10
|
+
- commit-messages
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'github\.event\.commits'
|
|
13
|
+
flags: 'i'
|
|
14
|
+
error_messages:
|
|
15
|
+
- "Only processing 20 of N commits"
|
|
16
|
+
- "Missing commits in push event payload"
|
|
17
|
+
root_cause: |
|
|
18
|
+
When a push event triggers a workflow, the `github.event.commits` array is capped at
|
|
19
|
+
a maximum of 20 commit objects, regardless of how many commits were included in the
|
|
20
|
+
push. This limit applies to the webhook payload that populates the `github.event`
|
|
21
|
+
context and is documented in the GitHub webhook specification.
|
|
22
|
+
|
|
23
|
+
Workflows that iterate over `github.event.commits` to extract commit messages for
|
|
24
|
+
changelog generation, enforce commit message formatting (Conventional Commits, etc.),
|
|
25
|
+
or trigger conditional logic based on commit content will silently miss any commits
|
|
26
|
+
beyond the 20th. The job completes successfully with no indication that commits were
|
|
27
|
+
dropped from the payload.
|
|
28
|
+
|
|
29
|
+
Commonly affected use cases:
|
|
30
|
+
- Automated changelog or release note generation from push commit messages
|
|
31
|
+
- Commit message linting or conventional commits enforcement
|
|
32
|
+
- Workflows triggered by specific keywords in commit messages
|
|
33
|
+
- Security scanning workflows that enumerate all modified files from commit diffs
|
|
34
|
+
fix: |
|
|
35
|
+
Use `git log` with the push SHA range (`github.event.before..github.event.after`) to
|
|
36
|
+
enumerate all commits in the push, regardless of count. This requires checking out
|
|
37
|
+
the repository with `fetch-depth: 0` (full history) or at minimum enough depth to
|
|
38
|
+
span the push range. Guard against the `before` SHA being all zeros (first push to
|
|
39
|
+
a new branch).
|
|
40
|
+
fix_code:
|
|
41
|
+
- language: yaml
|
|
42
|
+
label: "Wrong: iterating github.event.commits silently misses commits beyond 20"
|
|
43
|
+
code: |
|
|
44
|
+
- name: Lint commit messages
|
|
45
|
+
run: |
|
|
46
|
+
# Silently processes only up to 20 commits even on large pushes
|
|
47
|
+
echo '${{ toJSON(github.event.commits) }}' \
|
|
48
|
+
| jq -r '.[].message' \
|
|
49
|
+
| npx commitlint --from stdin
|
|
50
|
+
- language: yaml
|
|
51
|
+
label: "Correct: use git log with push range to enumerate all commits"
|
|
52
|
+
code: |
|
|
53
|
+
- uses: actions/checkout@v4
|
|
54
|
+
with:
|
|
55
|
+
fetch-depth: 0 # full history required for git log range
|
|
56
|
+
|
|
57
|
+
- name: Lint all commit messages in push
|
|
58
|
+
run: |
|
|
59
|
+
BEFORE="${{ github.event.before }}"
|
|
60
|
+
AFTER="${{ github.event.after }}"
|
|
61
|
+
|
|
62
|
+
# Guard: first push to a new branch has before = 000...000
|
|
63
|
+
if [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then
|
|
64
|
+
echo "First push to new branch — scanning tip commit only"
|
|
65
|
+
git log -1 --format="%s" "$AFTER"
|
|
66
|
+
else
|
|
67
|
+
git log --format="%s" "${BEFORE}..${AFTER}"
|
|
68
|
+
fi | npx commitlint --from stdin
|
|
69
|
+
prevention:
|
|
70
|
+
- "Never rely on `github.event.commits` for exhaustive commit enumeration on pushes — use `git log` with the SHA range instead"
|
|
71
|
+
- "Always add `fetch-depth: 0` to checkout when your workflow needs to enumerate commits from the push range"
|
|
72
|
+
- "Guard against `github.event.before` being all zeros (new branch first push) before running `git log BEFORE..AFTER`"
|
|
73
|
+
- "If commit count matters, log a warning when `github.event.commits | length == 20` to detect truncation"
|
|
74
|
+
docs:
|
|
75
|
+
- url: "https://docs.github.com/en/webhooks/webhook-events-and-payloads#push"
|
|
76
|
+
label: "GitHub Docs: Push webhook payload (20 commit limit)"
|
|
77
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push"
|
|
78
|
+
label: "GitHub Docs: push event trigger"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
id: triggers-045
|
|
2
|
+
title: "on.push.paths filter has no effect on workflow_dispatch — manual trigger always runs regardless of changed files"
|
|
3
|
+
category: triggers
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow-dispatch
|
|
7
|
+
- push
|
|
8
|
+
- paths-filter
|
|
9
|
+
- triggers
|
|
10
|
+
- manual-trigger
|
|
11
|
+
- always-runs
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'workflow_dispatch'
|
|
14
|
+
flags: i
|
|
15
|
+
error_messages:
|
|
16
|
+
- "Workflow ran unexpectedly when triggered via workflow_dispatch despite paths filter"
|
|
17
|
+
root_cause: |
|
|
18
|
+
GitHub Actions evaluates on.push.paths (and on.push.paths-ignore) filters only for push
|
|
19
|
+
events. The filter is tied to the event type it is defined under. When workflow_dispatch is
|
|
20
|
+
added as a separate trigger, it has no paths context — there is no commit diff associated
|
|
21
|
+
with a manual dispatch. GitHub therefore runs the workflow unconditionally when dispatched.
|
|
22
|
+
|
|
23
|
+
This surprises developers who add workflow_dispatch: to an existing push-triggered workflow
|
|
24
|
+
for convenience and expect the paths filter to still gate the run. The workflow_dispatch
|
|
25
|
+
event has no paths or branches key at all; any such keys would be silently ignored if added.
|
|
26
|
+
|
|
27
|
+
A related confusion: paths filters on pull_request or push do not protect against
|
|
28
|
+
workflow_dispatch, so adding a workflow_dispatch trigger effectively creates an unfiltered
|
|
29
|
+
entry point into the workflow.
|
|
30
|
+
fix: |
|
|
31
|
+
Accept that workflow_dispatch runs unconditionally — this is intentional GitHub behavior.
|
|
32
|
+
If you want to restrict what the dispatched workflow does based on inputs, use
|
|
33
|
+
workflow_dispatch inputs to pass a flag and gate steps with if: conditions. If the goal is
|
|
34
|
+
to limit accidental runs, remove workflow_dispatch from workflows where paths-gating is
|
|
35
|
+
critical, or add a required input that must be confirmed before proceeding.
|
|
36
|
+
fix_code:
|
|
37
|
+
- language: yaml
|
|
38
|
+
label: "Separate push-only workflow (with paths filter) from a dispatch-friendly workflow"
|
|
39
|
+
code: |
|
|
40
|
+
# Option 1: keep push and dispatch as separate workflow files
|
|
41
|
+
# push-filtered.yml — only triggered on relevant path changes
|
|
42
|
+
on:
|
|
43
|
+
push:
|
|
44
|
+
paths:
|
|
45
|
+
- 'src/**'
|
|
46
|
+
|
|
47
|
+
# dispatch-build.yml — no paths filter needed; always intentional
|
|
48
|
+
on:
|
|
49
|
+
workflow_dispatch:
|
|
50
|
+
- language: yaml
|
|
51
|
+
label: "Use a workflow_dispatch input to require confirmation before running"
|
|
52
|
+
code: |
|
|
53
|
+
on:
|
|
54
|
+
push:
|
|
55
|
+
paths:
|
|
56
|
+
- 'src/**'
|
|
57
|
+
workflow_dispatch:
|
|
58
|
+
inputs:
|
|
59
|
+
confirm:
|
|
60
|
+
description: 'Type YES to run regardless of changed files'
|
|
61
|
+
required: true
|
|
62
|
+
|
|
63
|
+
jobs:
|
|
64
|
+
build:
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
steps:
|
|
67
|
+
- name: Proceed only if push triggered by path change OR dispatch confirmed
|
|
68
|
+
if: github.event_name == 'push' || inputs.confirm == 'YES'
|
|
69
|
+
run: echo "Running build"
|
|
70
|
+
prevention:
|
|
71
|
+
- "Understand that paths/branches filters are per-event-type — workflow_dispatch has no file diff context"
|
|
72
|
+
- "Document in the workflow file that workflow_dispatch runs unconditionally to avoid future confusion"
|
|
73
|
+
- "Use workflow_dispatch inputs to gate critical steps when a manual trigger is added to a path-filtered workflow"
|
|
74
|
+
- "Review which workflows have both push.paths filters and workflow_dispatch before adding the dispatch trigger"
|
|
75
|
+
docs:
|
|
76
|
+
- url: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_dispatch
|
|
77
|
+
label: "GitHub Docs: workflow_dispatch event"
|
|
78
|
+
- url: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/triggering-a-workflow#using-filters-to-target-specific-branches-or-tags-for-push-events
|
|
79
|
+
label: "GitHub Docs: Using filters to target specific paths"
|
|
80
|
+
- url: https://stackoverflow.com/questions/65900201/workflow-dispatch-ignores-paths-filter
|
|
81
|
+
label: "Stack Overflow: workflow_dispatch ignores paths filter"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
id: yaml-syntax-043
|
|
2
|
+
title: "env Context Not Available in runs-on Expression — Job Waits for Non-Existent Runner"
|
|
3
|
+
category: yaml-syntax
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- runs-on
|
|
7
|
+
- env-context
|
|
8
|
+
- context-availability
|
|
9
|
+
- runner-selection
|
|
10
|
+
- expression
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: 'runs-on\s*:.*\$\{\{.*env\.'
|
|
13
|
+
flags: 'i'
|
|
14
|
+
error_messages:
|
|
15
|
+
- "No runner matching the specified labels was found"
|
|
16
|
+
- "Context access might be invalid: env"
|
|
17
|
+
- "Evaluates to empty string in runs-on"
|
|
18
|
+
root_cause: |
|
|
19
|
+
The `env` context is not available in `jobs.<job_id>.runs-on` expressions. GitHub
|
|
20
|
+
Actions documents an explicit context availability table, and `env` is excluded from
|
|
21
|
+
the `runs-on` field. Only the `github`, `needs`, `strategy`, `matrix`, `vars`, and
|
|
22
|
+
`inputs` contexts are available for `runs-on` expressions.
|
|
23
|
+
|
|
24
|
+
When a developer writes `runs-on: ${{ env.RUNNER_LABEL }}`, the expression evaluates
|
|
25
|
+
to an empty string (since `env` is not accessible at that evaluation point). GitHub
|
|
26
|
+
Actions may emit the warning "Context access might be invalid: env" during workflow
|
|
27
|
+
parsing. The job then queues waiting for a runner matching an empty or literal label,
|
|
28
|
+
which never matches, and the job hangs until it times out.
|
|
29
|
+
|
|
30
|
+
This is a common mistake when setting a custom self-hosted runner label as a
|
|
31
|
+
workflow-level or job-level `env:` variable and trying to reference it in `runs-on`
|
|
32
|
+
for DRY configuration.
|
|
33
|
+
fix: |
|
|
34
|
+
Use the `vars` context (repository, organization, or environment variables) instead of
|
|
35
|
+
`env` for dynamic runner label selection. Repository variables can be set in the
|
|
36
|
+
repository's Settings > Secrets and variables > Variables and referenced as
|
|
37
|
+
`${{ vars.RUNNER_LABEL }}`. The `vars` context IS available in `runs-on` expressions.
|
|
38
|
+
|
|
39
|
+
Alternatively, use `inputs` context for `workflow_dispatch` triggered workflows to
|
|
40
|
+
allow callers to specify the runner label.
|
|
41
|
+
fix_code:
|
|
42
|
+
- language: yaml
|
|
43
|
+
label: "Wrong: env context is not available in runs-on"
|
|
44
|
+
code: |
|
|
45
|
+
env:
|
|
46
|
+
RUNNER_LABEL: self-hosted-prod # This env var cannot be used in runs-on
|
|
47
|
+
|
|
48
|
+
jobs:
|
|
49
|
+
deploy:
|
|
50
|
+
# ERROR: env context unavailable — expression evaluates to empty string
|
|
51
|
+
runs-on: ${{ env.RUNNER_LABEL }}
|
|
52
|
+
- language: yaml
|
|
53
|
+
label: "Correct: use vars context (repository/org variable)"
|
|
54
|
+
code: |
|
|
55
|
+
# Set RUNNER_LABEL in Settings > Secrets and variables > Variables (repo or org level)
|
|
56
|
+
jobs:
|
|
57
|
+
deploy:
|
|
58
|
+
runs-on: ${{ vars.RUNNER_LABEL || 'ubuntu-latest' }}
|
|
59
|
+
- language: yaml
|
|
60
|
+
label: "Correct: use inputs for workflow_dispatch dynamic runner selection"
|
|
61
|
+
code: |
|
|
62
|
+
on:
|
|
63
|
+
workflow_dispatch:
|
|
64
|
+
inputs:
|
|
65
|
+
runner:
|
|
66
|
+
description: 'Runner label'
|
|
67
|
+
default: 'ubuntu-latest'
|
|
68
|
+
type: string
|
|
69
|
+
|
|
70
|
+
jobs:
|
|
71
|
+
deploy:
|
|
72
|
+
runs-on: ${{ inputs.runner }}
|
|
73
|
+
prevention:
|
|
74
|
+
- "Check GitHub's context availability table before using any context in `runs-on` — `env` is NOT available there"
|
|
75
|
+
- "Use `vars.*` (repository or org variables) not `env.*` for dynamic runner label configuration"
|
|
76
|
+
- "Add `|| 'ubuntu-latest'` as a fallback in `runs-on` expressions to avoid jobs that queue indefinitely"
|
|
77
|
+
- "If the job hangs immediately after queuing with no runner assignment, check `runs-on` expressions for invalid context usage"
|
|
78
|
+
docs:
|
|
79
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/contexts#context-availability"
|
|
80
|
+
label: "GitHub Docs: Context availability table (runs-on allowed contexts)"
|
|
81
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idruns-on"
|
|
82
|
+
label: "GitHub Docs: jobs.<job_id>.runs-on expressions"
|
package/package.json
CHANGED