ci(release): publish with curl instead of a go action, and lift MessagePack
Security Scan (reusable) / Security Scan (push) Failing after 23s
Security / scan (push) Failing after 23s
Build / Build (Release) (push) Successful in 30s

The v2.0.0 tag built fine and then died on its last step: gitea.com's
release-action declares `using: go`, the runner has to compile it, and act
cannot -- exec: "go": executable file not found, exit 127, after a green build.
The zip existed and never got attached, so the Discord announcement went out
while the download link pointed at nothing.

This is a known failure. It was diagnosed on another repo in June and the note
from then says in as many words that this repo carries the same pattern and
should migrate before its next release. It did not, and here we are.

The publish step is a plain curl call against the Gitea API now, running in the
job image with curl and python3, independent of go, the action cache, and
whatever @main happens to point at. Idempotent by design: a re-run reuses an
existing release and replaces the asset rather than failing on the duplicate,
which is exactly the state a recovery run finds.

MessagePack moves from the 3.1.4 floor to 3.1.7, which is what the trivy scan
was failing on. The range already allowed it -- NuGet resolves the lower bound
of a range, and trivy reads it the same way, so the floor is the version that
counts. The advisories are recursion depth in Skip and an LZ4 decompression
fault, both reachable only through crafted input; this plugin serialises its own
payloads and reads back its own bytes from a local database, so the practical
exposure is someone who already has write access to the file. Lifted because it
costs nothing and a scan that stays red for a known-harmless reason is how a real
finding gets missed later.
This commit is contained in:
2026-08-19 22:44:55 +02:00
parent e316a9b400
commit 226174bf12
3 changed files with 66 additions and 42 deletions
+56 -32
View File
@@ -22,9 +22,9 @@ on:
- 'v*' - 'v*'
# Manual recovery trigger. Use Gitea's "Run workflow" UI and select the # Manual recovery trigger. Use Gitea's "Run workflow" UI and select the
# tag (e.g. v1.4.4) from the Ref dropdown - not main. The Validate tag # tag (e.g. v1.4.4) from the Ref dropdown - not main. The Validate tag
# ref step below hard-fails if a non-tag ref is selected, because the # ref step below hard-fails if a non-tag ref is selected: the release
# release-action reads GITHUB_REF directly and rejects anything that # name and body are both derived from the tag, so a branch ref would
# does not start with refs/tags/. # publish a release named after a branch.
workflow_dispatch: workflow_dispatch:
permissions: permissions:
@@ -37,11 +37,8 @@ jobs:
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
# release-action@main reads GITHUB_REF directly (its action.yml # Validate up-front so a manual dispatch from a branch ref fails loud
# does not declare a tag_name input). Validate up-front so manual # here instead of burning a full build before the publish step notices.
# dispatches from a branch ref fail loud here instead of burning
# a full build before the final step errors out with "ref X is
# not a tag".
- name: Validate tag ref - name: Validate tag ref
run: | run: |
if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then
@@ -156,28 +153,55 @@ jobs:
Write-Host $body Write-Host $body
Write-Host "----------------------------------------" Write-Host "----------------------------------------"
# release-action@main only declares files/title/body/pre_release/ # The tag comes from GITHUB_REF, the body from the step above. Posted with
# draft/api_key/insecure as inputs (see its action.yml). It silently # curl rather than gitea.com/actions/release-action, which declares
# ignores anything else, including body_path and tag_name. The tag # `using: go` and has to be compiled by the runner -- act cannot do that
# itself comes from GITHUB_REF, the body must be passed inline via # here and the step dies with exec: "go": executable file not found, exit
# body:, so we re-emit release-body.md as a step output first. # 127, after a build that otherwise succeeded. This runs as a plain shell
- name: Expose release body for release-action # step in the job image, which has curl and python3.
id: body #
shell: bash # Idempotent on purpose: a re-run against an existing release reuses it and
run: | # replaces the asset instead of failing on the duplicate.
{
echo 'content<<RELEASE_BODY_EOF'
cat release-body.md
echo 'RELEASE_BODY_EOF'
} >> "$GITHUB_OUTPUT"
# Gitea-native release action. Creates the release if the tag has no
# release yet, or updates the existing one with latest.zip attached
# and the generated body. The auto-injected GITHUB_TOKEN on Gitea
# Actions has Gitea-API scope and is sufficient for release write.
- name: Attach to Gitea release - name: Attach to Gitea release
uses: https://gitea.com/actions/release-action@main shell: bash
with: env:
files: ${{ steps.locate.outputs.path }} GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
body: ${{ steps.body.outputs.content }} ZIP_PATH: ${{ steps.locate.outputs.path }}
api_key: ${{ secrets.GITHUB_TOKEN }} TAG_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}"
auth="Authorization: token ${GITEA_TOKEN}"
# Existing release for this tag, or create one.
rel_id="$(curl -sf -H "$auth" "$api/releases/tags/${TAG_NAME}" \
| python3 -c 'import sys,json; print(json.load(sys.stdin).get("id",""))' 2>/dev/null || true)"
if [ -z "$rel_id" ]; then
payload="$(python3 -c '
import json, os, sys
body = open("release-body.md", encoding="utf-8").read()
json.dump({"tag_name": os.environ["TAG_NAME"], "name": os.environ["TAG_NAME"],
"body": body, "draft": False, "prerelease": False}, sys.stdout)
')"
rel_id="$(printf '%s' "$payload" \
| curl -sf -X POST -H "$auth" -H "Content-Type: application/json" -d @- "$api/releases" \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')"
echo "Created release $rel_id for ${TAG_NAME}"
else
echo "Reusing release $rel_id for ${TAG_NAME}"
fi
# Drop a same-named asset from an earlier attempt, or the upload 409s.
old_id="$(curl -sf -H "$auth" "$api/releases/${rel_id}/assets" \
| python3 -c 'import sys,json; print(next((a["id"] for a in json.load(sys.stdin) if a["name"]=="latest.zip"), ""))' 2>/dev/null || true)"
if [ -n "$old_id" ]; then
curl -sf -X DELETE -H "$auth" "$api/releases/${rel_id}/assets/${old_id}"
echo "Replaced existing latest.zip (asset $old_id)"
fi
curl -sf -X POST -H "$auth" \
-F "attachment=@${ZIP_PATH};filename=latest.zip" \
"$api/releases/${rel_id}/assets?name=latest.zip" \
| python3 -c 'import sys,json; a=json.load(sys.stdin); print("Attached", a["name"], a["size"], "bytes")'
+1 -1
View File
@@ -13,7 +13,7 @@
<ItemGroup> <ItemGroup>
<!-- Closed ranges prevent surprise major bumps during lock file regeneration --> <!-- Closed ranges prevent surprise major bumps during lock file regeneration -->
<PackageReference Include="MessagePack" Version="[3.1.4, 4.0.0)" /> <PackageReference Include="MessagePack" Version="[3.1.7, 4.0.0)" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.8" /> <PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.8" />
<!-- v1.5.0 DI-container foundation; matches Lightless pin (Hosting 10.0.7) --> <!-- v1.5.0 DI-container foundation; matches Lightless pin (Hosting 10.0.7) -->
<PackageReference <PackageReference
+9 -9
View File
@@ -16,12 +16,12 @@
}, },
"MessagePack": { "MessagePack": {
"type": "Direct", "type": "Direct",
"requested": "[3.1.4, 4.0.0)", "requested": "[3.1.7, 4.0.0)",
"resolved": "3.1.4", "resolved": "3.1.7",
"contentHash": "BH0wlHWmVoZpbAPyyt2Awbq30C+ZsS3eHSkYdnyUAbqVJ22fAJDzn2xTieBeoT5QlcBzp61vHcv878YJGfi3mg==", "contentHash": "gXpifaxbhfBqh8bVToQn9j+OgCnhXWMbRCV7RQIbCVa9CNJG1fY6oHAYYl+ER3Tb9uUIHiQ5QbGiGwwFhoagbA==",
"dependencies": { "dependencies": {
"MessagePack.Annotations": "3.1.4", "MessagePack.Annotations": "3.1.7",
"MessagePackAnalyzer": "3.1.4", "MessagePackAnalyzer": "3.1.7",
"Microsoft.NET.StringTools": "17.11.4" "Microsoft.NET.StringTools": "17.11.4"
} }
}, },
@@ -131,13 +131,13 @@
}, },
"MessagePack.Annotations": { "MessagePack.Annotations": {
"type": "Transitive", "type": "Transitive",
"resolved": "3.1.4", "resolved": "3.1.7",
"contentHash": "aVWrDAkCdqxwQsz/q0ldPh2EFn48M99YUzE9OvZjMq2RNLKz4o2z88iGFvSvbMqOWRweRvKPHBJZe22PRqzslQ==" "contentHash": "IW1yX9viarFl/Dsio5G+pM90JkOSW4xhAiJcKxPPF5rzyp2/yCpieWju8G99QLEmaQeze2uQMEskUBA59YZmkw=="
}, },
"MessagePackAnalyzer": { "MessagePackAnalyzer": {
"type": "Transitive", "type": "Transitive",
"resolved": "3.1.4", "resolved": "3.1.7",
"contentHash": "CTaSsN/liJ7MhLCAB7Z4ZLBNuVGCq9lt2BT/cbrc9vzGv89yK3CqIA+z9T19a11eQYl9etZHL6MQJgCqECRVpg==" "contentHash": "ZF+OOJTRS7Ogzb4gG36hPKVQk3kEbp6kkUcdRjhddDq/uSGcV4BKIdYiyJipExXyE/zsqYb0ic215O9kI+fjPA=="
}, },
"Microsoft.Data.Sqlite.Core": { "Microsoft.Data.Sqlite.Core": {
"type": "Transitive", "type": "Transitive",