@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.
- package/LICENSE +21 -21
- package/README.md +108 -108
- package/errors/_schema.json +89 -89
- package/errors/caching-artifacts/artifact-storage-quota-exceeded.yml +118 -0
- package/errors/caching-artifacts/cache-miss.yml +56 -56
- package/errors/caching-artifacts/cache-save-cancelled-job.yml +82 -0
- package/errors/caching-artifacts/cache-v3-to-v4-breaking-changes.yml +95 -0
- package/errors/caching-artifacts/cross-repo-artifacts-not-supported.yml +102 -0
- package/errors/caching-artifacts/upload-artifact-no-files-found.yml +92 -0
- package/errors/caching-artifacts/upload-artifact-v4-breaking.yml +67 -67
- package/errors/concurrency-timing/cancel-in-progress-deploy-drops.yml +97 -0
- package/errors/concurrency-timing/jobs-cancelled-unexpectedly.yml +60 -60
- package/errors/concurrency-timing/skipped-needs-cascade.yml +103 -0
- package/errors/concurrency-timing/workflow-run-conclusion-unchecked.yml +100 -0
- package/errors/known-unsolved/composite-input-env-vars-missing.yml +91 -0
- package/errors/known-unsolved/composite-nested-outputs-null.yml +101 -0
- package/errors/known-unsolved/no-dynamic-secret-access.yml +111 -0
- package/errors/known-unsolved/no-step-level-rerun.yml +94 -0
- package/errors/known-unsolved/no-step-retry.yml +53 -53
- package/errors/permissions-auth/checkout-submodule-private-auth.yml +91 -0
- package/errors/permissions-auth/fork-pr-secrets-unavailable.yml +97 -0
- package/errors/permissions-auth/github-token-403.yml +64 -64
- package/errors/permissions-auth/github-token-protected-branch-push.yml +109 -0
- package/errors/permissions-auth/oidc-aws-failure.yml +85 -85
- package/errors/permissions-auth/oidc-azure-subject-mismatch.yml +91 -0
- package/errors/runner-environment/disk-space.yml +57 -57
- package/errors/runner-environment/docker-buildx-not-setup.yml +106 -0
- package/errors/runner-environment/macos-homebrew-path.yml +90 -0
- package/errors/runner-environment/node-runtime-deprecation.yml +56 -56
- package/errors/runner-environment/npm-ci-lockfile-mismatch.yml +112 -0
- package/errors/runner-environment/self-hosted-stale-toolcache.yml +73 -0
- package/errors/runner-environment/setup-node-version-file-missing.yml +105 -0
- package/errors/runner-environment/windows-execution-policy.yml +83 -0
- package/errors/silent-failures/add-mask-no-retroactive-masking.yml +75 -0
- package/errors/silent-failures/composite-boolean-inputs-as-strings.yml +110 -0
- package/errors/silent-failures/conditional-output-null-downstream.yml +82 -0
- package/errors/silent-failures/continue-on-error-masks-failure.yml +86 -0
- package/errors/silent-failures/github-token-no-trigger.yml +57 -57
- package/errors/silent-failures/reusable-workflow-env-secrets-empty.yml +90 -0
- package/errors/silent-failures/scheduled-workflow-disabled.yml +59 -59
- package/errors/triggers/cron-schedule-late.yml +59 -59
- package/errors/triggers/pull-request-target-rce-risk.yml +117 -0
- package/errors/triggers/workflow-not-triggering.yml +60 -60
- package/errors/triggers/workflow-run-default-branch-requirement.yml +78 -0
- package/errors/yaml-syntax/anchors-not-supported.yml +95 -0
- package/errors/yaml-syntax/dynamic-matrix-fromjson-failure.yml +99 -0
- package/errors/yaml-syntax/if-always-true.yml +52 -52
- package/errors/yaml-syntax/missing-expression-wrapper.yml +67 -0
- package/errors/yaml-syntax/needs-indirect-outputs.yml +91 -0
- package/errors/yaml-syntax/secrets-in-if.yml +55 -55
- package/errors/yaml-syntax/unexpected-yaml-key.yml +69 -69
- package/errors/yaml-syntax/working-directory-ignored-on-uses.yml +66 -0
- 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"
|