@htekdev/actions-debugger 1.0.60 → 1.0.62
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/fork-pr-cache-isolation.yml +114 -0
- package/errors/concurrency-timing/cancel-in-progress-required-check-cancelled-conclusion.yml +84 -0
- package/errors/known-unsolved/skipped-job-outputs-empty-string.yml +109 -0
- package/errors/permissions-auth/permissions-auth-044.yml +82 -0
- package/errors/runner-environment/runner-environment-123.yml +61 -0
- package/errors/runner-environment/runner-environment-124.yml +68 -0
- package/errors/silent-failures/silent-failures-062.yml +66 -0
- package/errors/silent-failures/workflow-call-boolean-input-string-coercion.yml +98 -0
- package/package.json +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
id: caching-artifacts-041
|
|
2
|
+
title: "Fork PR workflows cannot read caches from the parent repository — always cache miss"
|
|
3
|
+
category: caching-artifacts
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- fork
|
|
8
|
+
- pull-request
|
|
9
|
+
- security
|
|
10
|
+
- cache-miss
|
|
11
|
+
- isolation
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Cache not found for input keys'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'No cache found'
|
|
16
|
+
flags: i
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Cache not found for input keys:"
|
|
19
|
+
- "No cache found"
|
|
20
|
+
root_cause: |
|
|
21
|
+
GitHub Actions intentionally prevents fork PR workflows from reading caches created by
|
|
22
|
+
the parent repository. This is a security measure: a malicious fork could craft a cache
|
|
23
|
+
key matching a parent repo cache, read its contents (which may contain build artifacts,
|
|
24
|
+
installed dependencies, or sensitive intermediate files), and exfiltrate them via the
|
|
25
|
+
fork workflow's logs or artifact uploads.
|
|
26
|
+
|
|
27
|
+
The restriction applies to `pull_request` events triggered by fork contributors:
|
|
28
|
+
- The fork workflow runs in a restricted sandbox with a read-only GITHUB_TOKEN
|
|
29
|
+
- `restore-keys` fallback does NOT traverse to the parent repository's cache namespace
|
|
30
|
+
- Even a perfect cache key match on the parent repo returns a miss in the fork
|
|
31
|
+
- The `actions/cache` step reports "Cache not found" with no indication that security
|
|
32
|
+
isolation is the cause — developers commonly assume misconfigured cache keys
|
|
33
|
+
|
|
34
|
+
Affected scenarios:
|
|
35
|
+
- Open source projects where external contributors submit PRs
|
|
36
|
+
- npm/pip/gem dependency caches that would accelerate fork CI significantly
|
|
37
|
+
- Docker buildx layer caches configured via `actions/cache`
|
|
38
|
+
- Turborepo and Nx remote caches backed by GitHub Actions cache
|
|
39
|
+
fix: |
|
|
40
|
+
Fork PR cache isolation is intentional and cannot be disabled. Mitigations:
|
|
41
|
+
|
|
42
|
+
1. Accept cold builds for fork PRs and optimize speed without cache:
|
|
43
|
+
Use package manager offline flags (npm ci --prefer-offline, pip --no-index),
|
|
44
|
+
pre-built base Docker images, and build sharding to minimize cold-build time.
|
|
45
|
+
|
|
46
|
+
2. Use `pull_request_target` instead of `pull_request` — this event runs in the parent
|
|
47
|
+
repo context with full cache access. SECURITY WARNING: this grants the fork workflow
|
|
48
|
+
write-level GITHUB_TOKEN access. Only safe for workflows that do NOT check out and
|
|
49
|
+
execute code from the PR head branch.
|
|
50
|
+
|
|
51
|
+
3. Pre-warm fork caches via a separate trusted workflow that runs on push to the base
|
|
52
|
+
branch and stores a cache accessible by the fork's restore-keys fallback scope. This
|
|
53
|
+
works because forks CAN read the parent's default branch caches via restore-keys.
|
|
54
|
+
The fork cannot read arbitrary keys, but restore-keys prefix matching against the
|
|
55
|
+
default branch cache does work.
|
|
56
|
+
fix_code:
|
|
57
|
+
- language: yaml
|
|
58
|
+
label: "Accept cold builds — optimize fork PR without relying on cache"
|
|
59
|
+
code: |
|
|
60
|
+
on:
|
|
61
|
+
pull_request:
|
|
62
|
+
|
|
63
|
+
jobs:
|
|
64
|
+
build:
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v4
|
|
68
|
+
- uses: actions/setup-node@v4
|
|
69
|
+
with:
|
|
70
|
+
node-version: '20'
|
|
71
|
+
# Cache works for non-fork PRs; fork PRs will be cold builds
|
|
72
|
+
cache: 'npm'
|
|
73
|
+
- name: Install dependencies
|
|
74
|
+
# --prefer-offline minimizes network round-trips on cold builds
|
|
75
|
+
run: npm ci --prefer-offline --no-audit
|
|
76
|
+
|
|
77
|
+
- language: yaml
|
|
78
|
+
label: "Restore-keys fallback from default branch — forks CAN match default-branch caches"
|
|
79
|
+
code: |
|
|
80
|
+
# Caches saved on the default branch (main/master) ARE accessible to fork PRs
|
|
81
|
+
# via restore-keys prefix matching. Pre-warm by saving on each push to main.
|
|
82
|
+
on:
|
|
83
|
+
push:
|
|
84
|
+
branches: [main]
|
|
85
|
+
pull_request:
|
|
86
|
+
|
|
87
|
+
jobs:
|
|
88
|
+
build:
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
steps:
|
|
91
|
+
- uses: actions/checkout@v4
|
|
92
|
+
- uses: actions/cache@v4
|
|
93
|
+
with:
|
|
94
|
+
path: ~/.npm
|
|
95
|
+
# Exact key includes lockfile hash — will miss on forks
|
|
96
|
+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
97
|
+
# Restore-key matches default-branch cache — forks get a partial hit
|
|
98
|
+
restore-keys: |
|
|
99
|
+
${{ runner.os }}-node-
|
|
100
|
+
prevention:
|
|
101
|
+
- "Document that fork contributor CI will be slower due to cache isolation — set expectations"
|
|
102
|
+
- "Use restore-keys with a broad prefix to allow forks to partially match default-branch caches"
|
|
103
|
+
- "Avoid `pull_request_target` with PR head checkout and code execution — this is a high-severity security vulnerability"
|
|
104
|
+
- "Optimize cold-build speed with offline package manager flags and pre-built base images"
|
|
105
|
+
- "Consider caching at the Docker image layer level via a registry rather than GitHub Actions cache"
|
|
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: "pull_request event — fork restrictions — GitHub Docs"
|
|
109
|
+
- url: "https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-pull_request_target"
|
|
110
|
+
label: "Security hardening — pull_request_target risks — GitHub Docs"
|
|
111
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache"
|
|
112
|
+
label: "Cache access restrictions — GitHub Docs"
|
|
113
|
+
- url: "https://github.com/orgs/community/discussions/44783"
|
|
114
|
+
label: "Fork PR workflows cannot access parent repo cache — GitHub Community #44783"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
id: concurrency-timing-035
|
|
2
|
+
title: "cancel-in-progress posts 'cancelled' conclusion — required status check blocks PR merge"
|
|
3
|
+
category: concurrency-timing
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- concurrency
|
|
7
|
+
- cancel-in-progress
|
|
8
|
+
- required-checks
|
|
9
|
+
- branch-protection
|
|
10
|
+
- pull-request
|
|
11
|
+
- status-checks
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Required status checks have not passed'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'cancelled.*required.*check|required.*check.*cancelled'
|
|
16
|
+
flags: i
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Required status checks have not passed"
|
|
19
|
+
- "Branch protection rule requires status check to pass before merging"
|
|
20
|
+
root_cause: |
|
|
21
|
+
When `cancel-in-progress: true` is configured in a concurrency group and that workflow
|
|
22
|
+
is a required status check on a branch protection rule, a cancelled run posts a
|
|
23
|
+
`cancelled` conclusion to the Checks API. GitHub treats `cancelled` as a non-passing
|
|
24
|
+
conclusion for required status checks — identical to `failure`.
|
|
25
|
+
|
|
26
|
+
The failure cycle:
|
|
27
|
+
1. Push A triggers run A (check posts `in_progress`)
|
|
28
|
+
2. Push B triggers run B; concurrency group cancels run A
|
|
29
|
+
3. Run A posts `cancelled` conclusion — required check is now "failed"
|
|
30
|
+
4. Run B may itself get cancelled by Push C before it completes
|
|
31
|
+
5. PR is permanently stuck: merge button blocked until a run completes with `success`
|
|
32
|
+
|
|
33
|
+
This is especially painful in fast-moving PRs where rapid pushes prevent any run from
|
|
34
|
+
completing. The `cancelled` status on the required check is indistinguishable from a
|
|
35
|
+
genuine test failure in the PR view.
|
|
36
|
+
fix: |
|
|
37
|
+
Option 1 — Use a separate reporter job with `if: always()` that is NOT subject to the
|
|
38
|
+
concurrency group. The main build job can be cancelled; the reporter always runs and
|
|
39
|
+
posts the actual status. Configure branch protection to require the reporter job, not
|
|
40
|
+
the build job.
|
|
41
|
+
|
|
42
|
+
Option 2 — Set concurrency at job level instead of workflow level for the job that
|
|
43
|
+
posts the required status check. The job itself will not be cancelled mid-run; only
|
|
44
|
+
newly queued workflow runs are affected.
|
|
45
|
+
|
|
46
|
+
Option 3 — Accept the pattern and use auto-merge on PRs. Once any non-cancelled run
|
|
47
|
+
succeeds, the merge proceeds automatically without manual unblocking.
|
|
48
|
+
fix_code:
|
|
49
|
+
- language: yaml
|
|
50
|
+
label: "Reporter job pattern — required check never posts 'cancelled'"
|
|
51
|
+
code: |
|
|
52
|
+
concurrency:
|
|
53
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
54
|
+
cancel-in-progress: true
|
|
55
|
+
|
|
56
|
+
jobs:
|
|
57
|
+
build:
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v4
|
|
61
|
+
- run: npm ci && npm test
|
|
62
|
+
|
|
63
|
+
# This job is NOT inside the concurrency group — it always runs.
|
|
64
|
+
# Configure branch protection to require 'report-status', not 'build'.
|
|
65
|
+
report-status:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
needs: [build]
|
|
68
|
+
if: always()
|
|
69
|
+
steps:
|
|
70
|
+
- name: Fail if build did not succeed
|
|
71
|
+
if: needs.build.result != 'success'
|
|
72
|
+
run: exit 1
|
|
73
|
+
prevention:
|
|
74
|
+
- "Test required-check + cancel-in-progress interaction on a draft PR before enabling branch protection"
|
|
75
|
+
- "Configure branch protection to require a dedicated reporter job, not the cancellable build job"
|
|
76
|
+
- "Set concurrency at job level for jobs that post required checks"
|
|
77
|
+
- "Enable auto-merge to reduce manual intervention when cancelled checks momentarily block the PR"
|
|
78
|
+
docs:
|
|
79
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-concurrency"
|
|
80
|
+
label: "Using concurrency — GitHub Docs"
|
|
81
|
+
- url: "https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches"
|
|
82
|
+
label: "About protected branches — GitHub Docs"
|
|
83
|
+
- url: "https://github.com/orgs/community/discussions/13015"
|
|
84
|
+
label: "cancel-in-progress fails required status check — GitHub Community #13015"
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
id: known-unsolved-040
|
|
2
|
+
title: "Skipped job outputs always resolve to empty string — downstream consumers receive '' silently"
|
|
3
|
+
category: known-unsolved
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- outputs
|
|
7
|
+
- skipped
|
|
8
|
+
- needs
|
|
9
|
+
- conditional-jobs
|
|
10
|
+
- silent-failure
|
|
11
|
+
- limitation
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'needs\.[a-z_-]+\.outputs\.[a-z_-]+'
|
|
14
|
+
flags: i
|
|
15
|
+
error_messages: []
|
|
16
|
+
root_cause: |
|
|
17
|
+
When a job is skipped — because its `if:` condition evaluated to false, or because all
|
|
18
|
+
jobs in its `needs` array were skipped or failed — that job's `outputs` block never
|
|
19
|
+
executes. Any downstream job that reads `needs.<skipped-job>.outputs.<key>` receives an
|
|
20
|
+
empty string `''`.
|
|
21
|
+
|
|
22
|
+
This is completely silent: no workflow annotation, no job failure, no warning in the logs.
|
|
23
|
+
The downstream job receives `''` and continues executing as though the output was
|
|
24
|
+
legitimately set to an empty string.
|
|
25
|
+
|
|
26
|
+
Common failure scenarios:
|
|
27
|
+
- A build job is gated with `if: github.ref == 'refs/heads/main'`; a deploy job reads
|
|
28
|
+
its artifact path output and deploys with an empty path string
|
|
29
|
+
- A matrix job is conditionally skipped on a specific OS; a dependent job reads the
|
|
30
|
+
OS-specific output and silently operates on an empty value
|
|
31
|
+
- A fan-out aggregator reads outputs from multiple conditional jobs; skipped job outputs
|
|
32
|
+
silently produce empty aggregation results
|
|
33
|
+
|
|
34
|
+
There is no way to distinguish "output was deliberately set to empty string" from "job
|
|
35
|
+
was skipped and output was never set". The `needs.<job>.result` value is `'skipped'`
|
|
36
|
+
but `needs.<job>.outputs.*` is always `''` regardless of cause.
|
|
37
|
+
fix: |
|
|
38
|
+
There is no built-in mechanism to throw an error when reading outputs from a skipped job.
|
|
39
|
+
This is a known GitHub Actions limitation — skipped job outputs permanently resolve to ''.
|
|
40
|
+
|
|
41
|
+
Workarounds:
|
|
42
|
+
|
|
43
|
+
1. Guard downstream jobs with a result check:
|
|
44
|
+
Add `if: needs.<job>.result == 'success'` to every job that consumes outputs from a
|
|
45
|
+
conditional job. This prevents the downstream job from running on empty outputs.
|
|
46
|
+
|
|
47
|
+
2. Provide explicit sentinel defaults in the output-setting step:
|
|
48
|
+
Use a shell default expansion in the step that sets the output:
|
|
49
|
+
`echo "path=${ARTIFACT_PATH:-UNSET}" >> $GITHUB_OUTPUT`
|
|
50
|
+
Downstream jobs check for `'UNSET'` to detect the unset case.
|
|
51
|
+
|
|
52
|
+
3. Inline null-coalescing in expressions:
|
|
53
|
+
`${{ needs.build.outputs.path != '' && needs.build.outputs.path || 'default-value' }}`
|
|
54
|
+
fix_code:
|
|
55
|
+
- language: yaml
|
|
56
|
+
label: "Guard downstream job with result check — prevents execution on empty outputs"
|
|
57
|
+
code: |
|
|
58
|
+
jobs:
|
|
59
|
+
build:
|
|
60
|
+
if: github.ref == 'refs/heads/main'
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
outputs:
|
|
63
|
+
artifact-path: ${{ steps.build.outputs.path }}
|
|
64
|
+
steps:
|
|
65
|
+
- id: build
|
|
66
|
+
run: echo "path=dist/" >> $GITHUB_OUTPUT
|
|
67
|
+
|
|
68
|
+
deploy:
|
|
69
|
+
needs: [build]
|
|
70
|
+
# Only run if build actually succeeded — never run if skipped or failed
|
|
71
|
+
if: needs.build.result == 'success'
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- run: echo "Deploying ${{ needs.build.outputs.artifact-path }}"
|
|
75
|
+
|
|
76
|
+
- language: yaml
|
|
77
|
+
label: "Sentinel default — detect skipped output in downstream job"
|
|
78
|
+
code: |
|
|
79
|
+
jobs:
|
|
80
|
+
build:
|
|
81
|
+
if: github.event_name == 'push'
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
outputs:
|
|
84
|
+
version: ${{ steps.ver.outputs.version }}
|
|
85
|
+
steps:
|
|
86
|
+
- id: ver
|
|
87
|
+
run: echo "version=${VERSION:-UNSET}" >> $GITHUB_OUTPUT
|
|
88
|
+
|
|
89
|
+
publish:
|
|
90
|
+
needs: [build]
|
|
91
|
+
runs-on: ubuntu-latest
|
|
92
|
+
steps:
|
|
93
|
+
- name: Abort if build was skipped
|
|
94
|
+
if: needs.build.outputs.version == 'UNSET' || needs.build.outputs.version == ''
|
|
95
|
+
run: |
|
|
96
|
+
echo "Build was skipped or version not set — aborting publish"
|
|
97
|
+
exit 1
|
|
98
|
+
prevention:
|
|
99
|
+
- "Always check `needs.<job>.result == 'success'` before using that job's outputs"
|
|
100
|
+
- "Provide explicit sentinel defaults in output-setting steps for critical values"
|
|
101
|
+
- "Treat empty string outputs from conditional jobs as indicative of skip or failure"
|
|
102
|
+
- "Document which jobs are conditional and which downstream jobs depend on their outputs"
|
|
103
|
+
docs:
|
|
104
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs"
|
|
105
|
+
label: "Passing information between jobs — GitHub Docs"
|
|
106
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/using-conditions-to-control-job-execution"
|
|
107
|
+
label: "Using conditions to control job execution — GitHub Docs"
|
|
108
|
+
- url: "https://github.com/orgs/community/discussions/26704"
|
|
109
|
+
label: "Job outputs from skipped jobs are empty string — GitHub Community #26704"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
id: permissions-auth-044
|
|
2
|
+
title: "OIDC token sub claim format changes inside reusable workflow jobs, breaking cloud provider trust policies"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- oidc
|
|
7
|
+
- reusable-workflow
|
|
8
|
+
- aws
|
|
9
|
+
- gcp
|
|
10
|
+
- trust-policy
|
|
11
|
+
- sub-claim
|
|
12
|
+
- job-workflow-ref
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: 'Not authorized to perform sts:AssumeRoleWithWebIdentity'
|
|
15
|
+
flags: i
|
|
16
|
+
- regex: 'failed to generate Google Cloud federated token'
|
|
17
|
+
flags: i
|
|
18
|
+
- regex: 'Credentials could not be loaded.*Could not load credentials from any providers'
|
|
19
|
+
flags: i
|
|
20
|
+
error_messages:
|
|
21
|
+
- "An error occurred (AccessDenied) when calling the AssumeRoleWithWebIdentity operation: Not authorized to perform sts:AssumeRoleWithWebIdentity"
|
|
22
|
+
- "Error: google-github-actions/auth failed to generate Google Cloud federated token for..."
|
|
23
|
+
- "Error: Credentials could not be loaded, please check your action inputs: Could not load credentials from any providers"
|
|
24
|
+
root_cause: |
|
|
25
|
+
When a job runs inside a reusable workflow (called via uses:), GitHub changes the format of
|
|
26
|
+
the OIDC token sub claim to include the calling workflow's path. For a direct job the sub is:
|
|
27
|
+
repo:ORG/REPO:ref:refs/heads/main
|
|
28
|
+
For a job inside a reusable workflow the sub becomes:
|
|
29
|
+
repo:ORG/REPO:job_workflow_ref:ORG/REPO/.github/workflows/reusable.yml@refs/heads/main
|
|
30
|
+
AWS IAM OIDC trust policies and GCP Workload Identity Federation attribute conditions that
|
|
31
|
+
were configured to match the simpler ref-based sub format now reject the OIDC token with an
|
|
32
|
+
AccessDenied error. The error message gives no indication that the sub claim format changed —
|
|
33
|
+
it looks identical to any other OIDC trust policy mismatch.
|
|
34
|
+
fix: |
|
|
35
|
+
Update the cloud provider's OIDC trust policy to match the new sub claim format used by
|
|
36
|
+
reusable workflow jobs. For AWS, update the IAM trust policy StringLike condition to match
|
|
37
|
+
job_workflow_ref instead of ref. For GCP, update the attribute condition in the Workload
|
|
38
|
+
Identity Pool provider. Alternatively, use GitHub's OIDC subject claim customization feature
|
|
39
|
+
(repo Settings → Actions → General → OIDC subject claims) to define a consistent sub claim
|
|
40
|
+
template that works for both caller and reusable workflow jobs.
|
|
41
|
+
fix_code:
|
|
42
|
+
- language: yaml
|
|
43
|
+
label: "Reusable workflow with id-token permission declared (required)"
|
|
44
|
+
code: |
|
|
45
|
+
# In the reusable workflow file (.github/workflows/reusable.yml):
|
|
46
|
+
on:
|
|
47
|
+
workflow_call:
|
|
48
|
+
|
|
49
|
+
permissions:
|
|
50
|
+
id-token: write
|
|
51
|
+
contents: read
|
|
52
|
+
|
|
53
|
+
jobs:
|
|
54
|
+
deploy:
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
steps:
|
|
57
|
+
- name: Configure AWS credentials
|
|
58
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
59
|
+
with:
|
|
60
|
+
role-to-assume: arn:aws:iam::123456789:role/my-role
|
|
61
|
+
aws-region: us-east-1
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: "AWS IAM trust policy StringLike condition for reusable workflow sub claim"
|
|
64
|
+
code: |
|
|
65
|
+
# Update AWS IAM role trust policy Condition block to match reusable workflow sub format.
|
|
66
|
+
# Old (direct job): "repo:ORG/REPO:ref:refs/heads/main"
|
|
67
|
+
# New (reusable job): "repo:ORG/REPO:job_workflow_ref:ORG/REPO/.github/workflows/reusable.yml@refs/heads/main"
|
|
68
|
+
#
|
|
69
|
+
# Use a wildcard to allow both patterns:
|
|
70
|
+
# "token.actions.githubusercontent.com:sub": "StringLike": ["repo:ORG/REPO:*"]
|
|
71
|
+
prevention:
|
|
72
|
+
- "When refactoring direct jobs into reusable workflows, update cloud OIDC trust policies before deploying"
|
|
73
|
+
- "Use GitHub subject claim customization to define a consistent sub format that works across direct and reusable jobs"
|
|
74
|
+
- "Document the expected OIDC sub claim format in the reusable workflow README alongside cloud policy requirements"
|
|
75
|
+
- "Test OIDC authentication in a staging cloud environment when moving jobs into reusable workflows"
|
|
76
|
+
docs:
|
|
77
|
+
- url: https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/using-openid-connect-with-reusable-workflows
|
|
78
|
+
label: "GitHub Docs: Using OIDC with reusable workflows"
|
|
79
|
+
- url: https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-subject-claims-for-an-organization-or-repository
|
|
80
|
+
label: "GitHub Docs: Customizing OIDC subject claims"
|
|
81
|
+
- url: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc.html
|
|
82
|
+
label: "AWS Docs: Creating IAM OIDC identity providers"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
id: runner-environment-123
|
|
2
|
+
title: "actions/checkout@v6 breaks Docker container actions that use git authentication"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- checkout
|
|
7
|
+
- v6
|
|
8
|
+
- docker
|
|
9
|
+
- container-action
|
|
10
|
+
- git-auth
|
|
11
|
+
- breaking-change
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'fatal: could not read Username for.*No such device or address'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'fatal: Authentication failed for.*github\.com'
|
|
16
|
+
flags: i
|
|
17
|
+
- regex: 'fatal: credential helper.*is not executable'
|
|
18
|
+
flags: i
|
|
19
|
+
error_messages:
|
|
20
|
+
- "fatal: could not read Username for 'https://github.com/': No such device or address"
|
|
21
|
+
- "fatal: Authentication failed for 'https://github.com/org/repo.git/'"
|
|
22
|
+
- "Error: The process '/usr/bin/git' failed with exit code 128"
|
|
23
|
+
root_cause: |
|
|
24
|
+
actions/checkout@v6 changed credential storage: credentials are now written to the runner's
|
|
25
|
+
native credential manager rather than the global gitconfig file. Docker container actions run
|
|
26
|
+
in isolated containers without access to the runner host's credential store, so any git
|
|
27
|
+
operations inside a Docker-based action or container: job that require authentication fail.
|
|
28
|
+
This is a breaking change from v5, where credentials were written to gitconfig and could be
|
|
29
|
+
inherited by Docker containers. The v6 runner PR #4011 introduced this mechanism and Docker
|
|
30
|
+
container action support is gated behind a feature flag not yet enabled for all runners.
|
|
31
|
+
fix: |
|
|
32
|
+
Pin to actions/checkout@v5 for workflows that rely on Docker container actions making
|
|
33
|
+
authenticated git calls. Monitor the actions/checkout issue tracker for v6 Docker container
|
|
34
|
+
action support. Alternatively, pass the GITHUB_TOKEN as an environment variable to the Docker
|
|
35
|
+
action and configure credentials inside the container's own entrypoint script.
|
|
36
|
+
fix_code:
|
|
37
|
+
- language: yaml
|
|
38
|
+
label: "Pin to actions/checkout@v5 for Docker container action compatibility"
|
|
39
|
+
code: |
|
|
40
|
+
- name: Checkout repository
|
|
41
|
+
uses: actions/checkout@v5
|
|
42
|
+
with:
|
|
43
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
44
|
+
- language: yaml
|
|
45
|
+
label: "Pass token as env var to Docker action for container-side credential setup"
|
|
46
|
+
code: |
|
|
47
|
+
- name: Run Docker-based action with explicit token
|
|
48
|
+
uses: org/my-docker-action@v1
|
|
49
|
+
env:
|
|
50
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
51
|
+
prevention:
|
|
52
|
+
- "Pin actions/checkout to a tested major version and review release notes before upgrading to a new major"
|
|
53
|
+
- "Audit workflows for Docker container actions that perform authenticated repository operations before upgrading checkout"
|
|
54
|
+
- "Test Docker-based custom actions in CI against the new checkout version before rolling out"
|
|
55
|
+
docs:
|
|
56
|
+
- url: https://github.com/actions/checkout/issues/2313
|
|
57
|
+
label: "actions/checkout#2313: v6 breaks Docker actions that use git authentication"
|
|
58
|
+
- url: https://github.com/orgs/community/discussions/179107
|
|
59
|
+
label: "GitHub Community: actions/checkout v6 changes discussion"
|
|
60
|
+
- url: https://github.com/actions/runner/pull/4011
|
|
61
|
+
label: "actions/runner PR#4011: credential manager changes introduced in v6"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
id: runner-environment-124
|
|
2
|
+
title: "actions/setup-go@v6 GOTOOLCHAIN auto mode downloads unexpected Go version from go.mod toolchain directive"
|
|
3
|
+
category: runner-environment
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- setup-go
|
|
7
|
+
- v6
|
|
8
|
+
- gotoolchain
|
|
9
|
+
- go-version
|
|
10
|
+
- toolchain-directive
|
|
11
|
+
- breaking-change
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'go: downloading go\d+\.\d+\.\d+ \('
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'toolchain go\d+\.\d+\.\d+ cannot be used because it would require a later version'
|
|
16
|
+
flags: i
|
|
17
|
+
- regex: 'go: toolchain go\d+\.\d+\.\d+ not available on GOPROXY'
|
|
18
|
+
flags: i
|
|
19
|
+
error_messages:
|
|
20
|
+
- "go: downloading go1.23.4 (linux/amd64)"
|
|
21
|
+
- "toolchain go1.23.4 cannot be used because it would require a later version"
|
|
22
|
+
- "go: toolchain go1.23.4 not available on GOPROXY"
|
|
23
|
+
root_cause: |
|
|
24
|
+
actions/setup-go@v6 (released September 2025) changed toolchain handling to honor Go 1.21+
|
|
25
|
+
GOTOOLCHAIN semantics. When a go.mod file contains a 'toolchain goX.Y.Z' directive and
|
|
26
|
+
GOTOOLCHAIN is set to 'auto' (the default for Go 1.21+), Go will automatically download the
|
|
27
|
+
toolchain version specified in go.mod rather than using the version installed by setup-go.
|
|
28
|
+
The workflow runs with a different Go version than the one specified in the go-version: input,
|
|
29
|
+
causing unexpected behavior, build failures, or unintended Go version usage. In v5, setup-go
|
|
30
|
+
implicitly set GOTOOLCHAIN=local, preventing automatic toolchain downloads. v6 removed this
|
|
31
|
+
implicit override, meaning go.mod toolchain directives now take effect in CI.
|
|
32
|
+
fix: |
|
|
33
|
+
Set GOTOOLCHAIN=local in the step environment to force Go to use exactly the version installed
|
|
34
|
+
by setup-go, ignoring the toolchain directive in go.mod. Alternatively, align the go-version:
|
|
35
|
+
input with the toolchain directive in go.mod, or use go-version-file: go.mod to let setup-go
|
|
36
|
+
read the version directly from the module file.
|
|
37
|
+
fix_code:
|
|
38
|
+
- language: yaml
|
|
39
|
+
label: "Set GOTOOLCHAIN=local to prevent auto-download — use exactly the installed version"
|
|
40
|
+
code: |
|
|
41
|
+
- name: Set up Go
|
|
42
|
+
uses: actions/setup-go@v6
|
|
43
|
+
with:
|
|
44
|
+
go-version: '1.22'
|
|
45
|
+
|
|
46
|
+
- name: Build
|
|
47
|
+
run: go build ./...
|
|
48
|
+
env:
|
|
49
|
+
GOTOOLCHAIN: local # disables auto-download; uses only the version from setup-go
|
|
50
|
+
- language: yaml
|
|
51
|
+
label: "Or read go-version directly from go.mod to stay aligned with the toolchain directive"
|
|
52
|
+
code: |
|
|
53
|
+
- name: Set up Go
|
|
54
|
+
uses: actions/setup-go@v6
|
|
55
|
+
with:
|
|
56
|
+
go-version-file: go.mod # reads 'go X.Y' line from go.mod; stays in sync automatically
|
|
57
|
+
prevention:
|
|
58
|
+
- "After upgrading to setup-go@v6, verify the actual Go version used in builds matches the go-version: input"
|
|
59
|
+
- "Add GOTOOLCHAIN=local to workflow env or per-step env to opt out of automatic toolchain download behavior"
|
|
60
|
+
- "Keep the go.mod toolchain directive in sync with the go-version: value specified in setup-go"
|
|
61
|
+
- "Use go-version-file: go.mod instead of an explicit go-version: value to stay automatically aligned"
|
|
62
|
+
docs:
|
|
63
|
+
- url: https://github.com/actions/setup-go/releases/tag/v6.0.0
|
|
64
|
+
label: "actions/setup-go v6.0.0 release notes — toolchain handling breaking change"
|
|
65
|
+
- url: https://github.com/actions/setup-go/pull/460
|
|
66
|
+
label: "actions/setup-go PR#460: Improve toolchain handling"
|
|
67
|
+
- url: https://go.dev/doc/toolchain
|
|
68
|
+
label: "Go documentation: Toolchains — GOTOOLCHAIN environment variable"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
id: silent-failures-062
|
|
2
|
+
title: "actions/cache save failure emits Warning annotation but does not fail the workflow step"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- cache
|
|
7
|
+
- cache-save
|
|
8
|
+
- warning
|
|
9
|
+
- non-fatal
|
|
10
|
+
- false-success
|
|
11
|
+
- cache-service
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'Warning: Failed to save:.*Failed to CreateCacheEntry.*non-retryable'
|
|
14
|
+
flags: i
|
|
15
|
+
- regex: 'Warning: Failed to restore:.*Failed to GetCacheEntryDownloadURL.*non-retryable'
|
|
16
|
+
flags: i
|
|
17
|
+
error_messages:
|
|
18
|
+
- "Warning: Failed to save: Failed to CreateCacheEntry: Received non-retryable error: Failed request: (404) Not Found: invalid request"
|
|
19
|
+
- "Warning: Failed to restore: Failed to GetCacheEntryDownloadURL: Received non-retryable error: Failed request: (404) Not Found: invalid request"
|
|
20
|
+
root_cause: |
|
|
21
|
+
The actions/cache and actions/cache/save actions treat upload failures as non-fatal warnings
|
|
22
|
+
rather than step errors. When the GitHub cache backend returns an error (4xx/5xx), is
|
|
23
|
+
unavailable, or rejects the request, the action emits a yellow Warning annotation in the
|
|
24
|
+
workflow log but exits with code 0. The workflow step is marked green (success). Downstream
|
|
25
|
+
runs then see genuine "Cache not found" misses since nothing was persisted. Developers assume
|
|
26
|
+
the cache was saved based on the green checkmark, spending hours debugging unreliable cache
|
|
27
|
+
restore before finding the Warning annotation buried in the save step output. This behavior is
|
|
28
|
+
intentional — cache is treated as an optimization, not a requirement — but it means failures
|
|
29
|
+
are invisible unless annotations are actively checked.
|
|
30
|
+
fix: |
|
|
31
|
+
Check step-level annotations (the yellow warning triangle icon) on cache save steps, not just
|
|
32
|
+
the green/red status indicator. Use fail-on-cache-miss: true on restore steps when cache
|
|
33
|
+
availability is critical to your build speed so that a missing cache surfaces as a hard
|
|
34
|
+
failure. For save failures there is no built-in fail flag — add a downstream validation step
|
|
35
|
+
if guaranteed cache persistence is required. Always use supported cache action versions
|
|
36
|
+
(actions/cache@v3 or @v4) to ensure compatibility with the current cache backend service.
|
|
37
|
+
fix_code:
|
|
38
|
+
- language: yaml
|
|
39
|
+
label: "Use fail-on-cache-miss on restore to surface missing cache as an error"
|
|
40
|
+
code: |
|
|
41
|
+
- name: Restore build cache
|
|
42
|
+
id: restore-cache
|
|
43
|
+
uses: actions/cache/restore@v4
|
|
44
|
+
with:
|
|
45
|
+
key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }}
|
|
46
|
+
path: ~/.npm
|
|
47
|
+
fail-on-cache-miss: true # step fails with error if nothing was previously saved
|
|
48
|
+
|
|
49
|
+
- name: Save build cache
|
|
50
|
+
if: always()
|
|
51
|
+
uses: actions/cache/save@v4
|
|
52
|
+
with:
|
|
53
|
+
key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }}
|
|
54
|
+
path: ~/.npm
|
|
55
|
+
prevention:
|
|
56
|
+
- "Always check workflow annotations (yellow warning triangle) in addition to the step pass/fail status"
|
|
57
|
+
- "Use actions/cache@v4 or @v3 — deprecated pinned SHAs may fail silently after the cache backend migration"
|
|
58
|
+
- "Use fail-on-cache-miss: true on restore steps to make cache misses visible as hard failures"
|
|
59
|
+
- "Monitor the actions/cache issue tracker when cache restore reliability degrades — backend incidents are reported quickly"
|
|
60
|
+
docs:
|
|
61
|
+
- url: https://github.com/actions/cache/issues/1541
|
|
62
|
+
label: "actions/cache#1541: Bug: Failed to CreateCacheEntry (29 reactions, Feb 2025)"
|
|
63
|
+
- url: https://github.com/actions/cache/discussions/1510
|
|
64
|
+
label: "actions/cache Discussion#1510: Deprecation Notice — upgrade to latest before Feb 2025"
|
|
65
|
+
- url: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows
|
|
66
|
+
label: "GitHub Docs: Caching dependencies — fail-on-cache-miss option"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
id: silent-failures-061
|
|
2
|
+
title: "`workflow_call` boolean inputs evaluate as strings — `if: inputs.flag` is truthy even when caller passes `false`"
|
|
3
|
+
category: silent-failures
|
|
4
|
+
severity: silent-failure
|
|
5
|
+
tags:
|
|
6
|
+
- workflow-call
|
|
7
|
+
- reusable-workflow
|
|
8
|
+
- boolean
|
|
9
|
+
- inputs
|
|
10
|
+
- if-condition
|
|
11
|
+
- type-coercion
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: 'inputs\.[a-z_-]+\s*(?:==\s*true|==\s*false)?'
|
|
14
|
+
flags: i
|
|
15
|
+
error_messages: []
|
|
16
|
+
root_cause: |
|
|
17
|
+
In `on.workflow_call` inputs, declaring `type: boolean` describes the intended type of
|
|
18
|
+
the input — it does NOT cause the value to be passed as a JSON boolean. At runtime,
|
|
19
|
+
inside the called workflow, `${{ inputs.my_flag }}` always evaluates to the string
|
|
20
|
+
`'true'` or `'false'`.
|
|
21
|
+
|
|
22
|
+
Because non-empty strings are truthy in GitHub Actions expression evaluation:
|
|
23
|
+
- `if: inputs.my_flag` evaluates the string `'false'` as TRUTHY (non-empty string = true)
|
|
24
|
+
- Steps and jobs conditioned on `if: inputs.my_flag` run even when the caller passes false
|
|
25
|
+
|
|
26
|
+
This is distinct from the `workflow_dispatch` boolean input coercion issue (silent-failures-053)
|
|
27
|
+
in that it affects all callers of a reusable workflow simultaneously. A single bug in a
|
|
28
|
+
widely-used reusable workflow can cause unintended deploys, notifications, or expensive
|
|
29
|
+
steps to run across every calling workflow.
|
|
30
|
+
|
|
31
|
+
Example of the failure:
|
|
32
|
+
Caller passes `deploy: false` intending to skip deployment.
|
|
33
|
+
Reusable workflow has `if: inputs.deploy` on its deploy job.
|
|
34
|
+
The deploy job runs because the string 'false' is non-empty and therefore truthy.
|
|
35
|
+
fix: |
|
|
36
|
+
Always compare boolean inputs against the boolean literal `true` using `==`, or use
|
|
37
|
+
`fromJSON()` to parse the string to an actual boolean value:
|
|
38
|
+
|
|
39
|
+
CORRECT patterns:
|
|
40
|
+
- `if: inputs.my_flag == true` — Actions coerces the string 'true' to boolean true for == comparison
|
|
41
|
+
- `if: fromJSON(inputs.my_flag)` — parses string 'true'/'false' to actual boolean
|
|
42
|
+
|
|
43
|
+
WRONG patterns:
|
|
44
|
+
- `if: inputs.my_flag` — always true for non-empty strings ('false' is truthy)
|
|
45
|
+
- `if: inputs.my_flag == 'true'` — works but fragile; fails if value is boolean true not string
|
|
46
|
+
fix_code:
|
|
47
|
+
- language: yaml
|
|
48
|
+
label: "Correct boolean input handling in reusable workflow"
|
|
49
|
+
code: |
|
|
50
|
+
# reusable.yml
|
|
51
|
+
on:
|
|
52
|
+
workflow_call:
|
|
53
|
+
inputs:
|
|
54
|
+
deploy:
|
|
55
|
+
type: boolean
|
|
56
|
+
default: false
|
|
57
|
+
notify:
|
|
58
|
+
type: boolean
|
|
59
|
+
default: true
|
|
60
|
+
|
|
61
|
+
jobs:
|
|
62
|
+
deploy:
|
|
63
|
+
# Correct: == true comparison handles string-to-boolean coercion
|
|
64
|
+
if: inputs.deploy == true
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
steps:
|
|
67
|
+
- run: echo "Deploying..."
|
|
68
|
+
|
|
69
|
+
notify:
|
|
70
|
+
# Alternative: fromJSON() parses string to actual boolean
|
|
71
|
+
if: fromJSON(inputs.notify)
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- run: echo "Notifying..."
|
|
75
|
+
|
|
76
|
+
- language: yaml
|
|
77
|
+
label: "Caller side — no change needed; fix is in the reusable workflow"
|
|
78
|
+
code: |
|
|
79
|
+
# caller.yml — calling the reusable workflow
|
|
80
|
+
jobs:
|
|
81
|
+
call-reusable:
|
|
82
|
+
uses: ./.github/workflows/reusable.yml
|
|
83
|
+
with:
|
|
84
|
+
deploy: false # This passes as string 'false' — fix is in reusable.yml
|
|
85
|
+
notify: true
|
|
86
|
+
prevention:
|
|
87
|
+
- "Audit all reusable workflows for bare `if: inputs.<name>` patterns where the input is type boolean"
|
|
88
|
+
- "Use `if: inputs.<flag> == true` consistently — never `if: inputs.<flag>` for boolean inputs"
|
|
89
|
+
- "Add a comment in reusable workflows noting that boolean inputs are strings at runtime"
|
|
90
|
+
- "Test callers with both `true` and `false` values and verify both branches execute as expected"
|
|
91
|
+
- "Consider wrapping boolean inputs in `fromJSON()` at the point of use for clarity"
|
|
92
|
+
docs:
|
|
93
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#literals"
|
|
94
|
+
label: "Expressions — literals and type coercion — GitHub Docs"
|
|
95
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows#using-inputs-and-secrets-in-a-reusable-workflow"
|
|
96
|
+
label: "Reusable workflows — using inputs — GitHub Docs"
|
|
97
|
+
- url: "https://github.com/orgs/community/discussions/45843"
|
|
98
|
+
label: "workflow_call boolean inputs evaluated as truthy strings — GitHub Community #45843"
|
package/package.json
CHANGED