Compare commits

...
2 Commits
Author SHA1 Message Date
JonKazama-Hellion a4c4e15c3b chore(release): 2.0.1
Forge Announce / Post changelog to Hellion Forge (push) Successful in 8s
Security Scan (reusable) / Security Scan (push) Failing after 24s
Security / scan (push) Failing after 24s
Build / Build (Release) (push) Successful in 34s
Release / Build and attach release ZIP (push) Successful in 30s
Same-day hotfix on 2.0.0, nothing user-facing. It exists because the 2.0.0
archive was built before the MessagePack lift, and because the fixed release
workflow needs a tag to prove itself on -- 2.0.0 cannot, since Gitea reads the
workflow from the tagged tree and that tree still holds the broken version.

Not force-moving the 2.0.0 tag: its release object exists with the archive
attached, and someone may already have pulled it.
2026-08-19 22:46:10 +02:00
JonKazama-Hellion 226174bf12 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.
2026-08-19 22:44:55 +02:00
8 changed files with 117 additions and 89 deletions
+56 -32
View File
@@ -22,9 +22,9 @@ on:
- 'v*'
# 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
# ref step below hard-fails if a non-tag ref is selected, because the
# release-action reads GITHUB_REF directly and rejects anything that
# does not start with refs/tags/.
# ref step below hard-fails if a non-tag ref is selected: the release
# name and body are both derived from the tag, so a branch ref would
# publish a release named after a branch.
workflow_dispatch:
permissions:
@@ -37,11 +37,8 @@ jobs:
timeout-minutes: 20
steps:
# release-action@main reads GITHUB_REF directly (its action.yml
# does not declare a tag_name input). Validate up-front so manual
# 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".
# Validate up-front so a manual dispatch from a branch ref fails loud
# here instead of burning a full build before the publish step notices.
- name: Validate tag ref
run: |
if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then
@@ -156,28 +153,55 @@ jobs:
Write-Host $body
Write-Host "----------------------------------------"
# release-action@main only declares files/title/body/pre_release/
# draft/api_key/insecure as inputs (see its action.yml). It silently
# ignores anything else, including body_path and tag_name. The tag
# itself comes from GITHUB_REF, the body must be passed inline via
# body:, so we re-emit release-body.md as a step output first.
- name: Expose release body for release-action
id: body
shell: bash
run: |
{
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.
# The tag comes from GITHUB_REF, the body from the step above. Posted with
# curl rather than gitea.com/actions/release-action, which declares
# `using: go` and has to be compiled by the runner -- act cannot do that
# here and the step dies with exec: "go": executable file not found, exit
# 127, after a build that otherwise succeeded. This runs as a plain shell
# step in the job image, which has curl and python3.
#
# Idempotent on purpose: a re-run against an existing release reuses it and
# replaces the asset instead of failing on the duplicate.
- name: Attach to Gitea release
uses: https://gitea.com/actions/release-action@main
with:
files: ${{ steps.locate.outputs.path }}
body: ${{ steps.body.outputs.content }}
api_key: ${{ secrets.GITHUB_TOKEN }}
shell: bash
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ZIP_PATH: ${{ steps.locate.outputs.path }}
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")'
+7
View File
@@ -0,0 +1,7 @@
---
subtitle: "Hotfix"
versionsnatur: "Hotfix ohne sichtbare Änderungen"
---
- **Nichts Neues zu sehen.** Wer 2.0.0 in der ersten Stunde gezogen hat, sollte trotzdem updaten: das 2.0.0-Archiv wurde ohne eine Abhängigkeits-Aktualisierung gebaut, die hier drin ist.
- **MessagePack von 3.1.4 auf 3.1.7.** Das Paket serialisiert die Nachrichten-Payloads in der lokalen Datenbank. Die Meldungen betreffen die Rekursionstiefe in `Skip` und einen Fehler in der LZ4-Dekomprimierung, beide nur über präparierte Eingaben erreichbar. Das Plugin schreibt und liest ausschließlich seine eigenen Bytes in einer lokalen Datei, praktisch bräuchte ein Angreifer also schon Schreibzugriff darauf. Trotzdem gehoben, weil es nichts kostet.
- **Der Release-Workflow hängt sein Archiv wieder selbst an.** Bei 2.0.0 lief der Build sauber durch und scheiterte dann am Veröffentlichen, weshalb dieses Release von Hand fertiggestellt werden musste.
+2 -2
View File
@@ -1,7 +1,7 @@
<Project Sdk="Dalamud.NET.Sdk/15.0.0">
<PropertyGroup>
<!-- Independent versioning; see yaml changelog for upstream Chat 2 base -->
<Version>2.0.0</Version>
<Version>2.0.1</Version>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Use lock file to pin exact versions -->
@@ -13,7 +13,7 @@
<ItemGroup>
<!-- 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" />
<!-- v1.5.0 DI-container foundation; matches Lightless pin (Hosting 10.0.7) -->
<PackageReference
+9 -38
View File
@@ -35,6 +35,15 @@ tags:
- Replacement
- Privacy
changelog: |-
**v2.0.1 — Hotfix (2026-08-19)**
A same-day follow-up to 2.0.0 with no user-facing changes. Install it if you picked up 2.0.0 in the first hour; there is nothing new to look at, it just carries a dependency update the 2.0.0 archive was built without.
- MessagePack raised from 3.1.4 to 3.1.7. It handles the payload serialisation behind the message database. The advisories are a recursion-depth limit in `Skip` and a fault in LZ4 decompression, both reachable only through crafted input — this plugin writes and reads its own bytes in a local file, so the practical exposure needs someone who already has write access to it. Lifted anyway, because it costs nothing.
- The release workflow publishes through the Gitea API directly. The 2.0.0 build succeeded and then failed to attach its own archive, which is why that release had to be completed by hand.
---
**v2.0.0 — Rebuilt, Repaired, Reset (2026-08-19)**
Nine development cycles in one release. Everything built as v1.6.0 through v1.15.0 ships here; those versions were never published on their own.
@@ -111,42 +120,4 @@ changelog: |-
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
---
**v1.5.4 — Polish and Motion (2026-05-20)**
A polish cycle: smoother theme switching, faster theme and tab
access, and subtle hover motion. Three P3 items plus an
accessibility toggle.
User-visible:
- Theme switches now crossfade smoothly over ~300 ms across every
Hellion-rendered surface — sidebar, title, buttons, tabs,
scrollbar, separators. The window background snaps deliberately
so the per-window opacity override from Dalamud's pinning menu
stays untouched.
- New header quick-picker: a palette button left of the cog opens
a compact popup with two sections — every built-in and custom
theme, and every tab. The active entry carries a check glyph;
clicking another switches without closing the popup.
- Sidebar icons ease their opacity on hover, and card-mode message
borders highlight per tab while the cursor is over their rows.
Framerate-independent, so a stalled Wine frame cannot overshoot
the animation.
- New "Reduce motion" toggle in Theme & Layout disables the
crossfade, the hover animations and the unread-tab pulse for
users who prefer a static UI.
Under the hood:
- Two pure-helper lerp paths (ThemeAbgrCacheLerp, FrameLerp) with
xUnit coverage in the Build Suite, plus a ColourUtil.ApplyAlpha
alpha modulator. Two new /xlperf self-test steps pin the
crossfade and quick-picker contracts.
No schema bump, no migration. Migration v17 stays.
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
Earlier history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases
+9 -9
View File
@@ -16,12 +16,12 @@
},
"MessagePack": {
"type": "Direct",
"requested": "[3.1.4, 4.0.0)",
"resolved": "3.1.4",
"contentHash": "BH0wlHWmVoZpbAPyyt2Awbq30C+ZsS3eHSkYdnyUAbqVJ22fAJDzn2xTieBeoT5QlcBzp61vHcv878YJGfi3mg==",
"requested": "[3.1.7, 4.0.0)",
"resolved": "3.1.7",
"contentHash": "gXpifaxbhfBqh8bVToQn9j+OgCnhXWMbRCV7RQIbCVa9CNJG1fY6oHAYYl+ER3Tb9uUIHiQ5QbGiGwwFhoagbA==",
"dependencies": {
"MessagePack.Annotations": "3.1.4",
"MessagePackAnalyzer": "3.1.4",
"MessagePack.Annotations": "3.1.7",
"MessagePackAnalyzer": "3.1.7",
"Microsoft.NET.StringTools": "17.11.4"
}
},
@@ -131,13 +131,13 @@
},
"MessagePack.Annotations": {
"type": "Transitive",
"resolved": "3.1.4",
"contentHash": "aVWrDAkCdqxwQsz/q0ldPh2EFn48M99YUzE9OvZjMq2RNLKz4o2z88iGFvSvbMqOWRweRvKPHBJZe22PRqzslQ=="
"resolved": "3.1.7",
"contentHash": "IW1yX9viarFl/Dsio5G+pM90JkOSW4xhAiJcKxPPF5rzyp2/yCpieWju8G99QLEmaQeze2uQMEskUBA59YZmkw=="
},
"MessagePackAnalyzer": {
"type": "Transitive",
"resolved": "3.1.4",
"contentHash": "CTaSsN/liJ7MhLCAB7Z4ZLBNuVGCq9lt2BT/cbrc9vzGv89yK3CqIA+z9T19a11eQYl9etZHL6MQJgCqECRVpg=="
"resolved": "3.1.7",
"contentHash": "ZF+OOJTRS7Ogzb4gG36hPKVQk3kEbp6kkUcdRjhddDq/uSGcV4BKIdYiyJipExXyE/zsqYb0ic215O9kI+fjPA=="
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
+2 -2
View File
@@ -2,7 +2,7 @@
[![Build](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/actions/workflows/build.yml/badge.svg?branch=main)](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/actions/workflows/build.yml)
[![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-blue.svg)](LICENSE)
[![Latest release](https://img.shields.io/badge/release-v2.0.0-brightgreen)](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/latest)
[![Latest release](https://img.shields.io/badge/release-v2.0.1-brightgreen)](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/latest)
[![Dalamud API](https://img.shields.io/badge/Dalamud-API_15-purple)](https://github.com/goatcorp/Dalamud)
[![.NET](https://img.shields.io/badge/.NET-10.0-512BD4)](https://dotnet.microsoft.com/)
[![FFXIV](https://img.shields.io/badge/FFXIV-Dawntrail-c3a37f)](https://www.finalfantasyxiv.com/)
@@ -11,7 +11,7 @@
<img src="docs/images/hellion-forge.png" alt="Hellion Forge" width="180" />
</p>
**Version 2.0.0** — Privacy-first chat plugin for FINAL FANTASY XIV / Dalamud, originally
**Version 2.0.1** — Privacy-first chat plugin for FINAL FANTASY XIV / Dalamud, originally
forked from [Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2).
Hellion Chat is a privacy-first plugin built on the Chat 2 foundation. The majority of the engine
+26
View File
@@ -11,6 +11,32 @@ releases as an overview and links to the release pages for details.
---
## [2.0.1] — 2026-08-19
A same-day hotfix on 2.0.0 with nothing user-facing in it.
### Changed
- MessagePack from 3.1.4 to 3.1.7. The range in the csproj already allowed it;
NuGet resolves the lower bound of a range and so does trivy, so the floor is
the version that actually ships. The advisories cover recursion depth in
`MessagePackReader.Skip` and a fault in LZ4 decompression — both need crafted
input, and this plugin serialises its own payloads into a local database and
reads its own bytes back. Practical exposure requires write access to that
file. Lifted because it is free and a scan left red for a known-harmless
reason is how a real finding gets missed later.
### Fixed
- The release workflow attaches its archive again. `gitea.com/actions/release-action`
declares `using: go` and has to be compiled by the runner, which act cannot do
here — the 2.0.0 build went green and then died on the publish step with
exit 127, leaving an announced release with no download. Publishing runs
through the Gitea API with curl now, and is idempotent so a recovery run
replaces the asset instead of tripping over it.
---
## [2.0.0] — 2026-08-19
Rebuilt, repaired, reset. Everything developed as v1.6.0 through v1.15.0 ships
+6 -6
View File
@@ -3,7 +3,7 @@
"Author": "Jon Kazama (Hellion Forge)",
"Name": "Hellion Chat",
"InternalName": "HellionChat",
"AssemblyVersion": "2.0.0.0",
"AssemblyVersion": "2.0.1.0",
"Description": "A Hellion Forge plugin — privacy-focused chat replacement for FINAL FANTASY XIV, built for EU, US and JP data rules.\n\nBy default only your own conversations are stored. Public chat, NPC dialogue, system messages and battle logs are discarded at the storage layer unless you opt in. Retention windows are configurable per channel, history can be wiped retroactively, and everything can be exported on demand.\n\nFeatures:\n- Channel whitelist with a Privacy-First default\n- Per-channel retention with a daily background sweep\n- Retroactive cleanup with preview and Ctrl+Shift confirm\n- Export to Markdown, JSON or CSV\n- First-run wizard with four profiles: Privacy-First, Casual, Roleplay, Full History\n- Multi-language UI (24 locales) with live language switching\n- Own config and database — no shared state with other plugins\n\nBased on Chat 2 by Infi and Anna (EUPL-1.2).\nSupport: https://discord.gg/X9V7Kcv5gR",
"ApplicableVersion": "any",
"RepoUrl": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat",
@@ -20,12 +20,12 @@
"CanUnloadAsync": false,
"LoadPriority": 0,
"Punchline": "A Hellion Forge plugin. Privacy-first chat for FFXIV, built to stay out of your way.",
"Changelog": "**v2.0.0 — Rebuilt, Repaired, Reset (2026-08-19)**\n\nNine development cycles in one release. Everything built as v1.6.0 through v1.15.0 ships here; those versions were never published on their own.\n\n**This update resets your settings.** Nine cycles of rebuilding left saved values pointing at surfaces that no longer exist, and starting over is the only way to be sure every install is on the same defaults. **Your message history is untouched** — it lives in a separate database. Your old settings are kept next to the config file as `HellionChat.json.pre-2.0.0.bak`.\n\nFixed, and several of these lost data or hid it:\n\n- Retroactive cleanup could never be applied at all. The preview took the database lock itself and that counted as a change, so every preview went stale the instant it finished and the apply button never appeared.\n- Compacting the database ran against an open reader and failed, after which the plugin reported that nothing had been deleted — while everything had.\n- Deleting messages left them in the search index, so search kept returning rows that were already gone.\n- Pinned tell tabs came up empty for a whole session: the history query ran at plugin start, before any character is logged in, and never tried again.\n- Export wrote invalid JSON where a setting value was involved, and a byte-order mark that strict parsers reject.\n- A pop-out with its title bar switched on could not be closed, and a tell from a popped-out partner hijacked the main window's active tab.\n- One third-party emote service started returning 403, and that response took all 65 working global emotes down with it on every start.\n- The GDPR notice for the full-history profile had been translated into 25 languages and shown nowhere since May.\n\nChanged, and one of these changes what gets stored:\n\n- **The channel grid is now authoritative.** Until now the unknown-channel failsafe was applied to known channels too, so a channel you had unticked was still being written while that failsafe was on. If that was you, this release stores less than before. Nothing already in the database is touched.\n- Every window is drawn by the plugin rather than by ImGui defaults, and they share one visual language: structure carried by typography, a surface on anything you can press, and colours measured against what sits behind them.\n- Typography has named roles — sender, body and timestamp mean the same thing everywhere, timestamps sit in their own column, system messages are italic.\n- Export, the tab editor, database maintenance and pinning had lost their entry points during the rebuild and are reachable again.\n- Screenshot mode reached one of four surfaces that draw a tab name. It reaches all four now.\n\nRemoved: six settings that had a control and a saved value but no reader anywhere in the plugin; a per-tab regex filter the game's own blackword filter covers; and a wizard checkbox that was collected, reported as applied and never read.\n\nNew: an Emote tab in the default layout, local and server clocks in the status bar, a screenshot mode reachable from the input row, `/hellion wizard` to reopen the setup wizard, a style lab under `/hellion lab`, and 25 UI languages with the settings window and wizard fully covered.\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2). The two codebases have diverged far enough that they no longer line up.",
"Changelog": "**v2.0.1 — Hotfix (2026-08-19)**\n\nA same-day follow-up to 2.0.0 with no user-facing changes. Install it if you picked up 2.0.0 in the first hour; there is nothing new to look at, it just carries a dependency update the 2.0.0 archive was built without.\n\n- MessagePack raised from 3.1.4 to 3.1.7. It handles the payload serialisation behind the message database. The advisories are a recursion-depth limit in `Skip` and a fault in LZ4 decompression, both reachable only through crafted input — this plugin writes and reads its own bytes in a local file, so the practical exposure needs someone who already has write access to it. Lifted anyway, because it costs nothing.\n- The release workflow publishes through the Gitea API directly. The 2.0.0 build succeeded and then failed to attach its own archive, which is why that release had to be completed by hand.",
"AcceptsFeedback": true,
"DownloadLinkInstall": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v2.0.0/latest.zip",
"DownloadLinkUpdate": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v2.0.0/latest.zip",
"DownloadLinkTesting": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v2.0.0/latest.zip",
"TestingAssemblyVersion": "2.0.0.0",
"DownloadLinkInstall": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v2.0.1/latest.zip",
"DownloadLinkUpdate": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v2.0.1/latest.zip",
"DownloadLinkTesting": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/download/v2.0.1/latest.zip",
"TestingAssemblyVersion": "2.0.1.0",
"IconUrl": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/icon.png",
"ImageUrls": [
"https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/chatWindow.png",