@htekdev/actions-debugger 1.0.0 → 1.0.1

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.
Files changed (53) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +108 -108
  3. package/errors/_schema.json +89 -89
  4. package/errors/caching-artifacts/artifact-storage-quota-exceeded.yml +118 -0
  5. package/errors/caching-artifacts/cache-miss.yml +56 -56
  6. package/errors/caching-artifacts/cache-save-cancelled-job.yml +82 -0
  7. package/errors/caching-artifacts/cache-v3-to-v4-breaking-changes.yml +95 -0
  8. package/errors/caching-artifacts/cross-repo-artifacts-not-supported.yml +102 -0
  9. package/errors/caching-artifacts/upload-artifact-no-files-found.yml +92 -0
  10. package/errors/caching-artifacts/upload-artifact-v4-breaking.yml +67 -67
  11. package/errors/concurrency-timing/cancel-in-progress-deploy-drops.yml +97 -0
  12. package/errors/concurrency-timing/jobs-cancelled-unexpectedly.yml +60 -60
  13. package/errors/concurrency-timing/skipped-needs-cascade.yml +103 -0
  14. package/errors/concurrency-timing/workflow-run-conclusion-unchecked.yml +100 -0
  15. package/errors/known-unsolved/composite-input-env-vars-missing.yml +91 -0
  16. package/errors/known-unsolved/composite-nested-outputs-null.yml +101 -0
  17. package/errors/known-unsolved/no-dynamic-secret-access.yml +111 -0
  18. package/errors/known-unsolved/no-step-level-rerun.yml +94 -0
  19. package/errors/known-unsolved/no-step-retry.yml +53 -53
  20. package/errors/permissions-auth/checkout-submodule-private-auth.yml +91 -0
  21. package/errors/permissions-auth/fork-pr-secrets-unavailable.yml +97 -0
  22. package/errors/permissions-auth/github-token-403.yml +64 -64
  23. package/errors/permissions-auth/github-token-protected-branch-push.yml +109 -0
  24. package/errors/permissions-auth/oidc-aws-failure.yml +85 -85
  25. package/errors/permissions-auth/oidc-azure-subject-mismatch.yml +91 -0
  26. package/errors/runner-environment/disk-space.yml +57 -57
  27. package/errors/runner-environment/docker-buildx-not-setup.yml +106 -0
  28. package/errors/runner-environment/macos-homebrew-path.yml +90 -0
  29. package/errors/runner-environment/node-runtime-deprecation.yml +56 -56
  30. package/errors/runner-environment/npm-ci-lockfile-mismatch.yml +112 -0
  31. package/errors/runner-environment/self-hosted-stale-toolcache.yml +73 -0
  32. package/errors/runner-environment/setup-node-version-file-missing.yml +105 -0
  33. package/errors/runner-environment/windows-execution-policy.yml +83 -0
  34. package/errors/silent-failures/add-mask-no-retroactive-masking.yml +75 -0
  35. package/errors/silent-failures/composite-boolean-inputs-as-strings.yml +110 -0
  36. package/errors/silent-failures/conditional-output-null-downstream.yml +82 -0
  37. package/errors/silent-failures/continue-on-error-masks-failure.yml +86 -0
  38. package/errors/silent-failures/github-token-no-trigger.yml +57 -57
  39. package/errors/silent-failures/reusable-workflow-env-secrets-empty.yml +90 -0
  40. package/errors/silent-failures/scheduled-workflow-disabled.yml +59 -59
  41. package/errors/triggers/cron-schedule-late.yml +59 -59
  42. package/errors/triggers/pull-request-target-rce-risk.yml +117 -0
  43. package/errors/triggers/workflow-not-triggering.yml +60 -60
  44. package/errors/triggers/workflow-run-default-branch-requirement.yml +78 -0
  45. package/errors/yaml-syntax/anchors-not-supported.yml +95 -0
  46. package/errors/yaml-syntax/dynamic-matrix-fromjson-failure.yml +99 -0
  47. package/errors/yaml-syntax/if-always-true.yml +52 -52
  48. package/errors/yaml-syntax/missing-expression-wrapper.yml +67 -0
  49. package/errors/yaml-syntax/needs-indirect-outputs.yml +91 -0
  50. package/errors/yaml-syntax/secrets-in-if.yml +55 -55
  51. package/errors/yaml-syntax/unexpected-yaml-key.yml +69 -69
  52. package/errors/yaml-syntax/working-directory-ignored-on-uses.yml +66 -0
  53. package/package.json +70 -67
