From fba6bfc5c6b65a302d42aee879b9fbe1ba99a5f2 Mon Sep 17 00:00:00 2001 From: Dewet Diener Date: Wed, 27 May 2026 21:34:59 +0100 Subject: [PATCH 1/2] chore(ci): validate bumper version input before downstream use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `NEW` is sourced from `github.event.client_payload.version` / `inputs.version` and flows into git refs (`branch=bump/givenergy-modbus-${NEW}`, `git checkout -B`, `git push -f origin`), commit messages, PR titles and bodies, `GITHUB_OUTPUT`, and — most subtly — a Python `re.sub` backref template (`\g<1>{new}\g<2>`) in the bump step. The trust boundary is the BUMP_PAT used to fire the dispatch; a compromised PAT shouldn't be able to inject shell metacharacters, newlines (GITHUB_OUTPUT injection), git ref operators (`:`, `^`, `..`), or `\g<...>` regex backrefs. Validates against a SemVer-ish regex that accepts every modbus release tag in history (`X.Y.Z`, `X.Y.ZrcN`, `X.Y.ZaN`) plus forward-looking PEP 440 `.dev`/`.post` suffixes, rejecting everything else. A single guard at the top of the step defends every downstream use. Refs: https://github.blog/security/vulnerability-research/how-to-catch-github-actions-workflow-injections-before-attackers-do/ Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/bump-givenergy-modbus.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/bump-givenergy-modbus.yml b/.github/workflows/bump-givenergy-modbus.yml index 9b96bb7..801298f 100644 --- a/.github/workflows/bump-givenergy-modbus.yml +++ b/.github/workflows/bump-givenergy-modbus.yml @@ -29,6 +29,18 @@ jobs: echo "::error::no version supplied via client_payload.version or workflow inputs" exit 1 fi + # Validate before NEW flows into git refs, commit messages, PR bodies, + # GITHUB_OUTPUT, and a Python regex backref template downstream. The + # trust boundary here is the BUMP_PAT used to fire the dispatch — a + # compromised PAT shouldn't be able to inject shell metacharacters, + # newlines (GITHUB_OUTPUT injection), or `\g<...>` (regex backref + # subversion in the bump step). The character class permits every + # tag the modbus repo has historically published (`X.Y.Z`, + # `X.Y.ZrcN`, `X.Y.ZaN`) plus future PEP 440 `.dev`/`.post` suffixes. + if ! [[ "$NEW" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-?[a-z0-9.]+)?$ ]]; then + echo "::error::invalid givenergy-modbus version format (must match PEP 440-ish X.Y.Z[-?suffix])" + exit 1 + fi # Route the PR to the branch whose dependency range that major # version belongs to. Update the case statement when a new release # branch (and/or new modbus major) is introduced. From 43ea36fe4dfc48eaf5b91b2a032a7545cd01e121 Mon Sep 17 00:00:00 2001 From: Dewet Diener Date: Wed, 27 May 2026 21:45:17 +0100 Subject: [PATCH 2/2] chore(ci): reject empty dot-separated suffix segments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original `[a-z0-9.]+` class accepted `2.0.0..`, `2.0.0.post..1`, `2.0.0.`, and similar — values that pass the regex but then break `git checkout -B "bump/givenergy-modbus-${NEW}"` downstream with "not a valid branch name". Tighten the suffix to `[-.]?[a-z0-9]+(\.[a-z0-9]+)*` so each dot-separated segment is required to contain at least one alphanumeric character. All historical and forward-looking PEP 440 tags still pass; the `..` / trailing-`.` family no longer does. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/bump-givenergy-modbus.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/bump-givenergy-modbus.yml b/.github/workflows/bump-givenergy-modbus.yml index 801298f..7febb50 100644 --- a/.github/workflows/bump-givenergy-modbus.yml +++ b/.github/workflows/bump-givenergy-modbus.yml @@ -33,11 +33,16 @@ jobs: # GITHUB_OUTPUT, and a Python regex backref template downstream. The # trust boundary here is the BUMP_PAT used to fire the dispatch — a # compromised PAT shouldn't be able to inject shell metacharacters, - # newlines (GITHUB_OUTPUT injection), or `\g<...>` (regex backref - # subversion in the bump step). The character class permits every - # tag the modbus repo has historically published (`X.Y.Z`, - # `X.Y.ZrcN`, `X.Y.ZaN`) plus future PEP 440 `.dev`/`.post` suffixes. - if ! [[ "$NEW" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-?[a-z0-9.]+)?$ ]]; then + # newlines (GITHUB_OUTPUT injection), `\g<...>` (regex backref + # subversion in the bump step), or git ref operators (`..`, `^`, `:` + # in the branch name). The pattern accepts every modbus tag in + # history (`X.Y.Z`, `X.Y.ZrcN`, `X.Y.ZaN`) plus forward-looking + # PEP 440 `.dev`/`.post` suffixes; the inner repetition deliberately + # requires each dot-separated segment to be non-empty, so values + # like `2.0.0..` or `2.0.0.post..1` — which would pass a coarser + # `[a-z0-9.]+` class but then break `git checkout -B` downstream — + # are rejected here instead. + if ! [[ "$NEW" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.]?[a-z0-9]+(\.[a-z0-9]+)*)?$ ]]; then echo "::error::invalid givenergy-modbus version format (must match PEP 440-ish X.Y.Z[-?suffix])" exit 1 fi