netresearch/github-release
v0.12.3(MIT AND CC-BY-SA-4.0)
Safe, automated GitHub releases with supply chain security. Prevents dangerous gh release commands, orchestrates version bumps, signed tags, and CI-driven releases across ecosystems (TYPO3, PHP, Node.js, Go, Python, Rust, skill repos).
Changelog
All notable changes to this project are documented here.
The format follows Keep a Changelog, and the project adheres to Semantic Versioning.
This file starts at 0.12.1. Earlier releases are on the releases page; their notes were not backfilled here rather than reconstructed after the fact.
Unreleased
Added
Changed
Fixed
[0.12.3] - 2026-09-11
Changed
- The two PreToolUse guards now share one invocation splitter,
scripts/_invocations.py. It was developed in the tag guard (issue #105, plus the heredoc handling in 0.12.2) while the release guard kept a simpler copy, and the copies drifted apart until one was blind to what the other handled. The tag guard's behaviour is unchanged: its 67 cases pass before and after.
Fixed
guard-gh-release.pyjudged a whole Bash call as one string and requiredghto sit directly after a separator, so ten dangerous shapes walked past it. A newline is a command separator exactly as;is, but the call was flattened with" ".join(command.split())and the separator set held only[;&|]— sogh release createon its own line was never seen. Nor was one behind a prefix:sudo gh release create,GH_TOKEN=x gh release delete, one inside a loop body or a subshell, andgh api …/releases -X DELETEon its own line. Under immutable releases agh release createburns that tag name permanently, so this was the wrong direction to be wrong in. The guard now splits the call into invocations and judges each on its own, anchored at the start of the invocation — so the words insideecho "never run gh release create v1.2.3"or inside a heredoc body stay words.
[0.12.2] - 2026-09-11
Fixed
- The tag guard read heredoc bodies as script. A heredoc body is data the
command writes, not commands it runs, so a file documenting
git tag -d vX.Y.Zwas judged as a deletion of that tag. The body reachedsplit_invocationsintact, its separators split it into segments, and a quoted example was then checked as a real invocation. Because a denied call runs none of its parts, the file was never written and re-running the same call was denied identically -- so the guard blocked the commit messages, docs and tests that quote its own examples, this repository's included. Bodies are now dropped before splitting; the opener line, the terminator and everything around them are still inspected, including the<<-indented and unquoted-delimiter forms. Dropping only happens where a body provably ends:<<is also an arithmetic left shift, so$(( FLAG << SHIFT ))looks exactly like an opener whose delimiter isSHIFT, and a here-string (<<<) looks like one whose delimiter is its word. Neither is ever terminated, and stripping on sight would have swallowed the rest of the command -- a real tag deletion on a later line included.
0.12.1 - 2026-09-09
Fixed
- The tag guard judged a whole command by its first
git tagoccurrence. It collapsed newlines into spaces and captured to the end of the command, so one match decided the verdict for everything after it — in both directions. A read-only listing was blocked when a later, unrelated line held a version token, and once that first match returned early on-l, a real tag creation, tag deletion or tag force-push further along the same command was never examined (#105). - Splitting the command applied separators wherever they appeared and ignored
grouping entirely. An invocation inside a subshell, a brace group or a command
substitution was invisible, while a separator inside a quoted argument split
one invocation into two and reported a commit message as a lightweight tag.
A
\"inside a double-quoted argument was read as the closing quote, which hid the invocation that followed (#112). - The guard blocked forms that create no lightweight tag: the read-only
inspection flags git treats as implying
--list(-n,--contains,--points-at,--merged,--sort,--format,--column,--ignore-case), and-m/-F, which imply-a. It allowed a quoted tag name, which creates exactly the tag the bare form creates.git tag --delete vX.Y.Zwas reported as a lightweight tag rather than as a deletion.
Changed
- The shipped TER callers (
templates/release-typo3.yml,templates/ter-publish.ymland the pattern inreferences/ter-republish.md) show how to passexclude-from-packaging, commented out with the reason beside it: the shared workflow fails the job when the path does not exist, so an active line would break every adopter without that exact file (#104). references/ter-republish.mdno longer names three specific extension repositories where a generic phrase carries the same meaning (#92).- The
netresearch/skill-repo-skillpre-commit hook moves to v2.0.1.