@htekdev/actions-debugger 1.0.80 → 1.0.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,85 @@
1
+ id: runner-environment-147
2
+ title: "Windows runner 'shell: powershell' invokes PowerShell 5 not PowerShell 7"
3
+ category: runner-environment
4
+ severity: error
5
+ tags:
6
+ - windows
7
+ - powershell
8
+ - pwsh
9
+ - shell
10
+ - powershell-version
11
+
12
+ patterns:
13
+ - regex: 'The term .{0,20} is not recognized as the name of a cmdlet'
14
+ flags: i
15
+ - regex: 'Unexpected token.*\?\.'
16
+ flags: i
17
+ - regex: 'parameter cannot be found.*Parallel'
18
+ flags: i
19
+
20
+ error_messages:
21
+ - "The term '??' is not recognized as the name of a cmdlet, function, script file, or operable program."
22
+ - "Unexpected token '?.' in expression or statement."
23
+ - "ForEach-Object: A parameter cannot be found that matches parameter name 'Parallel'."
24
+
25
+ root_cause: |
26
+ On Windows GitHub-hosted runners, explicitly setting `shell: powershell` in a
27
+ workflow step invokes `powershell.exe` — Windows PowerShell 5.1 — NOT the modern
28
+ PowerShell 7.x (pwsh).
29
+
30
+ Windows PowerShell 5.1 is a separate executable that lacks numerous features
31
+ introduced in PowerShell 6.0 (Core) and later:
32
+ - Null-coalescing operator: `$a ?? $b` (requires PS 7.0+)
33
+ - Null-coalescing assignment: `$a ??= 'default'` (requires PS 7.0+)
34
+ - Null-conditional member access: `$obj?.Property` (requires PS 7.0+)
35
+ - Ternary operator: `$x ? $y : $z` (requires PS 7.0+)
36
+ - ForEach-Object -Parallel (requires PS 7.0+)
37
+ - Pipeline chain operators: `&&`, `||` (requires PS 7.0+)
38
+
39
+ The default shell on Windows runners (when no `shell:` key is specified) is `pwsh`
40
+ (PowerShell 7). This means workflows that omit `shell:` work fine, but any step that
41
+ explicitly adds `shell: powershell` regresses to PowerShell 5.1.
42
+
43
+ Developers often set `shell: powershell` when they mean "use PowerShell" without
44
+ knowing the distinction between Windows PowerShell and PowerShell 7.
45
+
46
+ fix: |
47
+ Replace `shell: powershell` with `shell: pwsh` to use PowerShell 7.
48
+
49
+ If the workflow requires PowerShell 5.1 compatibility (rare), audit the script
50
+ and remove PS7-only syntax. However, in almost all cases the intent is PowerShell 7.
51
+
52
+ fix_code:
53
+ - language: yaml
54
+ label: "Use pwsh for PowerShell 7 (fix)"
55
+ code: |
56
+ # Before (broken on modern PS syntax):
57
+ # - run: $value = $null ?? "default"
58
+ # shell: powershell
59
+
60
+ # After (correct):
61
+ - run: $value = $null ?? "default"
62
+ shell: pwsh
63
+ - language: yaml
64
+ label: "Set pwsh as default shell for all Windows steps"
65
+ code: |
66
+ defaults:
67
+ run:
68
+ shell: pwsh
69
+
70
+ jobs:
71
+ build:
72
+ runs-on: windows-latest
73
+ steps:
74
+ - run: $value = $null ?? "default" # uses pwsh by default
75
+
76
+ prevention:
77
+ - 'Use `shell: pwsh` (not `shell: powershell`) in any step using PowerShell 6+ syntax.'
78
+ - 'Set `defaults.run.shell: pwsh` at workflow level to apply PowerShell 7 globally on Windows.'
79
+ - 'Use actionlint — it does not currently distinguish PS5 vs PS7 but code review should catch explicit `shell: powershell`.'
80
+
81
+ docs:
82
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-syntax-for-github-actions#jobsjob_idstepsshell'
83
+ label: 'Workflow syntax: shell — GitHub Docs'
84
+ - url: 'https://learn.microsoft.com/en-us/powershell/scripting/whats-new/differences-from-windows-powershell'
85
+ label: 'Differences between Windows PowerShell 5.1 and PowerShell 7 — Microsoft Docs'
@@ -0,0 +1,102 @@
1
+ id: silent-failures-077
2
+ title: '`github.event.inputs` is undefined (null) when workflow triggered via `workflow_call`'
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - workflow-call
7
+ - workflow-dispatch
8
+ - inputs
9
+ - github-event-inputs
10
+ - reusable-workflow
11
+ patterns:
12
+ - regex: 'github\.event\.inputs\.'
13
+ flags: 'g'
14
+ - regex: 'on:\s*\n\s+workflow_call:'
15
+ flags: 'im'
16
+ error_messages:
17
+ - "Expression github.event.inputs.environment evaluated to ''"
18
+ - 'github.event.inputs is null'
19
+ - 'unexpected value '''''
20
+ root_cause: |
21
+ When a workflow supports both `workflow_dispatch` and `workflow_call` triggers,
22
+ developers often use `github.event.inputs.param` to read input values.
23
+ This works for `workflow_dispatch` but silently fails for `workflow_call`:
24
+
25
+ - `workflow_dispatch`: inputs are surfaced at `github.event.inputs.*`
26
+ (always strings regardless of declared type)
27
+ - `workflow_call`: inputs are NOT placed in `github.event.inputs`; they are
28
+ only available via the `inputs` context (`inputs.param`)
29
+
30
+ When a reusable workflow is invoked via `workflow_call`, `github.event.inputs`
31
+ is an empty object — referencing `github.event.inputs.param` evaluates to an
32
+ empty string `''`, not an error. The workflow continues running with silently
33
+ missing parameter values, producing incorrect behavior rather than a visible
34
+ failure.
35
+
36
+ The `inputs` context (without `github.event.`) is the correct way to read
37
+ inputs in both scenarios, as it is populated for both `workflow_dispatch` and
38
+ `workflow_call`.
39
+ fix: |
40
+ Replace all `github.event.inputs.*` references with `inputs.*` — the `inputs`
41
+ context works correctly for both `workflow_dispatch` and `workflow_call` events.
42
+ fix_code:
43
+ - language: yaml
44
+ label: 'Use inputs context instead of github.event.inputs'
45
+ code: |
46
+ on:
47
+ workflow_dispatch:
48
+ inputs:
49
+ environment:
50
+ type: string
51
+ required: true
52
+ workflow_call:
53
+ inputs:
54
+ environment:
55
+ type: string
56
+ required: true
57
+
58
+ jobs:
59
+ deploy:
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ # BAD: github.event.inputs is empty/null in workflow_call context
63
+ - run: echo "Deploying to ${{ github.event.inputs.environment }}"
64
+
65
+ # GOOD: inputs context works for both workflow_dispatch and workflow_call
66
+ - run: echo "Deploying to ${{ inputs.environment }}"
67
+
68
+ - language: yaml
69
+ label: 'Dual-trigger workflow using inputs context correctly'
70
+ code: |
71
+ on:
72
+ workflow_dispatch:
73
+ inputs:
74
+ dry_run:
75
+ type: boolean
76
+ default: false
77
+ workflow_call:
78
+ inputs:
79
+ dry_run:
80
+ type: boolean
81
+ default: false
82
+
83
+ jobs:
84
+ release:
85
+ runs-on: ubuntu-latest
86
+ steps:
87
+ - uses: actions/checkout@v4
88
+ # inputs.dry_run works for both dispatch and call
89
+ - if: '!inputs.dry_run'
90
+ run: ./publish.sh
91
+ prevention:
92
+ - 'Always use the `inputs` context (not `github.event.inputs`) when reading inputs in workflows that support both `workflow_dispatch` and `workflow_call`'
93
+ - 'Grep your workflow files for `github.event.inputs` and replace with `inputs` before adding a `workflow_call` trigger'
94
+ - '`github.event.inputs` values are always strings; `inputs` respects the declared type (boolean, number, string, choice)'
95
+ - 'Test reusable workflows by calling them from another workflow, not just via the UI dispatch button'
96
+ docs:
97
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_call'
98
+ label: 'workflow_call event — GitHub Docs'
99
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_dispatch'
100
+ label: 'workflow_dispatch event — GitHub Docs'
101
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#inputs-context'
102
+ label: 'inputs context — GitHub Docs'
@@ -0,0 +1,96 @@
1
+ id: silent-failures-075
2
+ title: "if: failure() notification job is skipped when workflow is manually cancelled"
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - if-condition
7
+ - failure
8
+ - cancelled
9
+ - always
10
+ - notification
11
+ - cleanup
12
+ patterns:
13
+ - regex: 'if:\s*failure\(\)'
14
+ flags: 'i'
15
+ - regex: 'Skipping.*cancelled'
16
+ flags: 'i'
17
+ error_messages:
18
+ - "Skipping: The job was skipped because one or more of its needs jobs was cancelled."
19
+ root_cause: |
20
+ When a workflow is manually cancelled (via the GitHub UI "Cancel run" button or
21
+ the REST API), in-progress jobs receive a cancellation signal. Jobs that have not
22
+ yet started are given the conclusion "cancelled". Jobs with if: failure() are
23
+ only evaluated against the "failure" conclusion — "cancelled" is a distinct
24
+ conclusion value. Because cancelled != failure, the condition evaluates to false
25
+ and the notification or cleanup job is silently skipped.
26
+
27
+ This catches teams by surprise when they add an alerting job intended to fire
28
+ whenever CI does not succeed:
29
+
30
+ notify-on-failure:
31
+ needs: build
32
+ if: failure()
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - name: Send Slack alert
36
+ ...
37
+
38
+ When an operator cancels a run mid-flight, the build job is marked cancelled,
39
+ not failed. The notify job evaluates if: failure() → false and is skipped with
40
+ no Slack message. The on-call team receives no signal that a run was interrupted.
41
+
42
+ The failure() function returns true only when at least one needed job has a
43
+ "failure" conclusion. It does NOT return true for "cancelled" conclusions.
44
+ fix: |
45
+ Use if: failure() || cancelled() to catch both failure and cancellation, or use
46
+ if: always() combined with a conditional check on the job's outcome inside the
47
+ step if you only want to act on non-success outcomes.
48
+ fix_code:
49
+ - language: yaml
50
+ label: "Catch both failure and cancellation in a notification job"
51
+ code: |
52
+ jobs:
53
+ build:
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - run: make build
57
+
58
+ notify-on-failure:
59
+ needs: build
60
+ # failure() catches job failures; cancelled() catches manual cancellation
61
+ if: failure() || cancelled()
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - name: Send failure or cancellation alert
65
+ run: |
66
+ echo "Build concluded: ${{ needs.build.result }}"
67
+ # Send Slack/PagerDuty notification here
68
+ - language: yaml
69
+ label: "Use always() and gate on outcome inside the step for fine-grained control"
70
+ code: |
71
+ jobs:
72
+ build:
73
+ runs-on: ubuntu-latest
74
+ steps:
75
+ - run: make build
76
+
77
+ notify:
78
+ needs: build
79
+ if: always()
80
+ runs-on: ubuntu-latest
81
+ steps:
82
+ - name: Notify only on non-success outcomes
83
+ if: needs.build.result != 'success'
84
+ run: |
85
+ echo "Build result: ${{ needs.build.result }}"
86
+ # Handles: failure, cancelled, skipped
87
+ prevention:
88
+ - "Use if: failure() || cancelled() instead of if: failure() for any alert, notification, or cleanup job that must run whenever CI does not succeed."
89
+ - "Prefer if: always() when the downstream job should run regardless of upstream outcome (e.g., always upload test reports)."
90
+ - "Audit all notification and cleanup jobs after cancellation to confirm they fired as expected — cancelled runs don't generate failure alerts by default."
91
+ - "The cancelled() function returns true when the workflow or job has been cancelled; combine with failure() using || for comprehensive coverage."
92
+ docs:
93
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-conditions-to-control-job-execution"
94
+ label: "GitHub Actions: Using conditions to control job execution"
95
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/using-conditions-to-control-job-execution#available-status-check-functions"
96
+ label: "GitHub Actions: Available status check functions — failure(), cancelled(), always()"
@@ -0,0 +1,85 @@
1
+ id: silent-failures-076
2
+ title: "Job outputs: block missing — needs.job.outputs.key is always empty string in downstream jobs"
3
+ category: silent-failures
4
+ severity: silent-failure
5
+ tags:
6
+ - job-outputs
7
+ - needs-context
8
+ - GITHUB_OUTPUT
9
+ - outputs-block
10
+ - empty-string
11
+ - downstream-jobs
12
+ patterns:
13
+ - regex: 'needs\.[a-z_][a-z0-9_-]*\.outputs\.[a-z_][a-z0-9_-]*'
14
+ flags: 'i'
15
+ error_messages:
16
+ - "needs.deploy.outputs.artifact_url is always empty string"
17
+ - "needs.build.outputs.sha: empty output in downstream job"
18
+ root_cause: |
19
+ Writing to $GITHUB_OUTPUT inside a step only makes the value available within that job
20
+ via steps.<step_id>.outputs.<key>. To expose an output to downstream jobs via
21
+ needs.<job>.outputs.<key>, the job must explicitly declare an outputs: block that maps
22
+ key names to step output expressions using ${{ steps.<id>.outputs.<key> }}.
23
+
24
+ Without the job-level outputs: mapping, $GITHUB_OUTPUT writes are silently ignored by
25
+ downstream consumers — needs.<job>.outputs.<key> evaluates to an empty string with no
26
+ warning, error, or annotation. The step that wrote to $GITHUB_OUTPUT exits with code 0
27
+ and no indication of the problem.
28
+
29
+ This two-step mechanism is required: the step writes to $GITHUB_OUTPUT (job-internal),
30
+ and the job's outputs: block re-exports selected values to the workflow's needs graph.
31
+ fix: |
32
+ Add an outputs: block at the job level (as a sibling of runs-on:, steps:, etc.)
33
+ that maps the desired key names to the corresponding step output expressions.
34
+ Every key you want accessible via needs.<job>.outputs.<key> must appear in this block.
35
+ fix_code:
36
+ - language: yaml
37
+ label: "Correct: job outputs: block declares which step outputs to expose"
38
+ code: |
39
+ jobs:
40
+ build:
41
+ runs-on: ubuntu-latest
42
+ outputs:
43
+ artifact-version: ${{ steps.version.outputs.value }}
44
+ build-sha: ${{ steps.hash.outputs.sha }}
45
+ steps:
46
+ - name: Compute version
47
+ id: version
48
+ run: echo "value=1.2.3" >> $GITHUB_OUTPUT
49
+ - name: Compute hash
50
+ id: hash
51
+ run: echo "sha=$(sha256sum dist/app | cut -d' ' -f1)" >> $GITHUB_OUTPUT
52
+
53
+ deploy:
54
+ needs: build
55
+ runs-on: ubuntu-latest
56
+ steps:
57
+ - run: echo "Deploying ${{ needs.build.outputs.artifact-version }}"
58
+ - language: yaml
59
+ label: "Wrong: outputs: block absent — needs.build.outputs.* is always empty"
60
+ code: |
61
+ jobs:
62
+ build:
63
+ runs-on: ubuntu-latest
64
+ # No outputs: block — step writes are invisible to downstream jobs
65
+ steps:
66
+ - name: Compute version
67
+ id: version
68
+ run: echo "value=1.2.3" >> $GITHUB_OUTPUT
69
+
70
+ deploy:
71
+ needs: build
72
+ runs-on: ubuntu-latest
73
+ steps:
74
+ - run: echo "${{ needs.build.outputs.artifact-version }}" # Always ''
75
+ prevention:
76
+ - "Any job that produces values for downstream jobs MUST have a matching outputs: block"
77
+ - "Use actionlint — it warns when needs.<job>.outputs.<key> references an undeclared key"
78
+ - "Debug by printing ${{ toJSON(needs) }} in a downstream step to see all available outputs"
79
+ - "Note: steps.<id>.outputs.<key> within the same job does NOT require an outputs: block"
80
+ - "Composite action outputs and reusable workflow outputs have separate but analogous declaration requirements"
81
+ docs:
82
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs"
83
+ label: "GitHub Docs: Passing information between jobs"
84
+ - url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idoutputs"
85
+ label: "GitHub Docs: jobs.<job_id>.outputs syntax"
@@ -0,0 +1,83 @@
1
+ id: triggers-055
2
+ title: "branches: filter does not apply to tag pushes — tag push always triggers unless tags-ignore is set"
3
+ category: triggers
4
+ severity: silent-failure
5
+ tags:
6
+ - push
7
+ - branches-filter
8
+ - tags
9
+ - tag-push
10
+ - tags-ignore
11
+ - ref-type
12
+ patterns:
13
+ - regex: 'on:\s*\n\s+push:\s*\n\s+branches:'
14
+ flags: 'm'
15
+ - regex: 'Triggered by refs/tags/'
16
+ flags: 'i'
17
+ error_messages:
18
+ - "Run triggered by push to refs/tags/v1.0.0 (unexpected — only branches: [main] was set)"
19
+ root_cause: |
20
+ The branches: filter under on.push: only evaluates branch refs (refs/heads/*). A push
21
+ to a tag ref (refs/tags/*) is an entirely separate ref type that the branches: filter
22
+ has no authority to gate. If no tags: or tags-ignore: filter is specified under
23
+ on.push:, ALL tag pushes pass through unconditionally.
24
+
25
+ This causes workflows intended to run "only on pushes to main" to also run on every
26
+ release tag, annotated tag, and automated tag created by CI pipelines.
27
+
28
+ Branch and tag filters are independent dimensions of the push event:
29
+ branches: / branches-ignore: — applies ONLY to branch refs (refs/heads/*)
30
+ tags: / tags-ignore: — applies ONLY to tag refs (refs/tags/*)
31
+
32
+ If a filter type is absent, all refs of that type pass through.
33
+ Setting branches: [main] does NOT automatically exclude tags.
34
+
35
+ The same mechanism applies in reverse: a tags: filter does not affect branch pushes.
36
+ And the paths: filter is also bypassed by tag pushes (see push-paths-filter-bypassed-by-tag).
37
+ fix: |
38
+ To restrict the workflow to branch pushes only, add a tags-ignore: ['**'] pattern
39
+ to exclude all tag pushes. Alternatively, add an if: condition to check github.ref_type.
40
+ fix_code:
41
+ - language: yaml
42
+ label: "Option 1: add tags-ignore to explicitly exclude all tag pushes"
43
+ code: |
44
+ on:
45
+ push:
46
+ branches:
47
+ - main
48
+ - 'release/**'
49
+ tags-ignore:
50
+ - '**' # Exclude all tag pushes from triggering this workflow
51
+ - language: yaml
52
+ label: "Option 2: use if condition on the job to check ref_type"
53
+ code: |
54
+ on:
55
+ push:
56
+ branches:
57
+ - main
58
+
59
+ jobs:
60
+ build:
61
+ if: github.ref_type == 'branch' # Skip if triggered by a tag push
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - run: echo "Only runs on branch pushes"
65
+ - language: yaml
66
+ label: "Option 3: explicitly allow specific tags alongside branches"
67
+ code: |
68
+ on:
69
+ push:
70
+ branches:
71
+ - main
72
+ tags:
73
+ - 'v[0-9]+.[0-9]+.[0-9]+' # Only semver release tags; other tags excluded
74
+ prevention:
75
+ - "Whenever you set branches:, also consider whether you need tags-ignore: ['**'] to exclude tag pushes"
76
+ - "Check if automated tooling (release-please, semantic-release, etc.) creates tags in your repo — they will trigger this workflow"
77
+ - "The same principle applies to paths: filter — it also does not block tag pushes"
78
+ - "Remember the rule: absent filter = all refs of that type pass through"
79
+ docs:
80
+ - url: "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onpushbranchestagsbranches-ignoretags-ignore"
81
+ label: "GitHub Docs: push event branches/tags filter syntax"
82
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push"
83
+ label: "GitHub Docs: Push event triggers"
@@ -0,0 +1,75 @@
1
+ id: triggers-056
2
+ title: '`on.release: types: [created]` fires when a draft release is first saved'
3
+ category: triggers
4
+ severity: silent-failure
5
+ tags:
6
+ - release
7
+ - triggers
8
+ - draft
9
+ - created
10
+ - published
11
+ patterns:
12
+ - regex: 'types:\s*\[?created\]?'
13
+ flags: 'i'
14
+ - regex: 'on:\s*\n\s+release:'
15
+ flags: 'im'
16
+ error_messages:
17
+ - 'Triggered by: release'
18
+ - 'github.event.action: created'
19
+ - 'github.event.release.draft: true'
20
+ root_cause: |
21
+ The `release` event with `types: [created]` fires when ANY release record is
22
+ first created in GitHub — including draft releases. When a developer clicks
23
+ "Save draft" to prepare release notes before publishing, GitHub fires the
24
+ `created` event immediately. This causes deployment or publish workflows to
25
+ run against an incomplete, unpublished draft.
26
+
27
+ The `created` type maps to the moment the release row is inserted in GitHub's
28
+ database, regardless of draft or published state. Many developers assume
29
+ `created` means "publicly released" because that mirrors the UI action of
30
+ clicking "Publish release", but the event fires earlier.
31
+
32
+ The correct type for "a release is now publicly visible" is `published`, which
33
+ fires only when the release transitions from draft (or pre-release) to a
34
+ published, non-draft release.
35
+ fix: |
36
+ Use `types: [published]` for workflows that should only run when a release
37
+ becomes publicly available. Add an `if:` guard as a defensive layer when
38
+ using `created`.
39
+ fix_code:
40
+ - language: yaml
41
+ label: 'Use published instead of created for deploy workflows'
42
+ code: |
43
+ # BAD: fires on draft creation too
44
+ on:
45
+ release:
46
+ types: [created]
47
+
48
+ # GOOD: fires only when release becomes publicly published
49
+ on:
50
+ release:
51
+ types: [published]
52
+ - language: yaml
53
+ label: 'Defensive if: guard when created type is required'
54
+ code: |
55
+ on:
56
+ release:
57
+ types: [created]
58
+ jobs:
59
+ deploy:
60
+ # Skip execution when triggered by a draft save
61
+ if: '!github.event.release.draft'
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - uses: actions/checkout@v4
65
+ - run: ./deploy.sh
66
+ prevention:
67
+ - 'Prefer `types: [published]` for deployment workflows to avoid triggering on draft saves'
68
+ - 'Note: `published` also fires when a pre-release is promoted to a full release — guard with `!github.event.release.prerelease` if needed'
69
+ - 'Add `if: ''!github.event.release.draft''` as an explicit guard when using the `created` type'
70
+ - 'Test your release workflow by creating a draft release and verifying it does or does not trigger as expected'
71
+ docs:
72
+ - url: 'https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#release'
73
+ label: 'release event — GitHub Docs'
74
+ - url: 'https://docs.github.com/en/rest/releases/releases#create-a-release'
75
+ label: 'Create a release REST API — GitHub Docs'
@@ -0,0 +1,98 @@
1
+ id: triggers-054
2
+ title: "workflow_dispatch choice input not validated when triggered via REST API — invalid values silently accepted"
3
+ category: triggers
4
+ severity: silent-failure
5
+ tags:
6
+ - workflow-dispatch
7
+ - choice-input
8
+ - rest-api
9
+ - inputs
10
+ - validation
11
+ - gh-cli
12
+ patterns:
13
+ - regex: 'type:\s*choice'
14
+ flags: 'i'
15
+ - regex: 'inputs\.\w+.*choice'
16
+ flags: 'i'
17
+ error_messages:
18
+ - "Workflow ran with input value not in declared choices list"
19
+ root_cause: |
20
+ The type: choice input in on.workflow_dispatch.inputs renders a dropdown
21
+ selector in the GitHub Actions UI and prevents users from submitting values
22
+ outside the declared options list. This validation is enforced entirely
23
+ client-side in the browser.
24
+
25
+ When a workflow is triggered via the GitHub REST API
26
+ (POST /repos/{owner}/{repo}/actions/workflows/{id}/dispatches) or the GitHub
27
+ CLI (gh workflow run --field key=value), GitHub does NOT validate that the
28
+ supplied value matches any of the declared choices. Any arbitrary string is
29
+ accepted and forwarded as-is to the workflow.
30
+
31
+ A workflow dispatched with inputs: { environment: "PROD" } when the declared
32
+ choices are ["prod", "staging", "dev"] will run with inputs.environment == "PROD".
33
+ Every if: condition that compares against the lower-case variants evaluates to
34
+ false, all deployment branches are skipped, and the workflow exits successfully
35
+ with no indication that the value was invalid. The mistyped run is invisible
36
+ unless logs are inspected manually.
37
+ fix: |
38
+ Add an explicit allowlist validation step at the top of the workflow that checks
39
+ the input value and exits with a non-zero status and a descriptive error message
40
+ if the value is not in the expected set. This makes API-triggered runs fail fast
41
+ with a clear error rather than silently no-oping.
42
+ fix_code:
43
+ - language: yaml
44
+ label: "Validate choice input with an allowlist check"
45
+ code: |
46
+ on:
47
+ workflow_dispatch:
48
+ inputs:
49
+ environment:
50
+ type: choice
51
+ description: Target deployment environment
52
+ options:
53
+ - prod
54
+ - staging
55
+ - dev
56
+
57
+ jobs:
58
+ deploy:
59
+ runs-on: ubuntu-latest
60
+ steps:
61
+ - name: Validate environment input
62
+ run: |
63
+ allowed="prod staging dev"
64
+ input="${{ inputs.environment }}"
65
+ if [[ ! " $allowed " =~ " ${input} " ]]; then
66
+ echo "::error::Invalid environment '${input}'. Allowed values: $allowed"
67
+ exit 1
68
+ fi
69
+
70
+ - name: Deploy to ${{ inputs.environment }}
71
+ run: echo "Deploying to ${{ inputs.environment }}"
72
+ - language: yaml
73
+ label: "Case-insensitive validation with normalisation"
74
+ code: |
75
+ - name: Validate and normalise environment input
76
+ id: validate
77
+ run: |
78
+ input=$(echo "${{ inputs.environment }}" | tr '[:upper:]' '[:lower:]')
79
+ allowed=("prod" "staging" "dev")
80
+ valid=false
81
+ for v in "${allowed[@]}"; do
82
+ [[ "$input" == "$v" ]] && valid=true && break
83
+ done
84
+ if [[ "$valid" != "true" ]]; then
85
+ echo "::error::Unrecognised environment '${{ inputs.environment }}' (normalised: '$input'). Allowed: ${allowed[*]}"
86
+ exit 1
87
+ fi
88
+ echo "environment=$input" >> "$GITHUB_OUTPUT"
89
+ prevention:
90
+ - "Treat type: choice as a UI hint only — always validate inputs programmatically inside the workflow."
91
+ - "Add an allowlist guard as the first step of any job that branches on a choice input."
92
+ - "Document valid values and casing rules in the input description field so API callers know the expected format."
93
+ - "Use actionlint to detect misspelled choices at authoring time before they reach production."
94
+ docs:
95
+ - url: "https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_dispatch"
96
+ label: "GitHub Actions: workflow_dispatch event — inputs"
97
+ - url: "https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event"
98
+ label: "GitHub REST API: Create a workflow dispatch event"