@htekdev/actions-debugger 1.0.17 → 1.0.18

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.
@@ -0,0 +1,89 @@
1
+ id: concurrency-timing-015
2
+ title: "Cancelling workflow from the Actions runs list leaves downstream needs/always() jobs zombie-queued"
3
+ category: concurrency-timing
4
+ severity: error
5
+ tags:
6
+ - cancel
7
+ - needs
8
+ - always
9
+ - zombie
10
+ - matrix
11
+ - queued
12
+ patterns:
13
+ - regex: "queued.*cancel|cancel.*queued"
14
+ flags: "i"
15
+ - regex: "This run has been cancelled"
16
+ flags: "i"
17
+ error_messages:
18
+ - "This run has been cancelled"
19
+ - "Job is queued"
20
+ root_cause: |
21
+ When a workflow is cancelled from the GitHub Actions **runs list page**
22
+ (https://github.com/{owner}/{repo}/actions), the cancellation signal does not
23
+ propagate to all pending/queued jobs. Specifically:
24
+
25
+ - Running jobs receive the cancellation correctly and terminate.
26
+ - Jobs that were not yet picked up by a runner but are waiting in a `needs:` chain
27
+ (especially with `if: always()`) remain stuck indefinitely in a "queued" state.
28
+ - These "zombie" queued jobs never start and never cancel on their own.
29
+
30
+ The problem is reproducible with:
31
+ 1. A large matrix build (many parallel jobs)
32
+ 2. A finalizer/aggregator job using `needs: [matrix-job]` + `if: always()`
33
+ 3. Cancelling from the Actions overview list before all matrix jobs have started
34
+
35
+ The finalizer job enters the "queued" state but never receives the cancel signal
36
+ because the runs-list cancel does not do a full transitive cancel of downstream jobs.
37
+
38
+ Reported in actions/runner#4411 (May 2026) — closed as a known UI inconsistency.
39
+ fix: |
40
+ **Workaround (immediate):** To fully cancel a stuck workflow:
41
+ 1. Click through to the specific workflow run detail page
42
+ (not the Actions overview list)
43
+ 2. Click the red "Cancel workflow" button from inside the run page
44
+
45
+ This second cancel terminates all zombie queued jobs immediately.
46
+
47
+ **Structural mitigation:** Add a `timeout-minutes` to your finalizer/aggregator job
48
+ so it self-terminates even if the cancel signal is not received:
49
+
50
+ jobs:
51
+ aggregate:
52
+ needs: [build]
53
+ if: always()
54
+ timeout-minutes: 5
55
+ runs-on: ubuntu-latest
56
+ steps:
57
+ - run: echo "Aggregating results"
58
+ fix_code:
59
+ - language: yaml
60
+ label: "Add timeout-minutes to always() jobs as a safety net"
61
+ code: |
62
+ jobs:
63
+ build:
64
+ strategy:
65
+ matrix:
66
+ target: [a, b, c, d, e, f, g, h]
67
+ runs-on: ubuntu-latest
68
+ steps:
69
+ - run: make ${{ matrix.target }}
70
+
71
+ aggregate:
72
+ needs: [build]
73
+ if: always()
74
+ timeout-minutes: 5 # prevents zombie queueing if cancel signal is lost
75
+ runs-on: ubuntu-latest
76
+ steps:
77
+ - run: echo "Build result ${{ needs.build.result }}"
78
+ prevention:
79
+ - "Always add `timeout-minutes` to finalizer jobs that use `if: always()` to limit zombie lifetime"
80
+ - "When cancelling a stuck workflow, navigate to the specific run page and use the in-run Cancel button"
81
+ - "Avoid relying on the Actions overview Cancel — it has incomplete propagation to queued downstream jobs"
82
+ - "Consider using a concurrency group to auto-cancel the entire workflow on re-trigger instead of manual cancellation"
83
+ docs:
84
+ - url: "https://github.com/actions/runner/issues/4411"
85
+ label: "actions/runner#4411 — Cancellation from runs list leaves needs+always() jobs zombie-queued"
86
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-jobs-in-a-workflow#defining-prerequisite-jobs"
87
+ label: "GitHub Docs — Defining prerequisite jobs (needs:)"
88
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/expressions#always"
89
+ label: "GitHub Docs — always() status check function"
@@ -0,0 +1,76 @@
1
+ id: concurrency-timing-014
2
+ title: "github.head_ref empty on push events collapses concurrency group and cancels unrelated runs"
3
+ category: concurrency-timing
4
+ severity: silent-failure
5
+ tags:
6
+ - concurrency
7
+ - head_ref
8
+ - push-event
9
+ - cancel-in-progress
10
+ - cross-branch
11
+ patterns:
12
+ - regex: "Run .*? was cancelled"
13
+ flags: "i"
14
+ - regex: "cancel-in-progress.*true"
15
+ flags: "i"
16
+ error_messages:
17
+ - "Run <workflow-name> was cancelled"
18
+ - "This run has been cancelled by a newer run"
19
+ root_cause: |
20
+ On `push` events, `github.head_ref` is always an empty string — it is only populated
21
+ for `pull_request` and `pull_request_target` events where a source branch exists.
22
+
23
+ When a concurrency group expression uses `github.head_ref` without a fallback value,
24
+ every push to any branch evaluates to the same empty-string group key. With
25
+ `cancel-in-progress: true`, each new push cancels any other in-progress run across
26
+ ALL branches — including unrelated feature branches and main.
27
+
28
+ Example of the broken pattern:
29
+ concurrency:
30
+ group: ${{ github.workflow }}-${{ github.head_ref }}
31
+ cancel-in-progress: true
32
+
33
+ Pushing to `main` and `feature-branch` simultaneously means the second push cancels
34
+ the first, even though they are completely unrelated.
35
+
36
+ This is confirmed by GitHub Actions documentation and illustrated by Gitea's
37
+ implementation fix for the same semantic (go-gitea/gitea#37311, April 2026).
38
+ fix: |
39
+ Use the `||` fallback operator so that push events fall back to `github.run_id`,
40
+ which is unique per workflow run and thus prevents any cross-run cancellation on push:
41
+
42
+ concurrency:
43
+ group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
44
+ cancel-in-progress: true
45
+
46
+ With this pattern:
47
+ - On pull_request events: `github.head_ref` is the source branch name — runs for the
48
+ same branch/PR cancel each other (desired behavior).
49
+ - On push events: `github.head_ref` is empty, so `github.run_id` is used — each
50
+ push run gets a unique group and is never cancelled by an unrelated run.
51
+ fix_code:
52
+ - language: yaml
53
+ label: "Correct: fallback to github.run_id on push events"
54
+ code: |
55
+ concurrency:
56
+ group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
57
+ cancel-in-progress: true
58
+ - language: yaml
59
+ label: "Wrong: head_ref alone is empty on push — collapses all pushes to one group"
60
+ code: |
61
+ # DO NOT USE — cancels unrelated push runs
62
+ concurrency:
63
+ group: ${{ github.workflow }}-${{ github.head_ref }}
64
+ cancel-in-progress: true
65
+ prevention:
66
+ - "Always use `${{ github.head_ref || github.run_id }}` in concurrency groups, never `github.head_ref` alone"
67
+ - "Test concurrency behavior with simultaneous pushes to two unrelated branches to verify isolation"
68
+ - "If you only want per-PR cancellation and not per-push cancellation, restrict the concurrency block to PR events using an `if:` condition"
69
+ - "Add a CI job that verifies no two concurrent push runs share a group key"
70
+ docs:
71
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs"
72
+ label: "GitHub Docs — Controlling workflow concurrency"
73
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push"
74
+ label: "GitHub Docs — push event payload (head_ref is absent)"
75
+ - url: "https://github.com/go-gitea/gitea/pull/37311"
76
+ label: "go-gitea/gitea#37311 — Fix actions concurrency groups cross-branch leak"
@@ -0,0 +1,103 @@
1
+ id: permissions-auth-020
2
+ title: "GHEC enterprise OIDC issuer slug causes token verification mismatch with cloud tools"
3
+ category: permissions-auth
4
+ severity: error
5
+ tags:
6
+ - oidc
7
+ - enterprise
8
+ - ghec
9
+ - issuer
10
+ - attestation
11
+ - cloud-auth
12
+ patterns:
13
+ - regex: "expected issuer.*got.*githubusercontent\\.com/[a-z0-9-]+"
14
+ flags: "i"
15
+ - regex: "issuer mismatch.*token\\.actions\\.githubusercontent\\.com"
16
+ flags: "i"
17
+ - regex: "no matching CertificateIdentity.*issuer"
18
+ flags: "i"
19
+ error_messages:
20
+ - "Error: verifying with issuer \"sigstore.dev\": failed to verify certificate identity: no matching CertificateIdentity found, last error: expected issuer value \"https://token.actions.githubusercontent.com\", got \"https://token.actions.githubusercontent.com/my-enterprise\""
21
+ - "Error: expected issuer to be https://token.actions.githubusercontent.com, got https://token.actions.githubusercontent.com/my-enterprise"
22
+ - "UnauthorizedException: Issuer does not match configured value"
23
+ root_cause: |
24
+ GitHub Enterprise Cloud (GHEC) organizations can customize the OIDC token issuer
25
+ claim to include their unique enterprise slug, for example:
26
+
27
+ Default issuer: https://token.actions.githubusercontent.com
28
+ Enterprise issuer: https://token.actions.githubusercontent.com/my-enterprise
29
+
30
+ This customization is a security feature (prevents tokens from one enterprise from
31
+ being used against another's cloud resources), but it creates a mismatch for:
32
+
33
+ 1. **`gh attestation verify`**: The CLI tool defaults to verifying against the
34
+ standard issuer. If the enterprise issuer is active, verification fails with
35
+ "expected issuer … got … my-enterprise" unless `--cert-oidc-issuer` is passed.
36
+
37
+ 2. **Cloud provider trust policies**: AWS, Azure, GCP OIDC federation configured
38
+ against the default issuer URL rejects enterprise-slug tokens with auth errors.
39
+
40
+ 3. **Third-party actions**: Actions that validate the OIDC token internally may
41
+ hardcode the default issuer and fail silently.
42
+
43
+ This affects any GHEC organization that has enabled the enterprise issuer
44
+ customization (via Enterprise Settings → OIDC provider → Customize issuer).
45
+ fix: |
46
+ **For `gh attestation verify`:** Pass the enterprise-specific issuer URL:
47
+
48
+ gh attestation verify artifact.zip \
49
+ --owner my-enterprise \
50
+ --cert-oidc-issuer https://token.actions.githubusercontent.com/my-enterprise
51
+
52
+ **For AWS IAM:** Update the OIDC provider URL in IAM to use the enterprise issuer:
53
+
54
+ Provider URL: https://token.actions.githubusercontent.com/my-enterprise
55
+ Audience: sts.amazonaws.com
56
+
57
+ **For Azure Workload Identity:** Set the issuer in the federated credential to:
58
+
59
+ https://token.actions.githubusercontent.com/my-enterprise
60
+
61
+ **For GCP Workload Identity Pool:** Set the issuer URI in the OIDC provider config
62
+ to the enterprise issuer URL.
63
+
64
+ **To check your enterprise issuer:**
65
+ Visit https://token.actions.githubusercontent.com/my-enterprise/.well-known/openid-configuration
66
+ — if it returns a valid document, enterprise issuer customization is active.
67
+ fix_code:
68
+ - language: yaml
69
+ label: "gh attestation verify with enterprise issuer"
70
+ code: |
71
+ - name: Attest artifact
72
+ run: |
73
+ gh attestation verify dist/my-app \
74
+ --owner my-enterprise \
75
+ --cert-oidc-issuer https://token.actions.githubusercontent.com/my-enterprise
76
+ env:
77
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
78
+ - language: yaml
79
+ label: "AWS credentials action with enterprise OIDC issuer"
80
+ code: |
81
+ # In AWS IAM, create the OIDC identity provider with the enterprise issuer URL
82
+ # instead of the default https://token.actions.githubusercontent.com
83
+ # Then configure the federated role trust policy:
84
+ {
85
+ "Condition": {
86
+ "StringEquals": {
87
+ "token.actions.githubusercontent.com/my-enterprise:aud": "sts.amazonaws.com",
88
+ "token.actions.githubusercontent.com/my-enterprise:sub": "repo:my-enterprise/my-repo:ref:refs/heads/main"
89
+ }
90
+ }
91
+ }
92
+ prevention:
93
+ - "Document whether your GHEC enterprise has the custom issuer enabled — include it in your OIDC onboarding checklist"
94
+ - "Store the full enterprise issuer URL in a reusable variable or org-level secret to avoid hardcoding it in every workflow"
95
+ - "When setting up new cloud OIDC federation, verify the issuer from the well-known endpoint rather than copying from docs"
96
+ - "Pin `gh attestation verify` calls to always include `--cert-oidc-issuer` in enterprise repos"
97
+ docs:
98
+ - url: "https://docs.github.com/en/enterprise-cloud@latest/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise"
99
+ label: "GitHub Docs — Customizing the OIDC issuer value for an enterprise"
100
+ - url: "https://github.com/cli/cli/pull/9616"
101
+ label: "cli/cli#9616 — Better messaging for attestation verify custom issuer mismatch error"
102
+ - url: "https://token.actions.githubusercontent.com/.well-known/openid-configuration"
103
+ label: "GitHub OIDC well-known endpoint (check your enterprise variant at /{slug}/.well-known/openid-configuration)"
@@ -0,0 +1,111 @@
1
+ id: permissions-auth-019
2
+ title: "OIDC sub claim breaks cloud trust policies after repository rename or transfer"
3
+ category: permissions-auth
4
+ severity: error
5
+ tags:
6
+ - oidc
7
+ - sub-claim
8
+ - repo-rename
9
+ - repo-transfer
10
+ - cloud-auth
11
+ - aws
12
+ - azure
13
+ - gcp
14
+ patterns:
15
+ - regex: "no matching CertificateIdentity"
16
+ flags: "i"
17
+ - regex: "sub.*does not match|subject.*mismatch|token validation failed"
18
+ flags: "i"
19
+ - regex: "Error: Could not assume role.*subject claim"
20
+ flags: "i"
21
+ error_messages:
22
+ - "Error: No matching identity found in OIDC token"
23
+ - "Error: Could not assume role with web identity: NotAuthorized"
24
+ - "no matching CertificateIdentity found, last error: certificate identity not found"
25
+ - "subject claim mismatch: expected repo:old-owner/old-repo, got repo:new-owner/new-repo"
26
+ root_cause: |
27
+ GitHub Actions OIDC tokens include a `sub` (subject) claim that encodes the repository
28
+ owner and name using their **mutable string names**, for example:
29
+
30
+ repo:octocat/my-app:ref:refs/heads/main
31
+
32
+ Cloud providers (AWS, Azure, GCP) and tools like `gh attestation verify` configure
33
+ trust policies against this `sub` claim value. When a repository is **renamed** or
34
+ **transferred** to a different organization, the `sub` claim changes immediately:
35
+
36
+ Before transfer: repo:old-org/my-app:ref:refs/heads/main
37
+ After transfer: repo:new-org/my-app:ref:refs/heads/main
38
+
39
+ All cloud trust policies that reference the old `sub` format instantly break.
40
+ OIDC authentication fails with "subject mismatch" or "no matching identity" errors
41
+ even though the workflow code and secrets are identical.
42
+
43
+ GitHub announced immutable sub claims (appending numeric owner/repo IDs) on
44
+ 2026-04-23, but existing repositories must opt in explicitly. New repositories
45
+ created or transferred after June 18, 2026 automatically use the new format.
46
+
47
+ Old (mutable) format: repo:owner/repo:ref:refs/heads/main
48
+ New (immutable) format: repo:owner-123456/repo-789012:ref:refs/heads/main
49
+ fix: |
50
+ **Before renaming or transferring a repo:**
51
+
52
+ 1. Enable immutable subject claims in repository or organization OIDC settings
53
+ (Settings → Actions → General → OIDC subject claims → Enable immutable format).
54
+ 2. Use the preview endpoint to see the new sub claim format before it takes effect.
55
+ 3. Update all cloud provider trust policies and IAM role conditions to accept the
56
+ new immutable format (includes numeric IDs alongside names).
57
+
58
+ **After an unplanned rename/transfer:**
59
+
60
+ 1. Identify the new `sub` claim value by decoding the OIDC token from a failed run
61
+ or using the GitHub OIDC preview API.
62
+ 2. Update the trust policy in each cloud provider to reference the new repo path.
63
+ 3. Optionally opt into immutable claims to future-proof against further renames.
64
+
65
+ **For AWS IAM (StringLike condition):**
66
+ Condition:
67
+ StringLike:
68
+ token.actions.githubusercontent.com:sub:
69
+ - "repo:new-org/new-repo:*"
70
+ fix_code:
71
+ - language: yaml
72
+ label: "AWS IAM trust policy update after repo rename"
73
+ code: |
74
+ # Update the StringLike condition in your IAM role trust policy
75
+ # Replace old path with new path after rename/transfer
76
+ {
77
+ "Condition": {
78
+ "StringLike": {
79
+ "token.actions.githubusercontent.com:sub": "repo:new-org/new-repo:*"
80
+ }
81
+ }
82
+ }
83
+ - language: yaml
84
+ label: "Workflow: use immutable OIDC subject for new repos (June 18 2026+)"
85
+ code: |
86
+ # No workflow change needed — immutable format is set in repo/org settings.
87
+ # Once enabled, the sub claim becomes:
88
+ # repo:owner-123456/repo-789012:ref:refs/heads/main
89
+ # Update cloud trust policies to use the new format BEFORE enabling immutable claims.
90
+ jobs:
91
+ deploy:
92
+ permissions:
93
+ id-token: write
94
+ contents: read
95
+ steps:
96
+ - uses: aws-actions/configure-aws-credentials@v4
97
+ with:
98
+ role-to-assume: arn:aws:iam::123456789:role/my-role
99
+ aws-region: us-east-1
100
+ prevention:
101
+ - "Enable immutable OIDC subject claims before any rename or transfer to prevent trust policy breakage"
102
+ - "Store the expected sub claim value in cloud trust policies as a wildcard (`repo:owner/repo:*`) rather than exact paths to tolerate ref changes"
103
+ - "Add OIDC trust policy updates to your repo rename/transfer runbook"
104
+ - "New repos created after June 18, 2026 automatically use immutable sub claims — update trust policies accordingly"
105
+ docs:
106
+ - url: "https://github.blog/changelog/2026-04-23-immutable-subject-claims-for-github-actions-oidc-tokens/"
107
+ label: "GitHub Changelog 2026-04-23 — Immutable subject claims for GitHub Actions OIDC tokens"
108
+ - url: "https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect"
109
+ label: "GitHub Docs — About security hardening with OpenID Connect"
110
+ - url: "https://docs.github.com/en/enterprise-cloud@latest/actions/reference/security/oidc"
111
+ label: "GitHub Docs — OIDC token claims reference"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@htekdev/actions-debugger",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "description": "65+ real GitHub Actions errors, queryable by agents. MCP server + Copilot skills + error database.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",