SporeStack Blog

Fatal Website Launcher Bug

From about 2026-04-18 20:45 to 2026-04-21 19:58 UTC, the website launcher (the index page on sporestack.com, the onion, or i2p) was broken to anyone who did not already have a token loaded in their Javascript localstorage.

As far as I know, this is now fixed. I am very thankful to the users who emailed me about this!

If you experience any issues on the web launcher and browser cache may be at play, use ctrl+shift+r to reload the page+dependencies. In the future I'd like to make all page dependencies versioned in some way, so that's not necessary and a standard reload will always do the job.

If there's another bug I missed, please let me know!

I'll update this post later with more explanation.

I am very sorry for the inconvenience.

Update with the bug

I made a number of changes around token generation. Traditionally, this has always been client side. It was also needlessly complicated, generating a random string plus a checksum. I simplified this by adding a API endpoint to get a suggested token. In the long run I imagine tokens being issued when an invoice is made, or something similar, but that's besides the point.

Existing tokens work fine, and you can generate tokens in the same manner. And the code did seem to work fine, but it added no real value.

With those changes, the old isValidToken() function was changed to this:

export function isValidToken(token) {
  if (token.length === 32) {
    return true;
  } else if (token.length === 64) {
    return true;
  } else {
    return false;
  }
}

Partly out of sheer oversight and partly out of my lack of Javascript experience, I didn't realize there was a situation where null could be passed there, and that Javascript would balk at the length property being used on null.

I tested these changes thoroughly... in my browser, which already had a token in localstorage. For anyone navigating to the website with any kind of private browsing or for the first time, the token passed would be null, and raise an exception killing the rest of Javascript on the page. Thus no token, no ability to manage servers, etc. A very major issue.

A friend of mine sent me a message about his token, saying the page didn't work. So I was curious and loaded the website through a different browser than I tested with, on my phone. And it was fine, because that browser already had a token in localstorage! I didn't see his subequent messages until after I saw emails reporting similar problems.

I foolishly did not test the site with a fresh browser. If I had, the bug would have been obvious.

Now, it's been fixed and the function looks like this:

export function isValidToken(token) {
  if (token === null) {
    return false;
  } else if (token.length === 32) {
    return true;
  } else if (token.length === 64) {
    return true;
  } else {
    return false;
  }
}

Far from eloquent, but it passes all of the checks I've put it through.

The logic is such that if there's a valid token, it uses it. If there's no valid token, like if there's no token in localstorage, it generates a new one through the new API endpoint.

Anyway, I hope this explains a bit about the bug. I'm so sorry about it. I need to make private mode browser testing part of my checks before shipping any web launcher changes.

Second update, another bug

This bug is quite bad as well. Maybe not as bad, but ridiculous nonetheless. And someone bit by the above would never get to the point of noticing this bug. It could've been caught with a basic real-world launch test, which I did not do. I didn't recall some old code that sort of made sense at one point, that no longer did and caused the bug. Completely my error.

Servers launched after the introduction of the above bug, silently did not appear in the web launcher. This is because at the same time I shortened the machine ID format. Because machine IDs are no longer global secrets, they don't have to be as "secure." Now they are time sorted and shorter. A long time ago, they were 64 character hex strings. Very recently, they were 32 character strings of mostly hex, but not just. Now they've shrunk to 21 characters. Like tokens, at one point machine IDs were generated client-side, but it's been only server side for quite some time.

I think that this diff explains most of it:

diff --git a/html/static/launcher.js b/html/static/launcher.js
index 3b7cdd6..3ec56cd 100644
--- a/html/static/launcher.js
+++ b/html/static/launcher.js
@@ -913,14 +913,6 @@ export async function refreshInfo(machineID) {
 }
 
 async function get_info_html(machineID) {
-  if (
-    typeof machineID !== "string" ||
-    (machineID.length !== 32 && machineID.length !== 64)
-  ) {
-    console.log("Invalid machineID found, skipping in get_info_html().");
-    return "";
-  }
-
   const token = document.getElementById("settlement_token").value;
   const info_response = await sporestackInfoFetch(token, machineID);
   if (info_response.status === 200) {
@@ -942,15 +934,6 @@ async function get_info_html(machineID) {
 
 function generate_server_info_inner_html(response) {
   const machineID = response["machine_id"];
-  if (
-    typeof machineID !== "string" ||
-    (machineID.length !== 32 && machineID.length !== 64)
-  ) {
-    console.log(
-      "Invalid machineID found, skipping in generate_server_info_html().",
-    );
-    return "";
-  }
 
   //let actions = "<div>";
   let actions = "";

I really have little excuse on this. Javascript localstorage or not, any launch would have triggered this bug. I just didn't anticipate these remnants in my old spaghetti code.

Another bug, while I'm at it

Very occasionally, Vultr servers would show up without IPv4 (on the SporeStack side.) At first I thought this was a Vultr change, but it didn't seem to be. This was very rare. I think I've fixed it now.

I never did hear complaints about this and it was rare, but it seemed to happen somewhat more often.

Impacted tokens/users

If you've been impacted by any of these bugs, please send a token message for a credit. I'm very sorry for my arrogance of not testing these changes more thoroughly.