@htekdev/actions-debugger 1.0.14 → 1.0.15
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/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/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/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-15-mono-nuget-removed.yml +151 -0
- package/errors/runner-environment/macos-15-xcode-simulator-sdk-policy.yml +141 -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/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/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,146 @@
|
|
|
1
|
+
id: permissions-auth-015
|
|
2
|
+
title: "Fine-Grained PAT Requires 'deployments: write' to Approve or Reject Deployments (Was 'read')"
|
|
3
|
+
category: permissions-auth
|
|
4
|
+
severity: error
|
|
5
|
+
tags:
|
|
6
|
+
- fine-grained-pat
|
|
7
|
+
- deployments
|
|
8
|
+
- permissions
|
|
9
|
+
- pat
|
|
10
|
+
- environments
|
|
11
|
+
- approval
|
|
12
|
+
- breaking-change
|
|
13
|
+
patterns:
|
|
14
|
+
- regex: "Resource not accessible by integration.*deployment|403.*deployment.*approve"
|
|
15
|
+
flags: "i"
|
|
16
|
+
- regex: "Must have admin rights to Repository.*approve.*deployment|permission.*deployments.*write"
|
|
17
|
+
flags: "i"
|
|
18
|
+
- regex: "HttpError: Resource not accessible by integration.*reviewers"
|
|
19
|
+
flags: "i"
|
|
20
|
+
- regex: "403.*Unable to approve deployment|Forbidden.*approve.*pending.*deployment"
|
|
21
|
+
flags: "i"
|
|
22
|
+
error_messages:
|
|
23
|
+
- "Resource not accessible by integration"
|
|
24
|
+
- "HttpError: Resource not accessible by integration"
|
|
25
|
+
- "Error: RequestError [HttpError]: Resource not accessible by integration"
|
|
26
|
+
- "Must have admin rights to Repository. - https://docs.github.com/rest/deployments/deployments"
|
|
27
|
+
- "403: Forbidden - You need deployments:write permission to approve or reject a deployment."
|
|
28
|
+
root_cause: |
|
|
29
|
+
On April 1, 2025, GitHub changed how deployment approval permissions work for
|
|
30
|
+
fine-grained personal access tokens (PATs). Prior to this change, a fine-grained PAT
|
|
31
|
+
with `deployments: read` permission could approve, approve-with-comment, or reject pending
|
|
32
|
+
deployments in protected environments. After April 1, 2025, the `deployments: write`
|
|
33
|
+
permission is **required** to review deployments.
|
|
34
|
+
|
|
35
|
+
This change was announced in the GitHub Changelog on March 20, 2025. Impacted customers
|
|
36
|
+
were notified by email in early March 2025. Fine-grained PATs that were created before
|
|
37
|
+
the change and only granted `deployments: read` stopped being able to approve or reject
|
|
38
|
+
deployments on April 1, 2025 without any change to the token itself — existing tokens
|
|
39
|
+
suddenly lost the ability they previously had.
|
|
40
|
+
|
|
41
|
+
**Affected scenarios:**
|
|
42
|
+
- GitHub Actions workflows that call `POST /repos/{owner}/{repo}/actions/runs/{run_id}/approvals`
|
|
43
|
+
or `POST /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments` with a
|
|
44
|
+
fine-grained PAT that only has `deployments: read`
|
|
45
|
+
- Custom deployment orchestration scripts using fine-grained PATs
|
|
46
|
+
- GitHub Apps or OAuth apps authenticated via fine-grained PAT that approved deployments
|
|
47
|
+
|
|
48
|
+
**Note:** Classic PATs and GitHub Apps with `deployments: write` scope are unaffected.
|
|
49
|
+
GitHub Apps using their own installation token (not a fine-grained PAT) are also
|
|
50
|
+
unaffected as long as they have the `deployments: write` permission in their app settings.
|
|
51
|
+
fix: |
|
|
52
|
+
Update the fine-grained PAT to grant `deployments: write` permission instead of
|
|
53
|
+
`deployments: read`:
|
|
54
|
+
|
|
55
|
+
1. Go to GitHub Settings → Developer settings → Personal access tokens → Fine-grained tokens
|
|
56
|
+
2. Click the PAT used in your workflow
|
|
57
|
+
3. Click "Regenerate token"
|
|
58
|
+
4. Under "Permissions → Repository permissions → Deployments", change from "Read" to "Read and write"
|
|
59
|
+
5. Update the secret in your repository/organization with the new token value
|
|
60
|
+
|
|
61
|
+
If you use a GitHub App instead of a PAT for deployments, ensure the App's repository
|
|
62
|
+
permission for "Deployments" is set to "Read and write" in the App settings.
|
|
63
|
+
fix_code:
|
|
64
|
+
- language: yaml
|
|
65
|
+
label: "Workflow using octokit to approve deployments — ensure PAT has deployments:write"
|
|
66
|
+
code: |
|
|
67
|
+
jobs:
|
|
68
|
+
approve-deployment:
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
steps:
|
|
71
|
+
- name: Approve pending deployment
|
|
72
|
+
uses: actions/github-script@v7
|
|
73
|
+
with:
|
|
74
|
+
# This PAT MUST have deployments:write permission (not just read)
|
|
75
|
+
github-token: ${{ secrets.DEPLOYMENT_APPROVAL_PAT }}
|
|
76
|
+
script: |
|
|
77
|
+
const pendingDeployments = await github.rest.actions.getPendingDeploymentsForRun({
|
|
78
|
+
owner: context.repo.owner,
|
|
79
|
+
repo: context.repo.repo,
|
|
80
|
+
run_id: context.runId
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const environmentIds = pendingDeployments.data
|
|
84
|
+
.filter(d => d.environment.name === 'production')
|
|
85
|
+
.map(d => d.environment.id);
|
|
86
|
+
|
|
87
|
+
await github.rest.actions.reviewPendingDeploymentsForRun({
|
|
88
|
+
owner: context.repo.owner,
|
|
89
|
+
repo: context.repo.repo,
|
|
90
|
+
run_id: context.runId,
|
|
91
|
+
environment_ids: environmentIds,
|
|
92
|
+
state: 'approved',
|
|
93
|
+
comment: 'Auto-approved by CI orchestration'
|
|
94
|
+
});
|
|
95
|
+
- language: yaml
|
|
96
|
+
label: "Verify token permissions before attempting deployment approval"
|
|
97
|
+
code: |
|
|
98
|
+
jobs:
|
|
99
|
+
check-permissions:
|
|
100
|
+
runs-on: ubuntu-latest
|
|
101
|
+
steps:
|
|
102
|
+
- name: Verify deployment token scope
|
|
103
|
+
uses: actions/github-script@v7
|
|
104
|
+
with:
|
|
105
|
+
github-token: ${{ secrets.DEPLOYMENT_APPROVAL_PAT }}
|
|
106
|
+
script: |
|
|
107
|
+
// Try a read operation first to confirm token is valid
|
|
108
|
+
const token = process.env.GITHUB_TOKEN;
|
|
109
|
+
|
|
110
|
+
// Check deployments permission by listing — uses read; write needed for approve
|
|
111
|
+
const deployments = await github.rest.repos.listDeployments({
|
|
112
|
+
owner: context.repo.owner,
|
|
113
|
+
repo: context.repo.repo
|
|
114
|
+
});
|
|
115
|
+
console.log(`Token valid — found ${deployments.data.length} deployments.`);
|
|
116
|
+
console.log('NOTE: To APPROVE deployments, this PAT needs deployments:write, not just read.');
|
|
117
|
+
- language: yaml
|
|
118
|
+
label: "Use GITHUB_TOKEN with environment protection rules instead of fine-grained PAT"
|
|
119
|
+
code: |
|
|
120
|
+
# Better pattern: let GitHub's environment protection handle approvals
|
|
121
|
+
# rather than auto-approving via API with a PAT
|
|
122
|
+
|
|
123
|
+
jobs:
|
|
124
|
+
deploy:
|
|
125
|
+
runs-on: ubuntu-latest
|
|
126
|
+
environment:
|
|
127
|
+
name: production
|
|
128
|
+
# Configure required reviewers in Environment settings
|
|
129
|
+
# No custom PAT needed — GITHUB_TOKEN works for normal deployment flows
|
|
130
|
+
steps:
|
|
131
|
+
- name: Deploy
|
|
132
|
+
run: echo "Deploying to production..."
|
|
133
|
+
# The 'environment' key above enforces approval through GitHub UI
|
|
134
|
+
# without needing deployments:write in a PAT
|
|
135
|
+
prevention:
|
|
136
|
+
- "When creating fine-grained PATs for CI/CD deployment workflows, always grant `deployments: write` upfront — even if you only need read today, write is required for any approval-related operation."
|
|
137
|
+
- "Audit all fine-grained PATs used in GitHub Actions for their `deployments` permission level after the April 2025 change."
|
|
138
|
+
- "Prefer using GitHub's built-in environment protection rules (required reviewers, wait timers) over custom approval scripts with PATs — fewer moving parts."
|
|
139
|
+
- "Subscribe to the GitHub Changelog to receive advance notice of breaking permission changes."
|
|
140
|
+
docs:
|
|
141
|
+
- url: "https://github.blog/changelog/2025-03-20-notification-of-upcoming-breaking-changes-in-github-actions/"
|
|
142
|
+
label: "GitHub Changelog: Modification to deployment permissions (March 20, 2025)"
|
|
143
|
+
- url: "https://docs.github.com/en/rest/actions/workflow-runs#review-pending-deployments-for-a-workflow-run"
|
|
144
|
+
label: "GitHub REST API: Review pending deployments for a workflow run"
|
|
145
|
+
- url: "https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token"
|
|
146
|
+
label: "GitHub Docs: Creating a fine-grained personal access token"
|
|
@@ -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"
|