Repository navigation
fix(security): gate Dependabot approve/merge on check success and commit provenance (#1399, #1400) #1421
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
Merged
Merged
fix(security): gate Dependabot approve/merge on check success and commit provenance (#1399, #1400) #1421
Changes from all commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
0530329
fix(security): gate Dependabot approve/merge on check success and com…
don-petry f0ae8f3
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 9fb2e5e
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 31953be
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry d9090ab
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry d9270d2
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 0cd6921
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 29f81b8
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 4f1bc38
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 1af3161
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry c661c72
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry ac9bb71
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 659bbfc
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry ccea012
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 30b4fad
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 59b9cfe
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 48d7069
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry b3ffe3e
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 9be6619
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 2178179
fix(security): withdraw Dependabot approval and auto-merge when the h…
don-petry 64d692a
fix(security): withdraw automation when a Dependabot update becomes i…
don-petry e70c253
test(dependabot): fail extraction of a missing run block; correct the…
don-petry 6150ffb
docs(dependabot): record the accepted auto-merge window (#1400)
don-petry 738f4f2
fix(dependabot): do not hold merges on org agent jobs
don-petry c526db3
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 3fa4ad2
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry e661fc0
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry b436fb8
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry d076999
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 9ace830
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 6015ebb
fix(dependabot): withdraw automation when metadata fails; count only …
don-petry 537a374
Merge branch 'main' of https://github.com/combsmith/.github into sec-…
don-petry b64c289
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 539990a
fix(dependabot): count a required check only as a GitHub Actions chec…
don-petry fd7b407
test(dependabot): pin that forged unsigned Dependabot-attributed comm…
don-petry 60cd8bb
fix(dependabot): bind required checks to the ruleset's app; scope bot…
don-petry 4362311
fix(dependabot): do not request workflows permission the App lacks (#…
don-petry 6a0f6c9
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 30de8af
Merge remote-tracking branch 'origin/fix/sec-dependabot-merge-gating'…
don-petry 831dcc7
fix(dependabot): read the merge gate with the App token, not GITHUB_T…
don-petry 17b4155
Merge branch 'main' into fix/sec-dependabot-merge-gating
don-petry 15804e0
Merge remote-tracking branch 'origin/fix/sec-dependabot-merge-gating'…
don-petry cc8abca
style(dependabot): wrap the failed-token error under the 200-column l…
don-petry 64497f8
test(dependabot): make the token-split assertions fail when they shou…
don-petry 5cc84ac
fix(dependabot): collapse superseded check runs only when unambiguous…
don-petry a775709
test(dependabot): pin that forged GITHUB_TOKEN commits are refused (#…
don-petry 1f54664
docs(dependabot): match the caller stub example to the template; note…
don-petry File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| # Dependabot gate scripts | ||
|
|
||
| Decision logic for the org-level Dependabot reusables | ||
| (`.github/workflows/dependabot-automerge-reusable.yml` and | ||
| `.github/workflows/dependabot-rebase-reusable.yml`). Both workflows feed the | ||
| same privileged sink — a GitHub App that approves Dependabot PRs and merges | ||
| them with ruleset bypass — so the gates that protect it live here, unit-tested | ||
| with bats (`test/workflows/dependabot/`), instead of inline in YAML. | ||
|
|
||
| Each reusable checks this repo out at its own commit (`job.workflow_sha`) into | ||
| `.dependabot-tooling/` and sources both libs, so the gates always match the | ||
| channel the caller pinned. | ||
|
|
||
| ## `lib/gates.sh` — pure decisions | ||
|
|
||
| No network calls. Each gate prints a one-line reason and returns `0` (pass) or | ||
| `1` (block). Malformed or missing input always blocks. | ||
|
|
||
| | Function | Passes only when | | ||
| |----------|------------------| | ||
| | `dependabot_checks_gate PR_JSON REQUIRED_JSON [SHA]` | Agent jobs (`DEPENDABOT_AGENT_JOB_RE`, GitHub Actions only, never a required check) are not counted. Every other check run on the head commit `COMPLETED` as `success`, `neutral` or `skipped`; every commit status is `success`; at least one check reported; every check the base branch's rules require is present as a check run from the required app: the app the rule's `integration_id` names, or GitHub Actions when it names none (a commit status, or a run from any other app or with an unreadable app, does not count); the rollup is for the head commit (and the head is `SHA` when given). Failure, cancelled, timed_out, action_required, stale, startup_failure, pending, missing, or more than one page of checks all block (#1399). A check run superseded on the same commit (same app, workflow, event and name, lower databaseId) is not counted, but only when that whole key can be read and the newest run is unique and `success`; otherwise every run of the check counts, so any failure among them blocks. | | ||
| | `dependabot_provenance_gate PR_JSON COMMITS_JSON [APP_LOGIN] [SHA]` | The full commit list was read, at least one commit is Dependabot's, and every commit has a verified signature and is either authored by `dependabot[bot]` and committed by `web-flow` or `dependabot[bot]`, or a merge commit by `APP_LOGIN` committed by `web-flow` (the App's update-branch merges). Anything else — e.g. a commit someone else pushed to the branch — blocks (#1400). Returns `2`, not `1`, when the reads cannot be trusted (no head, no commits, a commit list that does not match the PR, malformed input), so a push between two reads is never taken as a foreign commit. | | ||
| | `dependabot_required_contexts RULES_JSON` | Prints the unique required checks from `GET /repos/{r}/rules/branches/{b}` as `{context, integration_id}` objects, with `integration_id` null when the rule names no app (returns `1` on a non-array). | | ||
|
|
||
| ## `lib/gh-io.sh` — GitHub I/O | ||
|
|
||
| Reads state through `gh`, applies the gates, and performs the writes. Any read | ||
| failure fails closed. | ||
|
|
||
| | Function | Does | | ||
| |----------|------| | ||
| | `dependabot_merge_gate REPO PR APP_LOGIN` | Auto-merge requested + `MERGEABLE` + checks gate + provenance gate, all bound to one head SHA. Sets `DEPENDABOT_GATE_SHA` / `DEPENDABOT_GATE_REASON`. Used by the merger before every direct merge. | | ||
| | `dependabot_provenance_check REPO PR APP_LOGIN [SHA]` | Provenance gate on the live PR. Returns `0` pass, `1` block (sets `DEPENDABOT_GATE_STALE=true` when the head moved past `SHA`), `2` state unreadable. Reads the head again after the commit list and returns `2` if it moved in between. | | ||
| | `dependabot_merge_pr REPO PR SHA` | Squash-merges with `sha=SHA`, so GitHub rejects the merge if the head moved after the gate ran. | | ||
| | `dependabot_approve_and_automerge REPO PR SHA APP_LOGIN` | Approves with `commit_id=SHA`, enables auto-merge with `--match-head-commit SHA`, then re-reads the PR. If the head moved to a verified commit, it dismisses the approval of `SHA`; if the new head fails the gate or the PR cannot be read, it revokes everything. | | ||
| | `dependabot_dismiss_app_approvals REPO PR APP_LOGIN REASON [KEEP_SHA]` | Dismisses `APP_LOGIN`'s approvals except those of `KEEP_SHA`. Returns `1` if one may have survived. | | ||
| | `dependabot_revoke_automation REPO PR APP_LOGIN REASON` | Disables auto-merge and dismisses all of `APP_LOGIN`'s approvals. Returns `1` unless the PR reads back with auto-merge off and every dismissal succeeded, and the workflow then fails. | | ||
| | `dependabot_withdraw_app_automation REPO PR APP_LOGIN REASON` | For an update that became ineligible: dismisses `APP_LOGIN`'s approvals and disables auto-merge only if `APP_LOGIN` enabled it. | | ||
|
|
||
| **What this cannot make atomic.** Native auto-merge has no head-bound form that | ||
| survives a push: a writer's push does not disable it, and `--match-head-commit` | ||
| applies only when it is enabled. The re-read and the `synchronize` run together | ||
| withdraw that state on every interleaving, but not in the same instant, so a | ||
| foreign push has a short window before its run revokes. The merger is not | ||
| exposed to it: it re-runs both gates and merges with `sha=`. If the `synchronize` | ||
| run does not run at all, or fails because the provenance read returned `2` | ||
| (the PR state could not be read, so there is no verdict to act on), the state | ||
| stays until something re-evaluates the PR; a failed read fails the run, so it | ||
| shows up as a red check. The | ||
| required approval of the last push should block a merge in that window, but this | ||
| has not been tested end to end. The maintainer accepted this risk on 2026-11-02 | ||
| (see `standards/dependabot-policy.md`). | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,249 @@ | ||
| #!/usr/bin/env bash | ||
| # Merge / approval gates for the Dependabot reusables (issues #1399, #1400). | ||
| # | ||
| # Pure decisions: no network calls, no sleeps. Each gate takes the JSON the | ||
| # I/O glue (lib/gh-io.sh) fetched, prints a one-line reason, and returns | ||
| # 0 pass — safe to approve / merge the PR at the verified head SHA | ||
| # 1 block — skip the PR (fail closed); the reason says why | ||
| # Malformed or missing input always blocks. | ||
| # | ||
| # Tested by test/workflows/dependabot/gates.bats. | ||
| # Contract: see .github/scripts/dependabot/README.md | ||
|
|
||
| # Logins GitHub attributes to genuine Dependabot commits. Dependabot commits are | ||
| # authored by dependabot[bot] and committed by GitHub (`web-flow`) with a | ||
| # GitHub-verified signature. | ||
| DEPENDABOT_LOGIN="dependabot[bot]" | ||
|
|
||
| # Org agent jobs that run on every PR but do not test the change. A cancelled or | ||
| # failed agent job says nothing about a dependency update, so it does not hold | ||
| # the merge. Exact names and prefixes only, and only from GitHub Actions, so a | ||
| # similarly named external check still counts. A check the base branch's rules | ||
| # require is never exempt. | ||
| # dev-lead / *, pr-auto-review / * caller jobs of those reusables | ||
| # pr-review / review the agent-ingress caller job | ||
| # review / review the pre-ingress caller job | ||
| DEPENDABOT_AGENT_JOB_RE='^(dev-lead / .+|pr-auto-review / .+|pr-review / review|review / review)$' | ||
|
|
||
| # dependabot_checks_gate PR_JSON REQUIRED_JSON [EXPECTED_SHA] | ||
| # PR_JSON GraphQL `pullRequest` object (or the whole `{data:{repository: | ||
| # {pullRequest:…}}}` response) with headRefOid and | ||
| # commits(last:1){nodes{commit{oid statusCheckRollup{contexts{ | ||
| # pageInfo{hasNextPage} nodes{CheckRun{databaseId name status | ||
| # conclusion checkSuite{app{slug} workflowRun{event workflow{ | ||
| # databaseId}}}} StatusContext{context state}}}}}}}. | ||
| # REQUIRED_JSON JSON array of the checks the base branch's rules require: | ||
| # {context, integration_id} objects as dependabot_required_contexts | ||
| # prints them, or plain names (no integration_id). `[]` when none. | ||
| # EXPECTED_SHA optional — block unless the PR head is this commit. | ||
| # | ||
| # A check run superseded on the same commit — an earlier run of the same check | ||
| # from the same app, workflow and event, e.g. one cancelled by a concurrency | ||
| # group or replaced by a re-run — is not counted when the newest (highest | ||
| # databaseId) run is unique and succeeded. Otherwise every run of the check | ||
| # counts: runs without a databaseId, or whose app, workflow or event cannot | ||
| # be read, are never collapsed, nor are runs whose newest was skipped, | ||
| # neutral, failing or pending, or shares its databaseId. | ||
| # Agent jobs (DEPENDABOT_AGENT_JOB_RE) are not counted. Passes only when every | ||
| # other check run on the head commit COMPLETED as success, neutral or skipped, | ||
| # every commit status is success, at least one such check was reported, and | ||
| # every required check is present as a check run from the required app: the | ||
| # app the rule's integration_id names, or GitHub Actions when it names none. | ||
| # A commit status, or a run from any other app or with an unreadable app, | ||
| # does not count. Anything else — failure, cancelled, | ||
| # timed_out, action_required, stale, startup_failure, pending, missing, or more | ||
| # contexts than one page — blocks. | ||
| dependabot_checks_gate() { | ||
| local pr_json="$1" required_json="${2:-[]}" expected="${3:-}" out verdict reason | ||
|
|
||
| out=$(jq -r --argjson required "$required_json" --arg expected "$expected" \ | ||
| --arg agent_re "$DEPENDABOT_AGENT_JOB_RE" ' | ||
| (if has("data") then .data.repository.pullRequest else . end) as $pr | ||
| | ($pr.headRefOid // "") as $head | ||
| | ($pr.commits.nodes[0].commit // {}) as $commit | ||
| | ($commit.statusCheckRollup.contexts // null) as $ctx | ||
| | def run_key: | ||
| [(.checkSuite.app.slug? // null), (.checkSuite.workflowRun.workflow.databaseId? // null), | ||
| (.checkSuite.workflowRun.event? // null), (.name // null)]; | ||
| def collapsible: .__typename == "CheckRun" and ((.databaseId | type) == "number") | ||
| and (run_key | (.[0] | type) == "string" and .[0] != "" | ||
| and (.[1] | type) == "number" | ||
| and (.[2] | type) == "string" and .[2] != "" | ||
| and (.[3] | type) == "string" and .[3] != ""); | ||
| def latest_runs: | ||
| # Older runs of a check are dropped only when the newest run of the | ||
| # same (app, workflow, event, name) is unambiguous and succeeded. A run | ||
| # whose key cannot be read fully, a newest run that was skipped, | ||
| # neutral, failing or pending, or a tie on the newest databaseId keeps | ||
| # every run of the group, so any failure among them still holds. | ||
| ([.[] | select(collapsible)] | group_by(run_key) | ||
| | map((map(.databaseId) | max) as $top | ||
| | [.[] | select(.databaseId == $top)] as $newest | ||
| | if ($newest | length) == 1 | ||
| and (($newest[0].status // "" | ascii_upcase) == "COMPLETED") | ||
| and (($newest[0].conclusion // "" | ascii_upcase) == "SUCCESS") | ||
| then $newest else . end) | ||
| | add // []) as $kept | ||
| | [.[] | select(collapsible | not)] + $kept; | ||
| ($ctx.nodes // [] | latest_runs) as $nodes | ||
| | def name_of: if .__typename == "StatusContext" then (.context // "") | ||
| else (.name // "") end; | ||
| def ok: if .__typename == "CheckRun" then | ||
| ((.status // "" | ascii_upcase) == "COMPLETED") | ||
| and ((.conclusion // "" | ascii_upcase) | ||
| | IN("SUCCESS", "NEUTRAL", "SKIPPED")) | ||
| elif .__typename == "StatusContext" then | ||
| ((.state // "" | ascii_upcase) == "SUCCESS") | ||
| else false end; | ||
| def outcome: if .__typename == "StatusContext" then (.state // "MISSING") | ||
| elif ((.status // "" | ascii_upcase) != "COMPLETED") then (.status // "MISSING") | ||
| else (.conclusion // "MISSING") end | ascii_downcase; | ||
| def agent_job: .__typename == "CheckRun" | ||
| and (.checkSuite.app.slug? // "") == "github-actions" | ||
| and (name_of | test($agent_re)) | ||
| and (name_of as $n | [$required[] | if type == "object" then .context else . end] | index($n) | not); | ||
| # A required check counts only as a CheckRun from the app the rule names | ||
| # (integration_id), or from GitHub Actions when it names none. A commit | ||
| # status, or a run from any other app or with an unreadable app, with the | ||
| # same name does not satisfy it. | ||
| def satisfies($r): | ||
| .__typename == "CheckRun" and name_of == $r.context | ||
| and (if ($r.integration_id | type) == "number" | ||
| then (.checkSuite.app.databaseId? // null) == $r.integration_id | ||
| else (.checkSuite.app.slug? // "") == "github-actions" end); | ||
| ([$required[] | if type == "object" then . else {context: ., integration_id: null} end]) as $req | ||
| | ([$req[] | . as $r | select([$nodes[] | satisfies($r)] | any | not) | .context]) as $missing | ||
| | ([$nodes[] | select(agent_job | not)]) as $counted | ||
| | (($nodes | length) - ($counted | length)) as $exempt | ||
| | ([$counted[] | select(ok | not) | "\(name_of) (\(outcome))"]) as $bad | ||
|
don-petry marked this conversation as resolved.
Resolved
|
||
| | if $head == "" then "block\tPR head commit unknown" | ||
| elif ($expected != "" and $head != $expected) then | ||
| "block\thead moved to \($head[0:7]) (expected \($expected[0:7]))" | ||
| elif ($commit.oid // "") != $head then | ||
| "block\tcheck rollup is not for the head commit \($head[0:7])" | ||
| elif $ctx == null or ($counted | length) == 0 then | ||
| "block\tno checks reported on \($head[0:7])" | ||
| elif ($ctx.pageInfo.hasNextPage // false) then | ||
| "block\tmore checks than fit in one page on \($head[0:7]) — cannot verify them all" | ||
| elif ($bad | length) > 0 then | ||
| "block\t\($bad | length) check(s) not successful on \($head[0:7]): \($bad | join(", "))" | ||
| elif ($missing | length) > 0 then | ||
| "block\trequired check(s) not reported by the required app on \($head[0:7]): \($missing | join(", "))" | ||
| else | ||
| "pass\tall \($counted | length) check(s) succeeded on \($head[0:7])\(if $exempt > 0 then " (\($exempt) agent job(s) not counted)" else "" end)" | ||
| end | ||
| ' <<<"$pr_json" 2>/dev/null) || out="" | ||
|
|
||
| verdict="${out%%$'\t'*}" | ||
| reason="${out#*$'\t'}" | ||
| if [[ "$verdict" == "pass" ]]; then | ||
| echo "$reason" | ||
| return 0 | ||
| fi | ||
| if [[ "$verdict" == "block" ]]; then | ||
| echo "$reason" | ||
| else | ||
| echo "could not evaluate checks (malformed input)" | ||
| fi | ||
| return 1 | ||
| } | ||
|
|
||
| # dependabot_provenance_gate PR_JSON COMMITS_JSON [TRUSTED_MERGE_LOGIN] [EXPECTED_SHA] | ||
| # PR_JSON REST `GET /repos/{r}/pulls/{n}` object (head.sha, commits). | ||
| # COMMITS_JSON REST `GET /repos/{r}/pulls/{n}/commits` — a flat array, or the | ||
| # array of pages `gh api --paginate --slurp` produces. | ||
| # TRUSTED_MERGE_LOGIN login of the merge App (e.g. `my-app[bot]`); merge | ||
| # commits it created through the update-branch API are allowed. | ||
| # Empty — only Dependabot commits are allowed. | ||
| # EXPECTED_SHA optional — block unless the PR head is this commit. | ||
| # | ||
| # Passes only when the full commit list was read (its length equals the PR's | ||
| # commit count and contains the head), at least one commit is Dependabot's, | ||
| # and EVERY commit is either | ||
| # - authored by dependabot[bot], committed by web-flow or dependabot[bot], | ||
| # with a GitHub-verified signature; or | ||
| # - a merge commit (2+ parents) authored by TRUSTED_MERGE_LOGIN, committed | ||
| # by web-flow, with a GitHub-verified signature (update-branch). | ||
| # Any other commit — e.g. one pushed to the branch by someone else — blocks. | ||
| # | ||
| # Returns 0 pass, 1 block (a verdict about the commits), 2 block (the reads | ||
| # cannot be trusted: no head, no commits, a commit list that does not match | ||
| # the PR — a push between the two reads — or malformed input). Callers must | ||
| # not treat 2 as evidence of a foreign commit. | ||
| dependabot_provenance_gate() { | ||
| local pr_json="$1" commits_json="$2" trusted="${3:-}" expected="${4:-}" out verdict reason | ||
|
|
||
| # Both documents go through stdin: a long commit list can exceed the | ||
| # per-argument size limit of --argjson. | ||
| out=$({ printf '%s\n' "$pr_json"; printf '%s\n' "$commits_json"; } | jq -rs \ | ||
| --arg trusted "$trusted" --arg expected "$expected" --arg bot "$DEPENDABOT_LOGIN" ' | ||
| if length != 2 then error("expected two documents") else . end | ||
| | .[0] as $pr | .[1] as $pages | ||
| | ($pr.head.sha // "") as $head | ||
| | ($pr.commits // -1) as $count | ||
| | ([$pages | if type == "array" then flatten[] else empty end | ||
| | select(type == "object")]) as $commits | ||
| | def verified: (.commit.verification.verified // false) == true; | ||
| def author: (.author.login // ""); | ||
| def committer: (.committer.login // ""); | ||
| def from_dependabot: | ||
| author == $bot and (committer | IN("web-flow", $bot)) and verified; | ||
| def trusted_merge: | ||
| $trusted != "" and author == $trusted and committer == "web-flow" | ||
| and ((.parents // []) | length) >= 2 and verified; | ||
| ([$commits[] | select((from_dependabot or trusted_merge) | not) | ||
| | "\(.sha // "?" | .[0:7]) (author \(author | if . == "" then "unknown" else . end), committer \(committer | if . == "" then "unknown" else . end)\(if verified then "" else ", unverified" end))"]) as $foreign | ||
| | if $head == "" then "unreadable\tPR head commit unknown" | ||
| elif ($expected != "" and $head != $expected) then | ||
| "block\thead moved to \($head[0:7]) (expected \($expected[0:7]))" | ||
| elif ($commits | length) == 0 then "unreadable\tno commits read" | ||
| elif ($commits | length) != $count then | ||
| "unreadable\tread \($commits | length) of \($count) commits — cannot verify them all" | ||
| elif ([$commits[] | .sha] | index($head)) == null then | ||
| "unreadable\thead \($head[0:7]) not in the commit list" | ||
| elif ($foreign | length) > 0 then | ||
| "block\t\($foreign | length) commit(s) not from Dependabot: \($foreign | join(", "))" | ||
| elif ([$commits[] | select(from_dependabot)] | length) == 0 then | ||
| "block\tno Dependabot commit on the PR" | ||
| else | ||
| "pass\tall \($commits | length) commit(s) from Dependabot\(if $trusted != "" then " or \($trusted) branch updates" else "" end) (head \($head[0:7]))" | ||
| end | ||
| ' 2>/dev/null) || out="" | ||
|
|
||
| verdict="${out%%$'\t'*}" | ||
| reason="${out#*$'\t'}" | ||
| if [[ "$verdict" == "pass" ]]; then | ||
| echo "$reason" | ||
| return 0 | ||
| fi | ||
| if [[ "$verdict" == "block" ]]; then | ||
| echo "$reason" | ||
| return 1 | ||
| fi | ||
| if [[ "$verdict" == "unreadable" ]]; then | ||
| echo "$reason" | ||
| else | ||
| echo "could not evaluate commit provenance (malformed input)" | ||
| fi | ||
| return 2 | ||
| } | ||
|
|
||
| # dependabot_required_contexts RULES_JSON | ||
| # RULES_JSON REST `GET /repos/{r}/rules/branches/{b}` — a flat array of | ||
| # rules or the array of pages `--paginate --slurp` produces. | ||
| # Prints a JSON array of the unique required checks as {context, | ||
| # integration_id} (null when the rule names no app). Returns 1 (printing | ||
| # nothing) when the input is not an array. | ||
| dependabot_required_contexts() { | ||
| local rules_json="$1" out | ||
| out=$(jq -c ' | ||
| if type != "array" then error("not an array") else . end | ||
| | [flatten[] | select(type == "object" and .type == "required_status_checks") | ||
| | (.parameters.required_status_checks // [])[] | ||
| | select((.context | type) == "string" and .context != "") | ||
| | {context, integration_id: (if (.integration_id | type) == "number" then .integration_id else null end)}] | ||
| | unique | ||
| ' <<<"$rules_json" 2>/dev/null) || return 1 | ||
| [[ -n "$out" ]] || return 1 | ||
| echo "$out" | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.