Update 2026-07-09 (v0.4.20 to v0.4.22): the diagnostic upload now reads arbitrary addon files, and your error logs get attached
Three more Companion builds shipped since the last update: 0.4.20, 0.4.21, and 0.4.22. The detection, account-flag, and ed25519-signed file-modification engine documented for 0.4.15/0.4.16 are unchanged at every marker. Two of these builds widened what the app can read off your disk and send to GSE’s server. I also ran the current build under a decrypted capture; the new paths did not fire in that window, and I say so plainly below. As always: I am a competitor, so verify it yourself. Every claim is a line you can read in the shipped file.
Build hashes are in hashes.txt. The installed 0.4.22 app.asar I read is byte-identical to the installer extract, so this is the code that runs.
What changed in each build
-
0.4.20 is the substantive one. The unsigned diagnostic-upload function now takes a server-supplied list of file paths and reads arbitrary files under your WoW
Interface\AddOns\andWTF\folders, then POSTs their content toapi.gse.tools/diagnostic/upload. It reads any file (no file-type limit), caps each at 4 MB, refuses paths containing.., and resolves symlinks to keep the read inside those two folders. It is triggered by a server push on the event stream (acompanion:requestmessage), it needs only that you are signed in, and it is not signed. YourWTF\folder holds every addon’s SavedVariables, so the scope includesGRIP-EMS.lua. The server can ask for it by name. -
0.4.21 is a sync-notification dedup and nothing else.
-
0.4.22 adds a step to the gather that runs on every diagnostic upload: it reads your BugGrabber and BugSack SavedVariables (the two addons that record Lua errors) and attaches their content. Those logs collect errors from every addon you run, so the attachment can contain error text and state from other addons.
The signed engine, stated plainly
The ed25519-signed, server-pushed engine can read, delete entries from, change entries in, and rewrite any .lua file under your WTF\ folder. That is any addon’s SavedVariables, not only GSE’s. It runs only if the message carries a valid GSE signature, matches your account, has not expired, and WoW is closed. It is not gated by the enforce flag. That flag only retired the older fixed deletion routine and greys out buttons in the app; a validly signed plan runs whether enforce is true or false. It cannot write or delete addon source files, because its scope is .lua files under WTF\ only.
What I ran, and what I did not find
I installed 0.4.22, signed in, with GRIP-EMS present on disk and WoW closed, and watched it for about 30 minutes under a decrypted TLS capture, including two manual syncs I triggered myself. In that window the app did only GSE content sync. No companion:request directive arrived. Nothing read or wrote a file outside GSE’s own data. No diagnostic upload fired. The enforce flag read false the whole time, both for my signed-in account and anonymously. No .svmnt.tmp file (the signature the rewrite routine leaves) ever appeared, and my GRIP-EMS.lua was byte-for-byte unchanged from before.
So the capabilities are in the shipped code, and I did not catch them being used against me in that window. Both halves are true and I am stating both. A 30-minute capture is a window, not a proof of a negative; the signed engine is armed whenever GSE chooses to send a signed plan.
Two more limits, for honesty: the event-stream body itself was not rendered by the proxy (it does not buffer streamed responses), so I am inferring “no directive” from the absence of the file writes and uploads a directive would cause, not from reading the stream. And the four GRIP-EMS identifiers are absent from all three builds in every encoding I tried (18 encodings plus split-string checks), so the detection target is supplied by the server, not written in the client.
Verify it yourself
- Download the build from gse.tools/releases and open
%LOCALAPPDATA%\Programs\gse-companion\resources\app.asar(extract withnpx @electron/asar extract app.asar out, or read it with a text editor). - In
out/main/index.js, read these four things:- the capture engine: search
capture-denied; the roots function returnsInterface/AddOnsandWTF; the reader caps files at4 * 1024 * 1024and reads them as text with no type check. - the trigger: search
companion:request; the branch with notaskand noidxpasseskindsandpathsto the unsigned upload. - the signed engine scope: search for the writer that writes a
.svmnt.tmpfile and renames it over the target, and the scope check that requires the path to end in.luaand sit underWTF. - the error-log gather: search the regex
/^!?Bug(Grabber|Sack)\.lua$/and theerrorLog:entries it produces.
- the capture engine: search
- Check the live flag:
curl https://api.gse.tools/settings/access-policy.
Files in this update
hashes.txtgains the 0.4.20, 0.4.21, and 0.4.22 installer,app.asar, andout/main/index.jshashes.- The verbatim
out/main/index.jsof 0.4.22 (the code quoted here) is the same file you extract in step 1; its SHA-256 is inhashes.txt.
Scope
This documents behaviour in distributed software, from quoted code and a runtime capture. It is not a claim about anyone’s character, and it does not cover community moderation or account bans. Read the file and reach your own conclusion.