@@ -0,0 +1,111 @@
1
+ id: known-unsolved-005
2
+ title: "No Dynamic Secret Access — Cannot Resolve Secret Name from a Variable"
3
+ category: known-unsolved
4
+ severity: limitation
5
+ tags:
6
+ - secrets
7
+ - dynamic
8
+ - variable
9
+ - runtime
10
+ - limitation
11
+ - workaround
12
+ patterns:
13
+ - regex: "secrets\\[\\$\\{\\{.*\\}\\}\\]"
14
+ flags: "i"
15
+ - regex: "Unrecognized named-value: 'secrets'"
16
+ flags: "i"
17
+ error_messages:
18
+ - "Context access might be invalid: secrets[variableName]"
19
+ - "The secrets context does not support dynamic access."
20
+ root_cause: |
21
+ GitHub Actions secrets can only be accessed by their **literal name** at workflow
22
+ authoring time — there is no way to resolve a secret by a name computed at runtime.
23
+
24
+ Expressions like `${{ secrets[env.SECRET_NAME] }}` or
25
+ `${{ secrets[github.event.inputs.env] }}` are not valid. The expression parser requires
26
+ the secret name to be a compile-time string literal, not a variable reference.
27
+
28
+ This is a deliberate security measure: dynamic secret access would allow workflows
29
+ to iterate over all secrets and exfiltrate them, or allow user-controlled input to
30
+ select which secret to use (a form of secret injection attack).
31
+
32
+ Teams that need environment-specific secrets often try to work around this with dynamic
33
+ naming patterns (`PROD_API_KEY`, `STAGING_API_KEY`) but cannot select between them at
34
+ runtime without pre-wiring all possibilities at workflow authoring time.
35
+ fix: |
36
+ There is no supported dynamic secret access. Use one of these workarounds:
37
+ 1. **Inline all possibilities** with a conditional step per environment
38
+ 2. **Use GitHub Environments** — each environment has its own secret namespace,
39
+ and selecting `environment: production` vs `environment: staging` automatically
40
+ scopes secrets without dynamic resolution
41
+ 3. **Use a secrets manager** (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault)
42
+ where dynamic key lookups ARE supported at runtime
43
+ fix_code:
44
+ - language: yaml
45
+ label: "WRONG — dynamic secret access (not supported)"
46
+ code: |
47
+ jobs:
48
+ deploy:
49
+ runs-on: ubuntu-latest
50
+ steps:
51
+ - name: Get environment-specific secret
52
+ env:
53
+ # This does NOT work — cannot use variable as secret key
54
+ API_KEY: ${{ secrets[format('{0}_API_KEY', github.event.inputs.environment)] }}
55
+ run: ./deploy.sh
56
+ - language: yaml
57
+ label: "CORRECT option 1 — explicit conditional per environment"
58
+ code: |
59
+ jobs:
60
+ deploy:
61
+ runs-on: ubuntu-latest
62
+ steps:
63
+ - name: Set environment-specific secret
64
+ run: |
65
+ if [ "${{ github.event.inputs.environment }}" = "production" ]; then
66
+ echo "API_KEY=${{ secrets.PROD_API_KEY }}" >> $GITHUB_ENV
67
+ elif [ "${{ github.event.inputs.environment }}" = "staging" ]; then
68
+ echo "API_KEY=${{ secrets.STAGING_API_KEY }}" >> $GITHUB_ENV
69
+ fi
70
+ - run: ./deploy.sh
71
+ - language: yaml
72
+ label: "CORRECT option 2 — use GitHub Environments (recommended)"
73
+ code: |
74
+ on:
75
+ workflow_dispatch:
76
+ inputs:
77
+ environment:
78
+ type: choice
79
+ options: [production, staging]
80
+
81
+ jobs:
82
+ deploy:
83
+ runs-on: ubuntu-latest
84
+ environment: ${{ github.event.inputs.environment }} # auto-scopes secrets
85
+ steps:
86
+ # secrets.API_KEY automatically resolves to the correct environment's secret
87
+ - run: ./deploy.sh
88
+ env:
89
+ API_KEY: ${{ secrets.API_KEY }}
90
+ - language: yaml
91
+ label: "CORRECT option 3 — delegate to a secrets manager"
92
+ code: |
93
+ jobs:
94
+ deploy:
95
+ runs-on: ubuntu-latest
96
+ steps:
97
+ - uses: aws-actions/aws-secretsmanager-get-secrets@v2
98
+ with:
99
+ secret-ids: |
100
+ ${{ github.event.inputs.environment }}/api-key # dynamic lookup is fine here
101
+ prevention:
102
+ - "Use GitHub Environments — they provide per-environment secret namespacing without dynamic access."
103
+ - "For more than 2-3 environments, invest in a proper secrets manager (Vault, AWS SM, Azure KV)."
104
+ - "Never attempt `secrets[variable]` — it silently produces an empty string and the workflow continues with empty credentials."
105
+ docs:
106
+ - url: "https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment"
107
+ label: "Managing environments for deployment"
108
+ - url: "https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions"
109
+ label: "Using secrets in GitHub Actions"
110
+ - url: "https://github.com/orgs/community/discussions/29248"
111
+ label: "Community discussion: Dynamic secret access (GitHub Community)"
@@ -0,0 +1,94 @@
1
+ id: known-unsolved-006
2
+ title: "No Native Way to Retry a Single Failed Step Without Re-Running the Entire Job"
3
+ category: known-unsolved
4
+ severity: limitation
5
+ tags:
6
+ - retry
7
+ - step
8
+ - flaky
9
+ - re-run
10
+ - limitation
11
+ - idempotency
12
+ patterns:
13
+ - regex: "Re-run failed jobs"
14
+ flags: "i"
15
+ - regex: "retry.*step.*not supported"
16
+ flags: "i"
17
+ error_messages:
18
+ - "GitHub Actions does not support step-level re-run — only job-level or failed-jobs-level re-run is available."
19
+ root_cause: |
20
+ GitHub Actions' "Re-run" feature operates at the **job level** or "re-run failed jobs"
21
+ level — there is no UI or API to re-run a single failed step within an already-completed
22
+ job. When you click "Re-run failed jobs," the entire job restarts from the beginning,
23
+ including all steps that already passed.
24
+
25
+ This has three practical consequences:
26
+ 1. **Flaky step recovery is expensive** — a 20-minute job must re-run entirely to retry
27
+ a 30-second network call that failed transiently.
28
+ 2. **Non-idempotent steps can double-execute** — steps that modify external state
29
+ (publish to npm, create a GitHub release, push Docker image) will attempt to run again
30
+ on re-run, even if they succeeded the first time.
31
+ 3. **Re-run artifacts** — artifacts and cache from the first run may or may not be
32
+ available depending on what step failed and whether post-steps ran.
33
+
34
+ This limitation has been requested since GitHub Actions launched. As of 2024, GitHub
35
+ has not implemented step-level granularity for re-runs.
36
+ fix: |
37
+ Design workflows with step-level retry in mind from the start. Use retry actions
38
+ inline, make steps idempotent, and split long workflows into jobs so that a partial
39
+ re-run covers fewer steps.
40
+ fix_code:
41
+ - language: yaml
42
+ label: "Workaround 1 — use nick-fields/retry for flaky steps"
43
+ code: |
44
+ steps:
45
+ - name: Flaky network call (with retry)
46
+ uses: nick-fields/retry@v3
47
+ with:
48
+ timeout_minutes: 5
49
+ max_attempts: 3
50
+ retry_wait_seconds: 10
51
+ command: curl -f https://api.example.com/deploy
52
+ - language: yaml
53
+ label: "Workaround 2 — inline retry loop"
54
+ code: |
55
+ steps:
56
+ - name: Deploy with retry
57
+ run: |
58
+ for i in 1 2 3; do
59
+ echo "Attempt $i"
60
+ ./deploy.sh && break || {
61
+ echo "Attempt $i failed"
62
+ if [ $i -eq 3 ]; then
63
+ echo "::error::All 3 deploy attempts failed"
64
+ exit 1
65
+ fi
66
+ sleep 30
67
+ }
68
+ done
69
+ - language: yaml
70
+ label: "Workaround 3 — idempotent steps that safe to re-run"
71
+ code: |
72
+ steps:
73
+ - name: Create GitHub Release (idempotent — skip if already exists)
74
+ run: |
75
+ TAG="${{ github.ref_name }}"
76
+ if gh release view "$TAG" &>/dev/null; then
77
+ echo "Release $TAG already exists — skipping creation"
78
+ else
79
+ gh release create "$TAG" dist/*.tar.gz
80
+ fi
81
+ env:
82
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
83
+ prevention:
84
+ - "Build retry logic directly into steps using `nick-fields/retry` or shell loops — don't rely on job re-run for flaky operations."
85
+ - "Make every step idempotent (safe to run twice) so job re-runs don't cause double-execution side effects."
86
+ - "Split workflows into small jobs — `Re-run failed jobs` is more surgical with smaller jobs."
87
+ - "Cache and upload artifacts eagerly so a re-run can pick up from near the failure point."
88
+ docs:
89
+ - url: "https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/re-running-workflows-and-jobs"
90
+ label: "Re-running workflows and jobs"
91
+ - url: "https://github.com/nick-fields/retry"
92
+ label: "nick-fields/retry — step-level retry action"
93
+ - url: "https://github.com/orgs/community/discussions/17743"
94
+ label: "Community discussion: Step-level re-run (GitHub Community)"
@@ -1,53 +1,53 @@
1
- id: known-unsolved-002
2
- title: "No Step-Level Retry"
3
- category: known-unsolved
4
- severity: limitation
5
- tags:
6
- - retry
7
- - steps
8
- - limitation
9
- - flakiness
10
- - resilience
11
- patterns:
12
- - regex: "Process completed with exit code [0-9]+"
13
- flags: "i"
14
- - regex: 'curl: \(28\) Operation timed out'
15
- flags: "i"
16
- - regex: "npm ERR! network"
17
- flags: "i"
18
- error_messages:
19
- - "Process completed with exit code 1"
20
- - "curl: (28) Operation timed out"
21
- root_cause: |
22
- GitHub Actions can rerun jobs and whole workflows, but it does not provide native retry
23
- semantics for a single arbitrary step. That means flaky network calls, package registry
24
- hiccups, and transient shell commands fail the step immediately unless you build a retry
25
- wrapper yourself.
26
-
27
- This is a platform limitation rather than a YAML mistake.
28
- fix: |
29
- Wrap the flaky command in a retry helper or use a marketplace action such as
30
- `nick-fields/retry` when retry semantics are needed for one step only.
31
- fix_code:
32
- - language: yaml
33
- label: "Retry a flaky step with nick-fields/retry"
34
- code: |
35
- - name: Retry npm install
36
- uses: nick-fields/retry@v3
37
- with:
38
- timeout_minutes: 15
39
- max_attempts: 3
40
- retry_wait_seconds: 20
41
- command: npm ci
42
- prevention:
43
- - "Assume external network calls are flaky and wrap them in retries if they are business-critical."
44
- - "Prefer idempotent commands for steps that may need retry wrappers."
45
- - "Document step-level retry strategy because GitHub Actions does not provide it natively."
46
- docs:
47
- - url: "https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/re-running-workflows-and-jobs"
48
- label: "Re-running workflows and jobs"
49
- - url: "https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions"
50
- label: "Workflow syntax for GitHub Actions"
51
- source:
52
- article: "https://htek.dev/articles/github-actions-debugging-guide"
53
- section: "Platform limitations: no step-level retry"
1
+ id: known-unsolved-002
2
+ title: "No Step-Level Retry"
3
+ category: known-unsolved
4
+ severity: limitation
5
+ tags:
6
+ - retry
7
+ - steps
8
+ - limitation
9
+ - flakiness
10
+ - resilience
11
+ patterns:
12
+ - regex: "Process completed with exit code [0-9]+"
13
+ flags: "i"
14
+ - regex: 'curl: \(28\) Operation timed out'
15
+ flags: "i"
16
+ - regex: "npm ERR! network"
17
+ flags: "i"
18
+ error_messages:
19
+ - "Process completed with exit code 1"
20
+ - "curl: (28) Operation timed out"
21
+ root_cause: |
22
+ GitHub Actions can rerun jobs and whole workflows, but it does not provide native retry
23
+ semantics for a single arbitrary step. That means flaky network calls, package registry
24
+ hiccups, and transient shell commands fail the step immediately unless you build a retry
25
+ wrapper yourself.
26
+
27
+ This is a platform limitation rather than a YAML mistake.
28
+ fix: |
29
+ Wrap the flaky command in a retry helper or use a marketplace action such as
30
+ `nick-fields/retry` when retry semantics are needed for one step only.
31
+ fix_code:
32
+ - language: yaml
33
+ label: "Retry a flaky step with nick-fields/retry"
34
+ code: |
35
+ - name: Retry npm install
36
+ uses: nick-fields/retry@v3
37
+ with:
38
+ timeout_minutes: 15
39
+ max_attempts: 3
40
+ retry_wait_seconds: 20
41
+ command: npm ci
42
+ prevention:
43
+ - "Assume external network calls are flaky and wrap them in retries if they are business-critical."
44
+ - "Prefer idempotent commands for steps that may need retry wrappers."
45
+ - "Document step-level retry strategy because GitHub Actions does not provide it natively."
46
+ docs:
47
+ - url: "https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/re-running-workflows-and-jobs"
48
+ label: "Re-running workflows and jobs"
49
+ - url: "https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions"
50
+ label: "Workflow syntax for GitHub Actions"
51
+ source:
52
+ article: "https://htek.dev/articles/github-actions-debugging-guide"
53
+ section: "Platform limitations: no step-level retry"
@@ -0,0 +1,91 @@
1
+ id: permissions-auth-003
2
+ title: "Checkout Private Submodules Fails — Permission Denied or Repository Not Found"
3
+ category: permissions-auth
4
+ severity: error
5
+ tags:
6
+ - checkout
7
+ - submodules
8
+ - private-repos
9
+ - PAT
10
+ - SSH
11
+ - GITHUB_TOKEN
12
+ patterns:
13
+ - regex: "fatal: could not read Username for 'https://github\\.com/'.*terminal prompts disabled"
14
+ flags: "i"
15
+ - regex: "git@github\\.com: Permission denied \\(publickey\\)"
16
+ flags: "i"
17
+ - regex: "remote: Repository not found\\."
18
+ flags: "i"
19
+ - regex: "fatal: clone of '.*' into submodule path .* failed"
20
+ flags: "i"
21
+ - regex: "Host key verification failed"
22
+ flags: "i"
23
+ error_messages:
24
+ - "fatal: could not read Username for 'https://github.com/': terminal prompts disabled"
25
+ - "git@github.com: Permission denied (publickey)."
26
+ - "remote: Repository not found."
27
+ - "Error: fatal: clone of 'git@github.com:Org/SubRepo.git' into submodule path '<path>' failed"
28
+ - "Host key verification failed."
29
+ root_cause: |
30
+ The default `GITHUB_TOKEN` only grants access to the single repository that triggered the workflow.
31
+ When a repository contains submodules pointing to other private repositories, the checkout action
32
+ cannot authenticate to those private repos using GITHUB_TOKEN alone.
33
+
34
+ Two distinct failure modes exist depending on how submodule URLs are declared in `.gitmodules`:
35
+
36
+ 1. HTTPS submodule URL (`https://github.com/Org/Repo`) — fails with "terminal prompts disabled"
37
+ because git cannot interactively prompt for credentials in a non-interactive CI environment,
38
+ and GITHUB_TOKEN is not forwarded to the submodule clone.
39
+
40
+ 2. SSH submodule URL (`git@github.com:Org/Repo.git`) — fails with "Permission denied (publickey)"
41
+ because no SSH private key is configured on the runner.
42
+
43
+ This is especially confusing when the parent repository checkout succeeds and only the submodule
44
+ clone step fails, and because `remote: Repository not found` is the error GitHub returns when
45
+ authentication fails for a private repo (the repo is hidden from unauthenticated access).
46
+ fix: |
47
+ The fix depends on the URL scheme used in `.gitmodules`:
48
+
49
+ For HTTPS submodule URLs: provide a Personal Access Token (PAT) with `repo` scope via the `token`
50
+ input of `actions/checkout`. The action will automatically configure git credential helpers using
51
+ the provided token for all submodule clones.
52
+
53
+ For SSH submodule URLs (`git@github.com:...`): provide an SSH deploy key via the `ssh-key` input.
54
+ A PAT will NOT work for SSH-scheme URLs — the authentication mechanism is fundamentally different.
55
+
56
+ For organization-wide private submodule access, a GitHub App installation token is preferred over
57
+ a personal PAT because it does not depend on an individual user's account.
58
+ fix_code:
59
+ - language: yaml
60
+ label: "Checkout private HTTPS submodules using a PAT"
61
+ code: |
62
+ - uses: actions/checkout@v4
63
+ with:
64
+ submodules: recursive
65
+ token: ${{ secrets.GH_PAT_SUBMODULES }}
66
+ # PAT must have 'repo' scope and access to all private submodule repos
67
+ # Do NOT use GITHUB_TOKEN — it only covers the current repository
68
+ - language: yaml
69
+ label: "Checkout private SSH submodules using a deploy key"
70
+ code: |
71
+ - uses: actions/checkout@v4
72
+ with:
73
+ submodules: recursive
74
+ ssh-key: ${{ secrets.SUBMODULES_DEPLOY_KEY }}
75
+ # SSH private key must be added as a deploy key to all private submodule repos
76
+ # Required when .gitmodules uses SSH-scheme URLs (git@github.com:...)
77
+ prevention:
78
+ - "Check `.gitmodules` URL scheme first — HTTPS submodules need a PAT, SSH submodules need an SSH deploy key."
79
+ - "Never use GITHUB_TOKEN for private submodule checkout — it only covers the triggering repository."
80
+ - "Prefer GitHub App installation tokens over PATs for organization-wide private submodule access."
81
+ - "Add the PAT or deploy key to repository (or organization) secrets before running the workflow."
82
+ - "Test with a manual workflow_dispatch run before adding to CI pipelines."
83
+ docs:
84
+ - url: "https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token"
85
+ label: "GITHUB_TOKEN permissions — scope limited to the current repository"
86
+ - url: "https://github.com/actions/checkout#usage"
87
+ label: "actions/checkout README — token and ssh-key inputs"
88
+ - url: "https://github.com/actions/checkout/issues/738"
89
+ label: "actions/checkout#738 — terminal prompts disabled with private submodules (many affected users)"
90
+ - url: "https://github.com/actions/checkout/issues/2080"
91
+ label: "actions/checkout#2080 — Repository not found error with private submodules without PAT"
@@ -0,0 +1,97 @@
1
+ id: permissions-auth-005
2
+ title: "Secrets Unavailable in Workflows Triggered by Fork Pull Requests"
3
+ category: permissions-auth
4
+ severity: silent-failure
5
+ tags:
6
+ - secrets
7
+ - fork
8
+ - pull_request
9
+ - permissions
10
+ - security
11
+ - third-party
12
+ patterns:
13
+ - regex: "Context access might be invalid: secrets"
14
+ flags: "i"
15
+ - regex: "secrets\\.[A-Z_]+ is not available"
16
+ flags: "i"
17
+ error_messages:
18
+ - "Error: Input required and not supplied: token"
19
+ - "Error: Resource not accessible by integration"
20
+ - "HttpError: Resource not accessible by integration"
21
+ root_cause: |
22
+ By design, GitHub Actions does not pass `secrets.*` to workflows triggered by
23
+ `pull_request` events from **fork repositories**. This is a security measure: a
24
+ malicious actor could open a PR from a fork and, if secrets were available, extract
25
+ them by printing them or exfiltrating them via a network call in the workflow.
26
+
27
+ When a fork PR triggers a `pull_request` workflow, `secrets.GITHUB_TOKEN` is also
28
+ **read-only** (cannot push back to the repo) and all other named secrets resolve to
29
+ empty strings. There is no error — the secret silently returns `""`.
30
+
31
+ Common symptoms: actions/checkout fails to fetch LFS or private submodules,
32
+ third-party action API calls fail with 401/403, Docker logins fail silently.
33
+ fix: |
34
+ Use `pull_request_target` for workflows that need secrets from fork PRs, but **only
35
+ after thoroughly auditing the workflow for code injection risks** — `pull_request_target`
36
+ runs in the context of the base branch (not the fork's code) and has full secret access.
37
+ Alternatively, use the `Repository maintainers must approve` workflow setting to gate
38
+ secret access on trusted reviewer approval.
39
+ fix_code:
40
+ - language: yaml
41
+ label: "WRONG — pull_request from fork has no secrets"
42
+ code: |
43
+ on: pull_request # from fork: secrets are all empty strings
44
+
45
+ jobs:
46
+ test:
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+ - name: Authenticate to registry
51
+ run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u user --password-stdin
52
+ # FAILS silently — DOCKER_PASSWORD is "" for fork PRs
53
+ - language: yaml
54
+ label: "CORRECT — use pull_request_target with safety guard"
55
+ code: |
56
+ on:
57
+ pull_request_target:
58
+ types: [opened, synchronize]
59
+
60
+ jobs:
61
+ test:
62
+ runs-on: ubuntu-latest
63
+ # Only run for trusted contributors to prevent code injection
64
+ if: |
65
+ github.event.pull_request.head.repo.full_name == github.repository ||
66
+ contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.pull_request.author_association)
67
+ steps:
68
+ # CRITICAL: checkout the BASE, not the PR head, to avoid untrusted code execution
69
+ - uses: actions/checkout@v4
70
+ with:
71
+ ref: ${{ github.event.pull_request.base.sha }}
72
+ - run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u user --password-stdin
73
+ - language: yaml
74
+ label: "ALTERNATIVE — fork-safe workflow with manual approval step"
75
+ code: |
76
+ # In org settings: require approval for first-time contributors
77
+ # Settings → Actions → Fork pull request workflows → Require approval for all outside collaborators
78
+ on: pull_request
79
+
80
+ jobs:
81
+ test:
82
+ runs-on: ubuntu-latest
83
+ steps:
84
+ - uses: actions/checkout@v4
85
+ # For public tests that don't need secrets:
86
+ - run: npm test
87
+ prevention:
88
+ - "Never assume secrets are available in `pull_request` workflows from forks — always check with `github.event.pull_request.head.repo.fork`."
89
+ - "Require manual approval for fork PRs in your org's Actions settings."
90
+ - "When using `pull_request_target`, ALWAYS check out the base SHA, not the PR head — running fork code with full secrets is a critical security vulnerability."
91
+ docs:
92
+ - url: "https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections"
93
+ label: "Security hardening — script injection risks"
94
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_target"
95
+ label: "pull_request_target event"
96
+ - url: "https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/"
97
+ label: "Preventing pwn requests (GitHub Security Lab)"
@@ -1,64 +1,64 @@
1
- id: permissions-auth-001
2
- title: "GITHUB_TOKEN Permission Denied (403)"
3
- category: permissions-auth
4
- severity: error
5
- tags:
6
- - github-token
7
- - permissions
8
- - 403
9
- - auth
10
- - push
11
- patterns:
12
- - regex: "remote: Permission to .+\\.git denied to github-actions\\[bot\\]"
13
- flags: "i"
14
- - regex: "fatal: unable to access 'https://github\\.com/.+': The requested URL returned error: 403"
15
- flags: "i"
16
- - regex: "Resource not accessible by integration"
17
- flags: "i"
18
- error_messages:
19
- - "remote: Permission to org/repo.git denied to github-actions[bot]."
20
- - "fatal: unable to access 'https://github.com/org/repo.git/': The requested URL returned error: 403"
21
- - "Resource not accessible by integration"
22
- root_cause: |
23
- Since February 2023, newly created repositories default `GITHUB_TOKEN` to read-only
24
- permissions in many cases. A workflow that tries to push commits, create releases, or
25
- modify pull requests without an explicit `permissions:` block can suddenly fail with 403
26
- or integration access errors.
27
-
28
- The token exists, but it does not have the scopes the job assumed it had.
29
- fix: |
30
- Add the minimum required `permissions:` block at the workflow or job level. For pushes,
31
- tags, or release creation, that usually means `contents: write`. For pull request comments
32
- or checks, grant the specific write permission those APIs require.
33
- fix_code:
34
- - language: yaml
35
- label: "Grant explicit write permissions to GITHUB_TOKEN"
36
- code: |
37
- name: Release
38
-
39
- on:
40
- push:
41
- branches:
42
- - main
43
-
44
- permissions:
45
- contents: write
46
-
47
- jobs:
48
- release:
49
- runs-on: ubuntu-latest
50
- steps:
51
- - uses: actions/checkout@v4
52
- - run: git push origin HEAD:main
53
- prevention:
54
- - "Assume `GITHUB_TOKEN` is read-only unless you explicitly grant the needed permission."
55
- - "Set least-privilege `permissions:` blocks on every workflow instead of relying on defaults."
56
- - "Match API usage to the smallest write scope required."
57
- docs:
58
- - url: "https://docs.github.com/en/actions/security-guides/automatic-token-authentication"
59
- label: "Automatic token authentication"
60
- - url: "https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions"
61
- label: "permissions"
62
- source:
63
- article: "https://htek.dev/articles/github-actions-debugging-guide"
64
- section: "GITHUB_TOKEN 403 and read-only defaults"
1
+ id: permissions-auth-001
2
+ title: "GITHUB_TOKEN Permission Denied (403)"
3
+ category: permissions-auth
4
+ severity: error
5
+ tags:
6
+ - github-token
7
+ - permissions
8
+ - 403
9
+ - auth
10
+ - push
11
+ patterns:
12
+ - regex: "remote: Permission to .+\\.git denied to github-actions\\[bot\\]"
13
+ flags: "i"
14
+ - regex: "fatal: unable to access 'https://github\\.com/.+': The requested URL returned error: 403"
15
+ flags: "i"
16
+ - regex: "Resource not accessible by integration"
17
+ flags: "i"
18
+ error_messages:
19
+ - "remote: Permission to org/repo.git denied to github-actions[bot]."
20
+ - "fatal: unable to access 'https://github.com/org/repo.git/': The requested URL returned error: 403"
21
+ - "Resource not accessible by integration"
22
+ root_cause: |
23
+ Since February 2023, newly created repositories default `GITHUB_TOKEN` to read-only
24
+ permissions in many cases. A workflow that tries to push commits, create releases, or
25
+ modify pull requests without an explicit `permissions:` block can suddenly fail with 403
26
+ or integration access errors.
27
+
28
+ The token exists, but it does not have the scopes the job assumed it had.
29
+ fix: |
30
+ Add the minimum required `permissions:` block at the workflow or job level. For pushes,
31
+ tags, or release creation, that usually means `contents: write`. For pull request comments
32
+ or checks, grant the specific write permission those APIs require.
33
+ fix_code:
34
+ - language: yaml
35
+ label: "Grant explicit write permissions to GITHUB_TOKEN"
36
+ code: |
37
+ name: Release
38
+
39
+ on:
40
+ push:
41
+ branches:
42
+ - main
43
+
44
+ permissions:
45
+ contents: write
46
+
47
+ jobs:
48
+ release:
49
+ runs-on: ubuntu-latest
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+ - run: git push origin HEAD:main
53
+ prevention:
54
+ - "Assume `GITHUB_TOKEN` is read-only unless you explicitly grant the needed permission."
55
+ - "Set least-privilege `permissions:` blocks on every workflow instead of relying on defaults."
56
+ - "Match API usage to the smallest write scope required."
57
+ docs:
58
+ - url: "https://docs.github.com/en/actions/security-guides/automatic-token-authentication"
59
+ label: "Automatic token authentication"
60
+ - url: "https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions"
61
+ label: "permissions"
62
+ source:
63
+ article: "https://htek.dev/articles/github-actions-debugging-guide"
64
+ section: "GITHUB_TOKEN 403 and read-only defaults"