-
Notifications
You must be signed in to change notification settings - Fork 0
fix: dispatch checks for automation PRs #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ name: CI | |
|
|
||
| on: | ||
| pull_request: | ||
| workflow_dispatch: | ||
| push: | ||
| branches: | ||
| - main | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,93 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| usage() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Usage: $0 --pr <number> --check <required-check> --output <path>" >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pr_number="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| primary_check="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (($#)); do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "$1" in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --pr) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pr_number="${2:-}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shift 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --check) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| primary_check="${2:-}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shift 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --output) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output="${2:-}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shift 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| *) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| usage | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ "$pr_number" =~ ^[1-9][0-9]*$ ]] || usage | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ -n "$primary_check" && -n "$output" ]] || usage | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repository="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pr="$(gh api "repos/$repository/pulls/$pr_number")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state="$(jq -r .state <<<"$pr")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| base_ref="$(jq -r .base.ref <<<"$pr")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| head_ref="$(jq -r .head.ref <<<"$pr")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| head_sha="$(jq -r .head.sha <<<"$pr")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| head_repository="$(jq -r .head.repo.full_name <<<"$pr")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ "$state" == "open" ]] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ "$base_ref" == "main" ]] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ "$head_repository" == "$repository" ]] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ "$head_ref" =~ ^[A-Za-z0-9][A-Za-z0-9._/-]*$ ]] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ "$head_sha" =~ ^[0-9a-f]{40}$ ]] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+45
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Add diagnostics to the hard assertions. These bare 🩹 Proposed fix-[[ "$state" == "open" ]]
-[[ "$base_ref" == "main" ]]
-[[ "$head_repository" == "$repository" ]]
-[[ "$head_ref" =~ ^[A-Za-z0-9][A-Za-z0-9._/-]*$ ]]
-[[ "$head_sha" =~ ^[0-9a-f]{40}$ ]]
+[[ "$state" == "open" ]] || { echo "PR #$pr_number is not open." >&2; exit 1; }
+[[ "$base_ref" == "main" ]] || { echo "PR #$pr_number does not target main." >&2; exit 1; }
+[[ "$head_repository" == "$repository" ]] || { echo "PR #$pr_number head is not same-repository." >&2; exit 1; }
+[[ "$head_ref" =~ ^[A-Za-z0-9][A-Za-z0-9._/-]*$ ]] || { echo "PR #$pr_number head ref has an unexpected shape." >&2; exit 1; }
+[[ "$head_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "PR #$pr_number head sha has an unexpected shape." >&2; exit 1; }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checks_url="repos/$repository/commits/$head_sha/check-runs?per_page=100&filter=latest" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| before="$(gh api -H "Accept: application/vnd.github+json" "$checks_url")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| primary_before="$(jq --arg name "$primary_check" '[.check_runs[] | select(.name==$name) | .id] | max // 0' <<<"$before")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| protected_before="$(jq '[.check_runs[] | select(.name=="Protected controls") | .id] | max // 0' <<<"$before")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Events created with GITHUB_TOKEN do not recursively start pull_request | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # workflows. workflow_dispatch is the supported exception, so run the two | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # base-reviewed validators explicitly at the exact PR branch. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gh workflow run ci.yml --repo "$repository" --ref "$head_ref" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gh workflow run protected-controls.yml --repo "$repository" --ref "$head_ref" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -f "pr_number=$pr_number" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deadline=$((SECONDS + 900)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| latest="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while ((SECONDS < deadline)); do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| latest="$(gh api -H "Accept: application/vnd.github+json" "$checks_url")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| primary="$(jq -c --arg name "$primary_check" --argjson floor "$primary_before" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '[.check_runs[] | select(.name==$name and .id>$floor)] | max_by(.id) // empty' <<<"$latest")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| protected="$(jq -c --argjson floor "$protected_before" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '[.check_runs[] | select(.name=="Protected controls" and .id>$floor)] | max_by(.id) // empty' <<<"$latest")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -n "$primary" && -n "$protected" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| failed="$(jq -r -s '[.[] | select(.status=="completed" and .conclusion!="success") | .details_url] | join("\n")' \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <<<"$primary"$'\n'"$protected")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -n "$failed" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "A dispatched required check failed:" >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "$failed" >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if jq -e -s 'all(.[]; .status=="completed" and .conclusion=="success")' \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <<<"$primary"$'\n'"$protected" >/dev/null; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mkdir -p "$(dirname "$output")" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| jq -s --arg head "$head_sha" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'map({name, bucket:"pass", link:.details_url, headSha:$head, checkRunId:.id})' \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <<<"$primary"$'\n'"$protected" > "$output" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sleep 5 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+63
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Failure detection is gated behind both checks appearing, masking/delaying real failures. The 🐛 Proposed fix: evaluate failure independently of whether both checks have appeared- if [[ -n "$primary" && -n "$protected" ]]; then
- failed="$(jq -r -s '[.[] | select(.status=="completed" and .conclusion!="success") | .details_url] | join("\n")' \
- <<<"$primary"$'\n'"$protected")"
- if [[ -n "$failed" ]]; then
- echo "A dispatched required check failed:" >&2
- echo "$failed" >&2
- exit 1
- fi
- if jq -e -s 'all(.[]; .status=="completed" and .conclusion=="success")' \
- <<<"$primary"$'\n'"$protected" >/dev/null; then
+ present=()
+ [[ -n "$primary" ]] && present+=("$primary")
+ [[ -n "$protected" ]] && present+=("$protected")
+ if ((${`#present`[@]})); then
+ failed="$(printf '%s\n' "${present[@]}" | jq -r -s \
+ '[.[] | select(.status=="completed" and .conclusion!="success") | .details_url] | join("\n")')"
+ if [[ -n "$failed" ]]; then
+ echo "A dispatched required check failed:" >&2
+ echo "$failed" >&2
+ exit 1
+ fi
+ fi
+ if [[ -n "$primary" && -n "$protected" ]]; then
+ if jq -e -s 'all(.[]; .status=="completed" and .conclusion=="success")' \
+ <<<"$primary"$'\n'"$protected" >/dev/null; then
mkdir -p "$(dirname "$output")"
jq -s --arg head "$head_sha" \
'map({name, bucket:"pass", link:.details_url, headSha:$head, checkRunId:.id})' \
<<<"$primary"$'\n'"$protected" > "$output"
exit 0
fi
fi📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Timed out waiting for newly dispatched required checks at $head_sha." >&2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Bigpixelrocket/php-bin
Length of output: 6379
🌐 Web query:
GitHub Actions protected branch rules enforce pull requests created on untrusted forks cannot be built with workflow_dispatch💡 Result:
The statement that GitHub Actions protected branch rules specifically enforce that pull requests from untrusted forks cannot be built with workflow_dispatch is not accurate as a general rule, but it aligns with the broader security mechanisms GitHub employs to manage manual triggers and untrusted code [1][2]. Workflow_dispatch is a manual trigger that requires the workflow file to exist on the repository's default branch [3]. Because it is a manual trigger, it is not tied to the automated pull request lifecycle in the way events like pull_request or pull_request_target are [3]. Key security context: 1. Manual Trigger Controls: GitHub provides enterprise-level and repository-level policies that allow administrators to limit workflow_dispatch triggers specifically to maintainers or users with specific permissions [1][2]. This is designed to prevent manual-trigger abuse, where untrusted identities might otherwise attempt to initiate workflows [1][2]. 2. Fork Pull Request Protections: The primary concern with untrusted forks is the "pwn request" pattern, where malicious code from a fork is executed in a privileged context (e.g., using pull_request_target) [4]. GitHub mitigates this by restricting pull_request_target and by ensuring that actions/checkout v7 and later refuses to check out fork pull request code in privileged contexts by default [5][4]. 3. Event Restrictions: GitHub allows organizations to enforce event rules that control which triggers are permitted [1][2]. While you can restrict workflow_dispatch globally or by user, the restriction is not inherently linked to "pull requests created on untrusted forks" by default; rather, it is a policy-driven control to prevent unauthorized users from executing workflows [1][2]. In summary, while you cannot "pull" a workflow_dispatch trigger from a fork (since it must reside on the default branch), you can and should use GitHub Actions policies to restrict who is authorized to manually trigger workflows, thereby preventing untrusted actors from leveraging this capability [1][2][3].
Citations:
🌐 Web query:
GitHub Pull Request API head repo full_name base sha head sha fields documentation💡 Result:
The GitHub REST API provides detailed information about pull requests, including objects for head and base that contain specific branch and commit data [1][2]. When retrieving a single pull request via the GET /repos/{owner}/{repo}/pulls/{pull_number} endpoint, the response object includes nested head and base objects [1][2]. The relevant fields within these objects are: base object: - base.ref: The name of the base branch. - base.sha: The SHA of the latest commit on the base branch at the time the pull request was created or last updated [2]. - base.repo.full_name: The full name of the repository where the base branch resides (e.g., octocat/Hello-World) [2]. head object: - head.ref: The name of the head branch (the branch being merged). - head.sha: The SHA of the latest commit on the head branch [2]. - head.repo.full_name: The full name of the repository where the head branch resides [2]. These fields are essential for identifying the exact state and source of the branches involved in the pull request [2]. Users should consult the official GitHub REST API documentation for the most current schema definitions, as fields may evolve [1][3].
Citations:
🌐 Web query:
GitHub Actions branch name special characters backticks semicolon shell command injection github.ref💡 Result:
Using branch names or tag names that contain special characters (such as backticks, semicolons, quotes, or dollar signs) in GitHub Actions workflows creates a severe security risk known as shell command injection [1][2][3]. This vulnerability occurs when untrusted context variables—such as github.ref, github.head_ref, or github.event.pull_request.head.ref—are directly interpolated into shell scripts within a run: block [1][4][5]. Because GitHub Actions evaluates the ${{ }} expression and substitutes the raw string before the shell executes the command, a malicious branch name (e.g., a branch named test"; ls$GITHUB_WORKSPACE #) can break out of intended quotes or command structures to execute arbitrary code on the runner [1][4][5]. Key Vulnerability Mechanisms: - Interpolation: The $ {{ }} syntax automatically expands before the shell processes the script. If the value is not treated as a safe variable, special characters can be used to terminate the intended command and inject new ones [1][6][5]. - Shell Execution: Commands in run: steps are executed by a shell (like bash). If the input is not properly escaped or passed via an environment variable, the shell interprets injected characters (like backticks or semicolons) as control operators [1][4][7]. Recommended Hardening Strategies: - Never interpolate untrusted data directly into run: steps: Always pass untrusted inputs (like branch names, PR titles, or commit messages) through an env: block [4][5]. This ensures the value is passed to the shell as an environment variable rather than as part of the script itself [4][5]. - Use Environment Variables: Inside the run: script, reference the environment variable using double quotes (e.g., "$BRANCH_NAME") to prevent the shell from re-evaluating or expanding the content [4][5]. - Sanitize Inputs: If you must process data, avoid using dangerous shell functions like eval or unsanitized shell interpolation [3]. Use tools like printf %q or strictly validate that input matches an expected format (e.g., regex checks for alphanumeric branch names). Example of Secure Implementation: Instead of: - run: echo "Branch name: ${{ github.head_ref }}" Use: - name: Echo branch name safely env: BRANCH_NAME: ${{ github.head_ref }} run: | echo "Branch name: $BRANCH_NAME"
Citations:
Pass github context values through env before using them in the runner script.
${{ github.ref }},${{ github.sha }}, and${{ github.event_name }}are expanded before the bash script runs, so a malicious branch/ref payload can break out of shell syntax when used directly insiderun:. Define these as env vars from the expression and reference the env vars in the script, as already done forPR_NUMBER.🧰 Tools
🪛 zizmor (1.26.1)
[error] 36-36: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 Prompt for AI Agents
Source: Linters/SAST tools