@htekdev/actions-debugger 1.0.14 → 1.0.16
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/dist/db/search.js +3 -1
- package/dist/db/search.js.map +1 -1
- package/dist/tools/suggest-fix.d.ts.map +1 -1
- package/dist/tools/suggest-fix.js +5 -1
- package/dist/tools/suggest-fix.js.map +1 -1
- package/errors/caching-artifacts/cache-key-too-long.yml +93 -0
- package/errors/caching-artifacts/cache-path-not-exist-skipped.yml +152 -0
- package/errors/caching-artifacts/cache-save-same-key-html-conflict.yml +109 -0
- package/errors/caching-artifacts/docker-buildx-gha-cache-capacity.yml +107 -0
- package/errors/caching-artifacts/setup-ruby-bundler-ephemeral-workdir-cache-miss.yml +147 -0
- package/errors/caching-artifacts/upload-artifact-v3-retirement-blocked.yml +123 -0
- package/errors/caching-artifacts/upload-artifact-v4-large-file-macos-hang.yml +111 -0
- package/errors/concurrency-timing/always-cleanup-5min-forced-kill.yml +140 -0
- package/errors/concurrency-timing/concurrency-group-env-context-undefined.yml +99 -0
- package/errors/concurrency-timing/required-check-pending-path-filter-skip.yml +160 -0
- package/errors/concurrency-timing/wait-timer-cancel-in-progress-starvation.yml +125 -0
- package/errors/known-unsolved/composite-action-step-timeout-minutes-ignored.yml +146 -0
- package/errors/known-unsolved/reusable-workflow-no-composite-action-call.yml +116 -0
- package/errors/known-unsolved/schedule-trigger-default-branch-only.yml +113 -0
- package/errors/known-unsolved/secrets-not-allowed-in-if-conditions.yml +149 -0
- package/errors/known-unsolved/workflow-50-rerun-limit.yml +110 -0
- package/errors/permissions-auth/check-run-status-modification-blocked.yml +134 -0
- package/errors/permissions-auth/dependabot-pr-secrets-unavailable.yml +133 -0
- package/errors/permissions-auth/fine-grained-pat-deployment-write-required.yml +146 -0
- package/errors/permissions-auth/github-app-installation-token-new-format.yml +124 -0
- package/errors/permissions-auth/github-packages-read-requires-packages-permission.yml +128 -0
- package/errors/permissions-auth/oidc-id-token-write-permission-missing.yml +169 -0
- package/errors/permissions-auth/permissions-empty-block-removes-contents-read.yml +97 -0
- package/errors/permissions-auth/reusable-workflow-permissions-not-inherited.yml +114 -0
- package/errors/runner-environment/checkout-windows-ebusy-lock.yml +124 -0
- package/errors/runner-environment/deprecated-action-version-auto-rejected.yml +89 -0
- package/errors/runner-environment/github-hosted-runner-disk-space-full.yml +85 -0
- package/errors/runner-environment/github-path-same-step-not-found.yml +114 -0
- package/errors/runner-environment/github-script-v6-octokit-rest-actions-not-function.yml +87 -0
- package/errors/runner-environment/macos-13-deprecation-brownout.yml +93 -0
- package/errors/runner-environment/macos-15-mono-nuget-removed.yml +151 -0
- package/errors/runner-environment/macos-15-xcode-simulator-sdk-policy.yml +141 -0
- package/errors/runner-environment/multi-runtime-nov2025-removal.yml +120 -0
- package/errors/runner-environment/runner-oom-exit-code-137.yml +117 -0
- package/errors/runner-environment/setup-go-go123-telemetry-cache-failure.yml +92 -0
- package/errors/runner-environment/setup-java-distribution-required.yml +108 -0
- package/errors/runner-environment/ubuntu-2004-retirement-brownout.yml +107 -0
- package/errors/runner-environment/windows-latest-d-drive-removed.yml +104 -0
- package/errors/runner-environment/windows-vs2026-cuda-host-compiler-unsupported.yml +145 -0
- package/errors/silent-failures/event-commits-empty-on-workflow-dispatch.yml +110 -0
- package/errors/silent-failures/fetch-tags-depth-one-silent-no-op.yml +77 -0
- package/errors/silent-failures/github-env-multiline-value-truncated.yml +127 -0
- package/errors/silent-failures/github-sha-pr-merge-commit-not-head.yml +150 -0
- package/errors/silent-failures/job-output-masked-as-secret-empty.yml +147 -0
- package/errors/silent-failures/upload-artifact-permissions-stripped.yml +98 -0
- package/errors/triggers/pull-request-branches-filter-matches-base-not-head.yml +140 -0
- package/errors/triggers/push-event-fires-on-branch-delete.yml +129 -0
- package/errors/triggers/push-first-commit-before-sha-zeros.yml +160 -0
- package/errors/yaml-syntax/continue-on-error-env-context-rejected.yml +130 -0
- package/errors/yaml-syntax/fromjson-empty-string-crash.yml +99 -0
- package/errors/yaml-syntax/if-bang-negation-yaml-tag.yml +145 -0
- package/errors/yaml-syntax/local-action-path-always-top-level.yml +142 -0
- package/package.json +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
id: permissions-auth-014
|
|
2
|
+
title: "GitHub App Installation Tokens New Stateless Format Breaks Length/Regex Validation"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- github-app
|
|
7
|
+
- installation-token
|
|
8
|
+
- token-format
|
|
9
|
+
- stateless-token
|
|
10
|
+
- breaking-change
|
|
11
|
+
- auth
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "invalid.*token.*format|token.*validation.*failed"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "token.*length.*expected|token.*too long"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "Bad credentials|401.*installation.token"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "installation.*token.*truncated|column.*too long.*token"
|
|
20
|
+
flags: "i"
|
|
21
|
+
error_messages:
|
|
22
|
+
- "Bad credentials — installation token rejected by downstream validation"
|
|
23
|
+
- "column 'token' is too long for type character varying(40)"
|
|
24
|
+
- "token validation failed: expected length 40, got 520"
|
|
25
|
+
- "ERROR: value too long for type character(40)"
|
|
26
|
+
root_cause: |
|
|
27
|
+
Starting April 27, 2026, GitHub rolled out a **new stateless format** for GitHub App
|
|
28
|
+
installation tokens. The new tokens are approximately **520 characters** long and use
|
|
29
|
+
a new prefix, compared to the previous format which was a fixed **40-character**
|
|
30
|
+
opaque token.
|
|
31
|
+
|
|
32
|
+
**What changed:**
|
|
33
|
+
- Old format: 40-character token (e.g., `ghs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`)
|
|
34
|
+
- New format: ~520-character JWT-like stateless token with a new prefix scheme
|
|
35
|
+
|
|
36
|
+
**What breaks:**
|
|
37
|
+
1. **Database columns sized at `CHAR(40)` or `VARCHAR(40)`** — any app that stores
|
|
38
|
+
the token in a database field sized for the old format will fail with a column
|
|
39
|
+
truncation or "value too long" database error.
|
|
40
|
+
2. **Regex/length validation** — code that validates token format or length (e.g.,
|
|
41
|
+
`len(token) == 40`, or regex `^ghs_[a-f0-9]{36}$`) will reject the new tokens.
|
|
42
|
+
3. **HTTP header size limits** — some reverse proxies or WAFs with conservative
|
|
43
|
+
request header size limits may reject requests with the longer Authorization header.
|
|
44
|
+
4. **Logging or masking systems** — tools that detect or mask secrets by pattern may
|
|
45
|
+
not recognize the new format, causing tokens to appear in logs unmasked.
|
|
46
|
+
|
|
47
|
+
Source: GitHub Changelog 2026-04-24 — "Notice about upcoming new format for GitHub
|
|
48
|
+
App installation tokens"
|
|
49
|
+
fix: |
|
|
50
|
+
**Treat installation tokens as opaque strings of variable length.** Do not assume
|
|
51
|
+
a specific length, prefix pattern, or character set.
|
|
52
|
+
|
|
53
|
+
1. **Database**: Expand token storage columns to `VARCHAR(1024)` or use TEXT type
|
|
54
|
+
to future-proof against further format changes.
|
|
55
|
+
2. **Validation**: Remove length checks and pattern-based validation on token values.
|
|
56
|
+
Validate by making an authenticated API call (e.g., `GET /app/installations`) —
|
|
57
|
+
if it returns 200, the token is valid.
|
|
58
|
+
3. **HTTP proxy/WAF**: Increase `LargeClientHeader` or equivalent header size limits
|
|
59
|
+
if your infrastructure sits in front of the GitHub API requests.
|
|
60
|
+
4. **Secret masking**: Update any custom masking patterns to match the new prefix
|
|
61
|
+
or use `add-mask` in workflows rather than pattern-based masking.
|
|
62
|
+
fix_code:
|
|
63
|
+
- language: yaml
|
|
64
|
+
label: "Do not validate token length — use API to verify instead"
|
|
65
|
+
code: |
|
|
66
|
+
jobs:
|
|
67
|
+
setup:
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
permissions:
|
|
70
|
+
id-token: write
|
|
71
|
+
steps:
|
|
72
|
+
- name: Generate GitHub App installation token
|
|
73
|
+
id: app-token
|
|
74
|
+
uses: actions/create-github-app-token@v2
|
|
75
|
+
with:
|
|
76
|
+
app-id: ${{ vars.APP_ID }}
|
|
77
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
78
|
+
|
|
79
|
+
# ❌ Never validate token by length — new format is ~520 chars
|
|
80
|
+
# - run: |
|
|
81
|
+
# TOKEN="${{ steps.app-token.outputs.token }}"
|
|
82
|
+
# [ ${#TOKEN} -eq 40 ] || exit 1 # WILL FAIL with new format
|
|
83
|
+
|
|
84
|
+
# ✅ Verify by making an authenticated API call
|
|
85
|
+
- name: Verify token works
|
|
86
|
+
env:
|
|
87
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
88
|
+
run: gh api /app --jq '.name'
|
|
89
|
+
- language: yaml
|
|
90
|
+
label: "Mask the full new-format token in logs with add-mask"
|
|
91
|
+
code: |
|
|
92
|
+
jobs:
|
|
93
|
+
deploy:
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
steps:
|
|
96
|
+
- name: Generate token
|
|
97
|
+
id: app-token
|
|
98
|
+
uses: actions/create-github-app-token@v2
|
|
99
|
+
with:
|
|
100
|
+
app-id: ${{ vars.APP_ID }}
|
|
101
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
102
|
+
|
|
103
|
+
# Explicitly mask the token value so it does not appear in logs
|
|
104
|
+
# (pattern-based masking may not catch the new ~520-char format)
|
|
105
|
+
- name: Mask token
|
|
106
|
+
run: echo "::add-mask::${{ steps.app-token.outputs.token }}"
|
|
107
|
+
|
|
108
|
+
- name: Use token
|
|
109
|
+
env:
|
|
110
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
111
|
+
run: gh api /repos/${{ github.repository }}/releases
|
|
112
|
+
prevention:
|
|
113
|
+
- "Store GitHub App installation tokens in TEXT or VARCHAR(1024)+ columns — never size to the old 40-character format."
|
|
114
|
+
- "Remove any code that validates token length or matches against a fixed token-format regex."
|
|
115
|
+
- "Use `echo '::add-mask::TOKEN'` explicitly in workflows rather than relying on pattern-based log masking for App tokens."
|
|
116
|
+
- "Subscribe to GitHub Changelog token-format notices — https://github.blog/changelog/ — and test format changes in staging before they reach production."
|
|
117
|
+
- "Use `actions/create-github-app-token` (the official action) to generate tokens rather than rolling your own JWT exchange, so the action absorbs format changes automatically."
|
|
118
|
+
docs:
|
|
119
|
+
- url: "https://github.blog/changelog/2026-04-24-notice-about-upcoming-new-format-for-github-app-installation-tokens"
|
|
120
|
+
label: "GitHub Changelog: Notice about upcoming new format for GitHub App installation tokens"
|
|
121
|
+
- url: "https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation"
|
|
122
|
+
label: "GitHub Docs: Authenticating as a GitHub App installation"
|
|
123
|
+
- url: "https://github.com/actions/create-github-app-token"
|
|
124
|
+
label: "actions/create-github-app-token — official token generation action"
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
id: permissions-auth-011
|
|
2
|
+
title: "GITHUB_TOKEN Cannot Pull Private GitHub Packages Without packages: read Permission"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- github-packages
|
|
7
|
+
- ghcr
|
|
8
|
+
- container-registry
|
|
9
|
+
- packages-read
|
|
10
|
+
- github-token
|
|
11
|
+
- 401
|
|
12
|
+
- npm-packages
|
|
13
|
+
- docker
|
|
14
|
+
patterns:
|
|
15
|
+
- regex: "401 Unauthorized.*(?:ghcr\\.io|npm\\.pkg\\.github\\.com|maven\\.pkg\\.github\\.com)"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "(?:ghcr\\.io|npm\\.pkg\\.github\\.com).*401 Unauthorized"
|
|
18
|
+
flags: "i"
|
|
19
|
+
- regex: "Error response from daemon.*(?:unauthorized|denied).*ghcr\\.io"
|
|
20
|
+
flags: "i"
|
|
21
|
+
- regex: "npm ERR!\\s+401\\s+Unauthorized.*npm\\.pkg\\.github\\.com"
|
|
22
|
+
flags: "i"
|
|
23
|
+
- regex: "unauthorized: authentication required"
|
|
24
|
+
flags: "i"
|
|
25
|
+
error_messages:
|
|
26
|
+
- "Error response from daemon: pull access denied for ghcr.io/org/image, repository does not exist or may require 'docker login': denied: denied"
|
|
27
|
+
- "npm ERR! 401 Unauthorized - GET https://npm.pkg.github.com/@org%2fpkg/-/pkg-1.0.0.tgz"
|
|
28
|
+
- "unauthorized: authentication required"
|
|
29
|
+
- "Error: The process '/usr/bin/docker' failed with exit code 1"
|
|
30
|
+
root_cause: |
|
|
31
|
+
When a GitHub Actions workflow pulls from the GitHub Container Registry (ghcr.io) or from
|
|
32
|
+
GitHub Packages registries (npm.pkg.github.com, maven.pkg.github.com), the GITHUB_TOKEN
|
|
33
|
+
must have the `packages: read` permission explicitly granted.
|
|
34
|
+
|
|
35
|
+
The GITHUB_TOKEN's default permissions do NOT include `packages: read` unless:
|
|
36
|
+
1. The repository's default token permissions (Settings → Actions → General) are set to
|
|
37
|
+
"Read and write" at the org or repository level.
|
|
38
|
+
2. The workflow or job explicitly declares `permissions: packages: read`.
|
|
39
|
+
|
|
40
|
+
Commonly confused behaviors:
|
|
41
|
+
- A public package on ghcr.io from the same org is readable without auth — but a private
|
|
42
|
+
package or a package from a private repository requires authentication.
|
|
43
|
+
- GITHUB_TOKEN is scoped to the CURRENT repository. Pulling packages published from a
|
|
44
|
+
DIFFERENT repository in the same org may require a PAT even with packages: read set,
|
|
45
|
+
because the token only has read access to packages owned by the current repo.
|
|
46
|
+
- When `permissions:` is set at the workflow level (even just `contents: read`), all
|
|
47
|
+
unmentioned permissions are downgraded to 'none' — this silently removes packages: read
|
|
48
|
+
if it was previously implied by repository defaults.
|
|
49
|
+
|
|
50
|
+
This error is particularly common when:
|
|
51
|
+
- A workflow was working until someone added a top-level permissions: block for another
|
|
52
|
+
purpose (e.g., to set id-token: write for OIDC), silently removing packages: read
|
|
53
|
+
- The organization was migrated from packages-on-repo to ghcr.io
|
|
54
|
+
- Running workflows on forks (secrets unavailable, GITHUB_TOKEN scoped differently)
|
|
55
|
+
fix: |
|
|
56
|
+
Add `packages: read` to the workflow-level or job-level permissions block.
|
|
57
|
+
|
|
58
|
+
For cross-repository package pulls where GITHUB_TOKEN is insufficient, create a
|
|
59
|
+
Classic PAT with `read:packages` scope (or a fine-grained PAT with "Packages: read"
|
|
60
|
+
for the source repo) and store it as a repository secret.
|
|
61
|
+
fix_code:
|
|
62
|
+
- language: yaml
|
|
63
|
+
label: "Grant packages: read to pull from ghcr.io"
|
|
64
|
+
code: |
|
|
65
|
+
jobs:
|
|
66
|
+
build:
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
permissions:
|
|
69
|
+
contents: read
|
|
70
|
+
packages: read # ← required for ghcr.io and GitHub Packages
|
|
71
|
+
steps:
|
|
72
|
+
- name: Log in to GitHub Container Registry
|
|
73
|
+
uses: docker/login-action@v3
|
|
74
|
+
with:
|
|
75
|
+
registry: ghcr.io
|
|
76
|
+
username: ${{ github.actor }}
|
|
77
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
78
|
+
|
|
79
|
+
- name: Pull image
|
|
80
|
+
run: docker pull ghcr.io/${{ github.repository_owner }}/my-image:latest
|
|
81
|
+
- language: yaml
|
|
82
|
+
label: "Authenticate npm to GitHub Packages with GITHUB_TOKEN"
|
|
83
|
+
code: |
|
|
84
|
+
jobs:
|
|
85
|
+
install:
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
permissions:
|
|
88
|
+
contents: read
|
|
89
|
+
packages: read
|
|
90
|
+
steps:
|
|
91
|
+
- uses: actions/checkout@v4
|
|
92
|
+
|
|
93
|
+
- name: Authenticate npm to GitHub Packages
|
|
94
|
+
run: |
|
|
95
|
+
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> ~/.npmrc
|
|
96
|
+
echo "@myorg:registry=https://npm.pkg.github.com" >> ~/.npmrc
|
|
97
|
+
|
|
98
|
+
- run: npm ci
|
|
99
|
+
- language: yaml
|
|
100
|
+
label: "Cross-repo package pull using PAT (when GITHUB_TOKEN is insufficient)"
|
|
101
|
+
code: |
|
|
102
|
+
jobs:
|
|
103
|
+
build:
|
|
104
|
+
runs-on: ubuntu-latest
|
|
105
|
+
steps:
|
|
106
|
+
- name: Log in to ghcr.io with PAT (cross-repo packages)
|
|
107
|
+
uses: docker/login-action@v3
|
|
108
|
+
with:
|
|
109
|
+
registry: ghcr.io
|
|
110
|
+
username: ${{ github.actor }}
|
|
111
|
+
# PAT with read:packages scope stored as repository secret
|
|
112
|
+
password: ${{ secrets.PACKAGES_READ_PAT }}
|
|
113
|
+
|
|
114
|
+
- run: docker pull ghcr.io/other-org/shared-image:latest
|
|
115
|
+
prevention:
|
|
116
|
+
- "Always declare explicit `permissions:` blocks — adding ANY permission key removes all defaults, so list everything your job needs."
|
|
117
|
+
- "Include `packages: read` whenever a job pulls from ghcr.io, npm.pkg.github.com, or maven.pkg.github.com."
|
|
118
|
+
- "Note that GITHUB_TOKEN is scoped to the current repository; cross-repository package pulls may require a PAT with `read:packages` scope."
|
|
119
|
+
- "When adding `id-token: write` for OIDC, simultaneously audit all other permissions your jobs need to avoid accidentally removing packages: read."
|
|
120
|
+
docs:
|
|
121
|
+
- url: "https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions"
|
|
122
|
+
label: "GitHub docs — Publishing and installing packages with GitHub Actions"
|
|
123
|
+
- url: "https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token"
|
|
124
|
+
label: "GitHub docs — GITHUB_TOKEN permissions reference"
|
|
125
|
+
- url: "https://stackoverflow.com/questions/64124831/github-actions-401-unauthorized-when-installing-a-github-package-with-npm-or-yarn"
|
|
126
|
+
label: "Stack Overflow — 401 unauthorized installing GitHub Package with GITHUB_TOKEN"
|
|
127
|
+
- url: "https://github.com/orgs/community/discussions/162859"
|
|
128
|
+
label: "Community discussion #162859 — 401 bad request installing GitHub Packages"
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
id: permissions-auth-016
|
|
2
|
+
title: "OIDC Authentication Fails — id-token: write Missing from Restricted permissions Block"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- oidc
|
|
7
|
+
- id-token
|
|
8
|
+
- permissions
|
|
9
|
+
- jwt
|
|
10
|
+
- aws
|
|
11
|
+
- azure
|
|
12
|
+
- gcp
|
|
13
|
+
- workload-identity
|
|
14
|
+
- token-request
|
|
15
|
+
patterns:
|
|
16
|
+
- regex: "id-token.*write|id_token.*write"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "Error:.*OIDC.*provider|No OIDC provider found|OIDC.*not available"
|
|
19
|
+
flags: "i"
|
|
20
|
+
- regex: "RequestError.*GetIdToken|Unable to get OIDC token|getIDToken.*failed"
|
|
21
|
+
flags: "i"
|
|
22
|
+
- regex: "OIDCError|oidc-client.*failed|could not fetch oidc token"
|
|
23
|
+
flags: "i"
|
|
24
|
+
error_messages:
|
|
25
|
+
- "Error: No OIDC provider found. OIDC tokens are not available for this workflow."
|
|
26
|
+
- "RequestError: Unable to get OIDC token from actions ID token endpoint"
|
|
27
|
+
- "Error: getIDToken() failed: Could not get ID token. Input required and not supplied: audience"
|
|
28
|
+
- "OIDCError: Could not fetch OIDC token"
|
|
29
|
+
- "Error: No permission to get id-token"
|
|
30
|
+
- "failed to get credentials: failed to get OIDC token: failed to get ID token: Unable to get ID token"
|
|
31
|
+
root_cause: |
|
|
32
|
+
GitHub Actions OIDC (OpenID Connect) allows workflows to authenticate with cloud
|
|
33
|
+
providers (AWS, Azure, GCP) using short-lived JWT tokens instead of long-lived secrets.
|
|
34
|
+
To request an OIDC token, the workflow job MUST have `id-token: write` permission.
|
|
35
|
+
|
|
36
|
+
When a workflow or job defines a `permissions:` block, it switches from the **default
|
|
37
|
+
permissive** mode to **explicit restrictive** mode. In restrictive mode, any permission
|
|
38
|
+
NOT listed defaults to `none` — including `id-token`.
|
|
39
|
+
|
|
40
|
+
Common mistakes that silently remove `id-token: write`:
|
|
41
|
+
|
|
42
|
+
1. **Restricting permissions at workflow level without re-granting at job level:**
|
|
43
|
+
```yaml
|
|
44
|
+
permissions: # Workflow-level restriction
|
|
45
|
+
contents: read # id-token implicitly becomes 'none'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
2. **Adding `permissions: {}` to lock down a workflow:**
|
|
49
|
+
```yaml
|
|
50
|
+
permissions: {} # All permissions set to none, including id-token
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
3. **Following security advice to restrict GITHUB_TOKEN without accounting for OIDC:**
|
|
54
|
+
Security guides recommend restricting permissions, but if the job uses OIDC,
|
|
55
|
+
`id-token: write` must be explicitly added back.
|
|
56
|
+
|
|
57
|
+
4. **Inheriting a restricted workflow-level permission in a reusable workflow caller:**
|
|
58
|
+
Callers that set restrictive top-level `permissions:` do not automatically grant
|
|
59
|
+
`id-token: write` to reusable workflow jobs that need OIDC.
|
|
60
|
+
|
|
61
|
+
This is a particularly confusing error because:
|
|
62
|
+
- The error message "No OIDC provider found" sounds like a configuration issue
|
|
63
|
+
with the cloud provider, not a GitHub Actions permissions issue
|
|
64
|
+
- The workflow runs successfully in unrestricted mode (when no `permissions:` block
|
|
65
|
+
exists and defaults apply), so the error only appears after tightening security
|
|
66
|
+
Source: GitHub Docs — security hardening OIDC requirements
|
|
67
|
+
Source: GitHub Community — "No OIDC provider found" recurring discussion
|
|
68
|
+
fix: |
|
|
69
|
+
Add `id-token: write` to the `permissions:` block for any job that uses OIDC
|
|
70
|
+
authentication (actions/configure-aws-credentials, azure/login, etc.).
|
|
71
|
+
|
|
72
|
+
If `permissions:` is defined at the workflow level, you can either:
|
|
73
|
+
- Add `id-token: write` to the workflow-level block (grants it to all jobs), or
|
|
74
|
+
- Override permissions at the job level with `id-token: write` just for the job
|
|
75
|
+
that needs it (more secure — principle of least privilege)
|
|
76
|
+
|
|
77
|
+
Also ensure `contents: read` is present if the job uses `actions/checkout`.
|
|
78
|
+
fix_code:
|
|
79
|
+
- language: yaml
|
|
80
|
+
label: "Fix: add id-token: write to the job permissions block"
|
|
81
|
+
code: |
|
|
82
|
+
jobs:
|
|
83
|
+
deploy:
|
|
84
|
+
runs-on: ubuntu-latest
|
|
85
|
+
permissions:
|
|
86
|
+
id-token: write # ✅ Required for OIDC token request
|
|
87
|
+
contents: read # Required for actions/checkout
|
|
88
|
+
steps:
|
|
89
|
+
- uses: actions/checkout@v4
|
|
90
|
+
|
|
91
|
+
- name: Configure AWS credentials via OIDC
|
|
92
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
93
|
+
with:
|
|
94
|
+
role-to-assume: arn:aws:iam::123456789012:role/my-github-actions-role
|
|
95
|
+
aws-region: us-east-1
|
|
96
|
+
|
|
97
|
+
- name: Deploy
|
|
98
|
+
run: aws s3 sync dist/ s3://my-bucket
|
|
99
|
+
|
|
100
|
+
- language: yaml
|
|
101
|
+
label: "Workflow with restricted top-level permissions and OIDC job"
|
|
102
|
+
code: |
|
|
103
|
+
name: Deploy
|
|
104
|
+
|
|
105
|
+
on:
|
|
106
|
+
push:
|
|
107
|
+
branches: [main]
|
|
108
|
+
|
|
109
|
+
# Restrict default permissions for the whole workflow
|
|
110
|
+
permissions:
|
|
111
|
+
contents: read
|
|
112
|
+
|
|
113
|
+
jobs:
|
|
114
|
+
build:
|
|
115
|
+
runs-on: ubuntu-latest
|
|
116
|
+
# Inherits workflow-level permissions: contents=read, id-token=none
|
|
117
|
+
steps:
|
|
118
|
+
- uses: actions/checkout@v4
|
|
119
|
+
- run: npm ci && npm run build
|
|
120
|
+
- uses: actions/upload-artifact@v4
|
|
121
|
+
with:
|
|
122
|
+
name: build
|
|
123
|
+
path: dist/
|
|
124
|
+
|
|
125
|
+
deploy:
|
|
126
|
+
runs-on: ubuntu-latest
|
|
127
|
+
needs: build
|
|
128
|
+
# ✅ Override permissions at job level to add id-token: write
|
|
129
|
+
permissions:
|
|
130
|
+
id-token: write
|
|
131
|
+
contents: read
|
|
132
|
+
steps:
|
|
133
|
+
- uses: actions/download-artifact@v4
|
|
134
|
+
with:
|
|
135
|
+
name: build
|
|
136
|
+
|
|
137
|
+
- name: Configure Azure login via OIDC
|
|
138
|
+
uses: azure/login@v2
|
|
139
|
+
with:
|
|
140
|
+
client-id: ${{ vars.AZURE_CLIENT_ID }}
|
|
141
|
+
tenant-id: ${{ vars.AZURE_TENANT_ID }}
|
|
142
|
+
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
|
|
143
|
+
|
|
144
|
+
- language: yaml
|
|
145
|
+
label: "Reusable workflow: caller must pass id-token write permission"
|
|
146
|
+
code: |
|
|
147
|
+
# Caller workflow (in the calling repo)
|
|
148
|
+
jobs:
|
|
149
|
+
call-deploy:
|
|
150
|
+
uses: org/shared-workflows/.github/workflows/deploy.yml@main
|
|
151
|
+
permissions:
|
|
152
|
+
id-token: write # ✅ Caller must grant this for the reusable workflow
|
|
153
|
+
contents: read
|
|
154
|
+
with:
|
|
155
|
+
environment: production
|
|
156
|
+
|
|
157
|
+
prevention:
|
|
158
|
+
- "Whenever you add a `permissions:` block, explicitly include `id-token: write` for any job using OIDC."
|
|
159
|
+
- "Audit OIDC-dependent jobs after adding or tightening `permissions:` blocks."
|
|
160
|
+
- "Use job-level permissions instead of workflow-level when only some jobs need OIDC — principle of least privilege."
|
|
161
|
+
- "Document `id-token: write` with an inline comment explaining it is required for OIDC authentication."
|
|
162
|
+
- "For reusable workflows using OIDC: callers must explicitly pass `id-token: write` in their job permissions."
|
|
163
|
+
docs:
|
|
164
|
+
- url: "https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings"
|
|
165
|
+
label: "GitHub Docs: OIDC — Adding permissions settings (id-token: write)"
|
|
166
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token#modifying-the-permissions-for-the-github_token"
|
|
167
|
+
label: "GitHub Docs: Controlling permissions for GITHUB_TOKEN"
|
|
168
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token#defining-access-for-the-github_token-scopes"
|
|
169
|
+
label: "GitHub Docs: Defining access for GITHUB_TOKEN scopes"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
id: permissions-auth-017
|
|
2
|
+
title: "Empty permissions: {} Block Removes contents: read, Breaking Checkout on Private Repos"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- permissions
|
|
7
|
+
- github-token
|
|
8
|
+
- contents-read
|
|
9
|
+
- checkout
|
|
10
|
+
- private-repo
|
|
11
|
+
- security-hardening
|
|
12
|
+
patterns:
|
|
13
|
+
- regex: "Resource not accessible by integration"
|
|
14
|
+
flags: "i"
|
|
15
|
+
- regex: "403.*Resource not accessible"
|
|
16
|
+
flags: "i"
|
|
17
|
+
- regex: "HttpError.*Resource not accessible by integration"
|
|
18
|
+
flags: "i"
|
|
19
|
+
error_messages:
|
|
20
|
+
- "Error: Resource not accessible by integration"
|
|
21
|
+
- "HttpError: Resource not accessible by integration"
|
|
22
|
+
- "remote: Repository not found.\nError: The process '/usr/bin/git' failed with exit code 128"
|
|
23
|
+
- "Error: fatal: repository 'https://github.com/owner/repo/' not found"
|
|
24
|
+
root_cause: |
|
|
25
|
+
GitHub Actions workflows have a default token permission set controlled by the repository's
|
|
26
|
+
settings. When you add a `permissions:` block to a workflow or job, you are making permissions
|
|
27
|
+
EXPLICIT — any scope not listed is set to `none`, even if it was previously granted by default.
|
|
28
|
+
|
|
29
|
+
An empty `permissions: {}` block (or `permissions:` with no sub-keys) silently removes ALL
|
|
30
|
+
permissions including `contents: read`. This causes `actions/checkout` to fail on private
|
|
31
|
+
repositories because the GITHUB_TOKEN no longer has permission to clone the repository.
|
|
32
|
+
|
|
33
|
+
This is counterintuitive because developers often add `permissions: {}` as a security hardening
|
|
34
|
+
step without realizing it removes the read access needed for basic workflow operations like
|
|
35
|
+
checking out code.
|
|
36
|
+
|
|
37
|
+
The failure appears immediately at the checkout step. On public repositories, checkout may
|
|
38
|
+
succeed (public repos allow unauthenticated read) but any write operations, API calls, or
|
|
39
|
+
steps requiring token scopes will silently receive no permissions.
|
|
40
|
+
fix: |
|
|
41
|
+
Always specify the minimum required permissions explicitly. At minimum, include
|
|
42
|
+
`contents: read` to allow `actions/checkout` to clone the repository. Add other scopes
|
|
43
|
+
only as needed for the workflow's specific operations.
|
|
44
|
+
fix_code:
|
|
45
|
+
- language: yaml
|
|
46
|
+
label: "Minimal permissions: checkout only"
|
|
47
|
+
code: |
|
|
48
|
+
permissions:
|
|
49
|
+
contents: read # required for actions/checkout on private repos
|
|
50
|
+
|
|
51
|
+
jobs:
|
|
52
|
+
build:
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/checkout@v4
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: "Typical CI workflow permissions"
|
|
58
|
+
code: |
|
|
59
|
+
permissions:
|
|
60
|
+
contents: read # checkout
|
|
61
|
+
pull-requests: write # comment on PRs
|
|
62
|
+
checks: write # report check status
|
|
63
|
+
|
|
64
|
+
jobs:
|
|
65
|
+
test:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v4
|
|
69
|
+
# ...
|
|
70
|
+
- language: yaml
|
|
71
|
+
label: "Read-only lockdown (safest baseline)"
|
|
72
|
+
code: |
|
|
73
|
+
# At workflow level: lock down everything to read-only
|
|
74
|
+
permissions:
|
|
75
|
+
contents: read
|
|
76
|
+
packages: read
|
|
77
|
+
|
|
78
|
+
jobs:
|
|
79
|
+
deploy:
|
|
80
|
+
# Override at job level for the job that needs write access
|
|
81
|
+
permissions:
|
|
82
|
+
contents: read
|
|
83
|
+
deployments: write
|
|
84
|
+
id-token: write # OIDC
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/checkout@v4
|
|
88
|
+
prevention:
|
|
89
|
+
- "Never use `permissions: {}` — always list scopes explicitly with their required levels"
|
|
90
|
+
- "Include `contents: read` as the minimum permission in every workflow that uses actions/checkout"
|
|
91
|
+
- "Use job-level permissions overrides to grant elevated scopes only where needed, not at the workflow level"
|
|
92
|
+
- "GitHub's security hardening guide recommends workflow-level read-only + job-level write overrides as the safest pattern"
|
|
93
|
+
docs:
|
|
94
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token"
|
|
95
|
+
label: "GitHub Docs — Controlling permissions for GITHUB_TOKEN"
|
|
96
|
+
- url: "https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-permissions-for-github_token"
|
|
97
|
+
label: "GitHub Docs — Security hardening: using permissions for GITHUB_TOKEN"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
id: permissions-auth-012
|
|
2
|
+
title: "Reusable Workflow Permissions Not Inherited from Caller — Must Be Granted Explicitly"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- reusable-workflow
|
|
7
|
+
- permissions
|
|
8
|
+
- github-token
|
|
9
|
+
- caller
|
|
10
|
+
- contents
|
|
11
|
+
patterns:
|
|
12
|
+
- regex: "is requesting '([^']+)', but is only allowed '([^']+)'"
|
|
13
|
+
flags: "i"
|
|
14
|
+
- regex: "The workflow.*is requesting.*but is only allowed"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "Resource not accessible by integration"
|
|
17
|
+
flags: "i"
|
|
18
|
+
error_messages:
|
|
19
|
+
- "The workflow is not valid. .github/workflows/caller.yml: Error calling workflow 'org/repo/.github/workflows/reusable.yml@main'. The workflow 'org/repo/.github/workflows/reusable.yml@main' is requesting 'contents: read', but is only allowed 'contents: none'."
|
|
20
|
+
- "Resource not accessible by integration"
|
|
21
|
+
root_cause: |
|
|
22
|
+
When a workflow calls a reusable workflow using `jobs.<job>.uses`, the called workflow
|
|
23
|
+
runs with a GITHUB_TOKEN whose permissions are the INTERSECTION of:
|
|
24
|
+
1. The permissions declared in the caller workflow (or job)
|
|
25
|
+
2. The permissions the called reusable workflow needs
|
|
26
|
+
|
|
27
|
+
Reusable workflows do NOT automatically inherit the caller's permissions. If the caller
|
|
28
|
+
workflow declares a restrictive permissions block (or relies on repository-default
|
|
29
|
+
read-all), but the reusable workflow declares or needs broader permissions, the call
|
|
30
|
+
fails at validation time with:
|
|
31
|
+
|
|
32
|
+
"The workflow 'X' is requesting 'contents: read', but is only allowed 'contents: none'."
|
|
33
|
+
|
|
34
|
+
This also manifests at runtime as "Resource not accessible by integration" (HTTP 403)
|
|
35
|
+
when the reusable workflow's steps attempt API calls or git operations that require
|
|
36
|
+
permissions not granted by the caller.
|
|
37
|
+
|
|
38
|
+
Common scenarios:
|
|
39
|
+
- Caller explicitly sets `permissions: {}` (no permissions) for security hardening
|
|
40
|
+
- Caller sets only `id-token: write` for OIDC but reusable workflow needs `contents: read`
|
|
41
|
+
- Repository setting "Read and write permissions" is overridden to "Read repository
|
|
42
|
+
contents and packages permissions" at org level, reducing the default token scope
|
|
43
|
+
- Caller passes `secrets: inherit` but forgets to also pass `permissions:`
|
|
44
|
+
|
|
45
|
+
Source: GitHub Community Discussion #52665
|
|
46
|
+
fix: |
|
|
47
|
+
Explicitly grant the required permission scopes in the **caller** workflow, at either
|
|
48
|
+
the workflow level or the specific job level that uses the reusable workflow.
|
|
49
|
+
|
|
50
|
+
To discover which permissions a reusable workflow needs, check its `on.workflow_call`
|
|
51
|
+
declaration or read its steps to see which GitHub API/resource operations it performs.
|
|
52
|
+
|
|
53
|
+
Follow least privilege: grant only what the reusable workflow actually needs, not
|
|
54
|
+
blanket read-all.
|
|
55
|
+
fix_code:
|
|
56
|
+
- language: yaml
|
|
57
|
+
label: "Broken — caller grants no permissions, reusable workflow needs contents"
|
|
58
|
+
code: |
|
|
59
|
+
# ❌ BROKEN: caller sets permissions: {} — reusable workflow inherits none
|
|
60
|
+
name: Release
|
|
61
|
+
on: push
|
|
62
|
+
|
|
63
|
+
permissions: {} # Locks down all permissions — nothing passes through
|
|
64
|
+
|
|
65
|
+
jobs:
|
|
66
|
+
release:
|
|
67
|
+
uses: my-org/shared-workflows/.github/workflows/release.yml@main
|
|
68
|
+
# Error: release.yml requests 'contents: write' but caller allows 'contents: none'
|
|
69
|
+
- language: yaml
|
|
70
|
+
label: "Fixed — grant required scopes explicitly at the job level"
|
|
71
|
+
code: |
|
|
72
|
+
# ✅ FIXED: grant only what the reusable workflow needs
|
|
73
|
+
name: Release
|
|
74
|
+
on: push
|
|
75
|
+
|
|
76
|
+
permissions: {} # Lock down at workflow level for security
|
|
77
|
+
|
|
78
|
+
jobs:
|
|
79
|
+
release:
|
|
80
|
+
permissions:
|
|
81
|
+
contents: write # Required by the reusable workflow for creating releases
|
|
82
|
+
id-token: write # Required for OIDC if the reusable workflow uses it
|
|
83
|
+
uses: my-org/shared-workflows/.github/workflows/release.yml@main
|
|
84
|
+
- language: yaml
|
|
85
|
+
label: "Fixed — grant permissions at workflow level when multiple jobs use reusable workflows"
|
|
86
|
+
code: |
|
|
87
|
+
# ✅ FIXED: grant at workflow level when multiple jobs need the same scopes
|
|
88
|
+
name: CI
|
|
89
|
+
on: [push, pull_request]
|
|
90
|
+
|
|
91
|
+
permissions:
|
|
92
|
+
contents: read # Needed for checkout in reusable workflows
|
|
93
|
+
packages: write # Needed for reusable workflow that publishes packages
|
|
94
|
+
pull-requests: write # Needed for reusable workflow that adds PR comments
|
|
95
|
+
|
|
96
|
+
jobs:
|
|
97
|
+
build:
|
|
98
|
+
uses: my-org/shared-workflows/.github/workflows/build.yml@main
|
|
99
|
+
publish:
|
|
100
|
+
needs: build
|
|
101
|
+
uses: my-org/shared-workflows/.github/workflows/publish.yml@main
|
|
102
|
+
prevention:
|
|
103
|
+
- "Always read the reusable workflow's steps to know which permissions it needs before calling it."
|
|
104
|
+
- "Test reusable workflow calls from a caller with explicit `permissions: {}` first — failures reveal required scopes."
|
|
105
|
+
- "Document required permissions in the reusable workflow's `on.workflow_call` block as comments."
|
|
106
|
+
- "When adding `permissions:` to a caller, remember that declaring ANY permission sets all others to `none` — enumerate every scope you need."
|
|
107
|
+
- "Use job-level `permissions:` rather than workflow-level to apply least-privilege per job."
|
|
108
|
+
docs:
|
|
109
|
+
- url: "https://docs.github.com/en/actions/sharing-automations/reusing-workflows"
|
|
110
|
+
label: "Reusing workflows — permissions and access"
|
|
111
|
+
- url: "https://github.com/orgs/community/discussions/52665"
|
|
112
|
+
label: "GitHub Community #52665 — reusable workflow permissions"
|
|
113
|
+
- url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token"
|
|
114
|
+
label: "Controlling permissions for GITHUB_TOKEN"
|