epicness - Home - Blog

Head in the Clouds, Code in the Ground: A Code Review of Skymu

Posted on February 21, 2026 - Word count: ~3100


UPDATE: I have personally spoken with perfidious, one of the Skymu developers, after the publication of this post. He has made a solid effort to rectify some of the issues stated in this article and allowed me to do a more thorough review with actual source code. I will provide an "updated" review later on. This article will stay as-is for preservation purposes.

Note: I do not condone any harassment of any kind towards Skymu or its developers or members. This piece is intended as a critique towards Skymu's practices and development style, in a "brutally honest" manner. I am open to being contacted with any questions by the Skymu team and would be willing to provide advice.

I've been a long-time connossieur of old technology of all kinds. Whether it be the Nintendo Entertainment System and copy of Super Mario Bros. + Duck Hunt that I bought in 2019 in hopes of becoming a big shot in the speedrunning community, the Windows CE PDA gifted to me by a member of the CrossTalk Discord simply because of the H in SuperH, or the iPhone 4S I bought out of a longing curiosity of the old iOS ecosystem from my friends in the circle - and many things in between - I've been part of the retro community for a while. In fact, pulling up the earliest backup I have of any computer I've ever owned, an HP all-in-one from 2014 running Windows 8.1, there's a video of 8-year-old me trying to brute-force a 16-bit Windows educational app from a CD I begged my elementary school teacher to give me into running on a 64-bit Windows VM. Fun times.

If there's another thing I'm not a stranger to, it's project management and development - both on the good and the bad side. For example, I once helped run a project called the PC Recovery Archive, which started off well, then crumbled due to astonishingly bad levels of mismanagement from the level above me. I've also watched communities grow immensely - some of which handle the growth well, but many don't. On the development side, I've tinkered with many codebases - even those of billion-dollar companies' software products - that have code that makes me wonder, what the hell were they thinking? Though, I understand - I look back at my code from years ago and I'm like, what the hell was I thinking?

But, I digress. This post isn't about my life story - it's about a project I saw around the Internet, called Skymu. Fundamentally, it's just a one-for-one clone of Skype's user interface, but designed to work with modern (and not-so-modern) protocols and to be easily extensible. And, when I mean a one-for-one clone of Skype - I mean one-for-one. As in, the application brands itself as Skype by default.


Left: Skype 5.5, courtesy of Tom Keating/TMCnet / Right: Screenshot of Skymu in "stub plugin" mode

I hope I don't have to remind you of cases such as the NINA cease and desist to tell you how companies can and will go after you if it could even be assumed with a sprinkle of possibility that you are endorsed by the original copyright holder. But to straight up copy the interface one-for-one, and brand it as Skype by default on top of that, is pretty ballsy.

The truth is - I would've normally just wrote this off as "yet another project riding the Frutiger Aero wave". But this project saw hundreds of users flocking to it, drama took place that nearly spelled the end of the project, and the project developers (minus one that left) reportedly decided to take development easy. Another thing that ticked me off is the lead developer complaining in a private Discord server for someone else's instant messaging software (not retro-related at all) that the developer upgraded the .NET runtime version used - because seemingly, all developers have an inherent obligation to support Windows 7 - and recommending things that would certainly require an entire refactor of the person's codebase. All of this combined got me a little curious - and let's just say, we're in for a ride.

Versioning and Codenames

Part of me wonders how one can even manage to mess up a versioning system - though, it happened here, somehow.

So, ordinarily, one would use a typical version format, like maybe 1.3.3 - if you subscribe to the thought process of semantic versioning, this makes it pretty easy to determine what's a big upgrade, what's a small update, and what's a patch. And, hell, even the average Joe can tell you a bigger number is a newer version nowadays. Maybe, if you want to add a little marketing twang to your releases, you give each major version a fancy codename - like macOS Tahoe, or Android KitKat, to name a few.

What you don't do, is maintain a major and minor/patch codename at the same time, mash them together for each release, and make them the primary public identifier, creating such version identifier monstrosities as Breithorn Oreo Waffle Cake. Ah, yeah, because I can surely determine that's supposed to be an update to... ahem... Breithorn Nutella Waffle.

To be fair, it seems like wacky naming conventions are not abnormal in the Skymu project: the beta testers team is called the Beta Application Scouting and Experimentation Division (BASED, for short) - in which he seems to always have to clarify that he means beta testers. What's the point in a fancy acronym or team name if it doesn't get the point across?

I can only imagine the levels of confusion that users tend to have with overzealous codenames and acronyms, especially when presented directly to the public, and especially as the primary form of version communication. They still end up using version identifiers anyways for GitHub purposes, so I wager they should just switch to using those instead.

The Near-Crisis

Note: No active security vulnerabilities are described in this article, nor have I identified any. That being said, there are plenty of bad coding patterns that may cause issues later on, especially as the codebase gains new features.

I figured, to get a look under the hood at this wonderful code, I'd throw the Skymu executable into ILSpy. PDBs ended up getting packaged with each release, though these are a little less important in .NET-land (though they give some useful information). Everything seems to be built in Debug mode instead of Release mode - maybe a bit slower, not a good practice, but I'll write it off as a rookie mistake.

There was a lot of logic in the MainWindow.cs file, a 1700-line (including generated code) behemoth. I was looking through this, however, and something caught my eye:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private async Task RunWormholeSendAsync(string filePath)
{
	ProcessStartInfo psi = new ProcessStartInfo
	{
		FileName = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "python", "python.exe"),
		Arguments = "-u -m wormhole send \"" + filePath + "\"",
		UseShellExecute = false,
		CreateNoWindow = true,
		RedirectStandardOutput = true,
		RedirectStandardError = true
	};
	Process process = new Process
	{
		StartInfo = psi
	};
	StringBuilder output = new StringBuilder();
	process.ErrorDataReceived += delegate(object s, DataReceivedEventArgs e)
	{
		if (!string.IsNullOrEmpty(e.Data))
		{
			output.AppendLine(e.Data);
			if (e.Data.StartsWith("wormhole receive "))
			{
				((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
				{
					SendMessage("&SKYMU-START&TransferWormhole," + e.Data.Substring("wormhole receive ".Length) + "&SKYMU-END&");
				});
			}
			else if (e.Data.Contains("TransferError"))
			{
				((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
				{
					Universal.ShowMsg(e.Data, "File transfer error");
				});
			}
			else if (e.Data.Contains("Transfer complete"))
			{
				((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
				{
					Universal.ShowMsg("The file was transferred successfully.", "File transfer complete");
				});
			}
		}
	};
	process.Start();
	process.BeginOutputReadLine();
	process.BeginErrorReadLine();
	await process.WaitForExitAsync();
}

If you don't have a clue what in the world is going on here - allow me to explain:

Luckily (and maybe someone had some sense, or maybe this was an abandoned feature, who knows) - this feature was never surfaced, and there's no interpretation logic for this message as of the latest Skymu version (I'm not even gonna try because they've probably chosen a new sweet by the time I upload this blogpost.) But - this was already pretty close to disaster, right?

A Lesson in Rickrolling your Friends

Later, while looking through message handling code, something else caught my eye: the AddTextOrLinkOrClickable function. Because, truthfully, any function that tries to juggle 3 things at once sounds fun to me as a security researcher.

This function is 100-something lines and does the following:

Now, I'm not gonna lie - the use of ShellExecute caught me off guard for a bit - but this is apparently the recommended way to do this, even per some of Microsoft's own documentation, and alongside the prefix checks it seems to have no security holes in it.

Many platforms have a "warning" when link text is enabled, as well as a filter to prevent you from overriding link text with another link - because it can be used for phishing, among other things. However, Skymu takes it with no problem, allowing you to tell someone Hey, look at this cool thing: [https://en.wikipedia.org/wiki/8086](https://youtube.com/watch?v=dQw4w9WgXcQ) - congrats, you've (hopefully) succesfully pulled off a Rickroll on the other side.

A Crisis in the Making

The "clickable" handling also concerned me. As stated earlier, these are basically anything that the protocol plugin wants the user to be able to click on. The protocol plugin defines the following:

1
2
3
4
5
6
public class ClickableConfiguration
{
	public string DelimiterLeft { get; set; }
	public string DelimiterRight { get; set; }
	public ClickableItemType Type { get; set; }
}

where the ClickableItemType is any of User, Server, ServerRole, ServerChannel, or GroupChat.

What immediately intrigued me is that it only had a filter for left and right delimiters - what was actually between the delimiters didn't matter at all. This means, if, for example, my plugin expected a clickable in the form of [[123456789]], I have no way to specify that - I could only specify that it must start with [[ and end with ]].

Now, considering the clickables don't even have handlers yet, this doesn't really matter. But, I'd think, eventually, these would actually have handlers, say, to open up someone's profile when you click on their name, or to go to a channel when you click on it. Allow me to describe a possible future scenario:

This "weird request" could also utilize path traversal within a GET request (if there's an impactful enough endpoint), among other things - but, in the end, the issue is that the code would potentially be able to do something it wasn't able to due to extremely lax parsing.

They use a lot of monstrous regexes in this codebase - this is probably the one place where a regex would've actually made sense. Unless you're planning on nesting clickables for some reason, this would've done the job.

A Discussion on Language Grammars (and why you shouldn't parse Markdown with regex)

Some of you may remember this infamous Stack Overflow post about parsing HTML with regex. In the Chomsky hierarchy, which is pretty much a fancy way of saying "how complex is this language", regular expressions can only define a Type 3 (regular) grammar - hence the name regular expression - and, importantly, Type 3 grammars cannot handle nesting. HTML, on the other hand, is a Type 2 (context-free) grammar - these can contain nesting. Therefore, you cannot create a competent HTML parser with a regex.

Markdown is pretty similar - it may not be as complex as HTML, but you can still have nested expressions - for example, _**underlined, bold text**_, or # *formatted text* within a **heading**, or a number of other examples. Needless to say, if bobince from that Stack Overflow post hasn't taken up farming by now after fending off the millionth person trying to parse HTML with regex, he might go similarly insane over this monstrous regex I'm about to show you, with the purpose of parsing Markdown:

(```)(.+?)\1|(`)(.+?)\3|(\*\*\*)(.+?)\5|(\*\*)(.+?)\7|(__)(.+?)\9|(\*|_)(.+?)\11|~~(.+?)~~|(?m)^(?:\*|-)\s+(.+)|(?m)^>\s+(.+)|(?m)^(#{1,6})\s+(.+)|(?m)^\-#\s+(.+)

Some of you who have gone insane over regex before may notice something besides the fact that it's absolutely asinine to try and parse a Type 2 language with a Type 3 grammar: the usage of backreferences on static strings. If we isolate (```)(.+?)\1, for example, you can see it backreferences the Markdown code block indicator ```, which is always going to match the same no matter what because it's statically defined.

Effectively, not only are they trying to do the impossible, they're using notoriously slow regex features to do it, entirely unnecessarily.

Now, you may ask: how does this break things? Let's make a mini test suite, where Discord and Skymu are our test platforms, and plan our hypotheses:

As expected, all 3 work on Discord:


Skymu, on the other hand, also performs as expected - it passes test case 1, but 2 and 3 do not format properly, ignoring the nested operation entirely:


This is because regular expressions, which define a Type 3 grammar that cannot handle nesting, are entirely incapable of parsing a Type 2 language that can nest.

A Bit of Shade

We'll take a bit of detour from the programming side of things - to draw attention to something I found in the resources. So far, this code isn't looking too great, with multiple programming disasters, including abandoned code that had the potential to lead to a zero-click remote code execution. So, it definitely baffled me when I found a "diss" on a similar project, reportedly branded as SeanKype, hidden in the resources/dark/buttons folder. It was a "deep fried" image, I presume meant to be a developer in-joke, but amazingly, the original PSD file used to throw this meme together is also in the same folder - so I exported a "clean" copy of the image from Affinity Designer for your viewing pleasure:


From what I can tell, SeanKype seems to be another project from the same creator - perhaps, the "prequel" to Skymu. Perhaps, a bit of recognition of self-improvement? This image was roughly produced in May of 2025, based on the PSD metadata. I think there's still a bit of work to do, but I don't know.

Reportedly, there was also recent drama over the API implementations, where the creator of the project, persfidious, got angry over his API implementation not being favored by another developer who had also put forward his own logo. My immediate thought process is - if you're going to try and force the team's hand on using your work, why even give people the option to propose their own work in the first place? Maybe this kinda helps to explain the almost egotistical nature of this meme embedded within the Skymu application.

The Protocol Plugins

We've spent a decent amount of time looking at the base client - now, let's look at the plugins.

There actually isn't much to write home about in some of these. I will admit - the Discord plugin seems pretty solid from my review of the code. Matrix, bafflingly, doesn't even implement the protocol - it uses something called Beeper to abstract it all away. There's a Skype database viewer that doesn't have much to write about, and there's supposed to be an implementation of the MSNP protocol for MSN/Windows Live Messenger, but it's not available yet. So, I focused on the XMPP plugin. Oh boy.

Nearly immediately, I was flashbanged with XMPPClient.ProcessXmlBuffer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
private void ProcessXmlBuffer(StringBuilder xmlBuffer)
{
	string xmlString = xmlBuffer.ToString();
	try
	{
		if (xmlString.Contains("<message") && xmlString.Contains("</message>"))
		{
			int startIndex = xmlString.IndexOf("<message");
			int endIndex = xmlString.IndexOf("</message>") + "</message>".Length;
			if (startIndex >= 0 && endIndex > startIndex)
			{
				string messageStanza = xmlString.Substring(startIndex, endIndex - startIndex);
				ProcessMessageStanza(messageStanza);
				xmlBuffer.Remove(startIndex, endIndex - startIndex);
			}
		}
		if (xmlString.Contains("<presence") && xmlString.Contains("</presence>"))
		{
			int startIndex2 = xmlString.IndexOf("<presence");
			int endIndex2 = xmlString.IndexOf("</presence>") + "</presence>".Length;
			if (startIndex2 >= 0 && endIndex2 > startIndex2)
			{
				string presenceStanza = xmlString.Substring(startIndex2, endIndex2 - startIndex2);
				ProcessPresenceStanza(presenceStanza);
				xmlBuffer.Remove(startIndex2, endIndex2 - startIndex2);
			}
		}
		if (xmlString.Contains("<iq") && xmlString.Contains("</iq>"))
		{
			int startIndex3 = xmlString.IndexOf("<iq");
			int endIndex3 = xmlString.IndexOf("</iq>") + "</iq>".Length;
			if (startIndex3 >= 0 && endIndex3 > startIndex3)
			{
				string iqStanza = xmlString.Substring(startIndex3, endIndex3 - startIndex3);
				ProcessIqStanza(iqStanza);
				xmlBuffer.Remove(startIndex3, endIndex3 - startIndex3);
			}
		}
		if (xmlBuffer.Length > 50000)
		{
			xmlBuffer.Clear();
		}
	}
	catch (Exception ex)
	{
		this.OnError?.Invoke(this, "XML processing error: " + ex.Message);
	}
}

The "stanza" processing functions actually use proper XML parsing through XDocument.Parse - but, for some reason, the base XML parsing function does some weird index and substring stuff.

This immediately breaks the moment any tag starting with message, presence, or iq that isn't one of those three comes over the wire - and when we're dealing with a super-extensible protocol like XMPP, this doesn't seem too unlikely. Not to mention, I'd presume it's not too hard to "smuggle" in fake opening/closing tags into parameters and break the protocol even more, or worse, smuggle an entire *message* into a parameter.

I was going to try to actually find a way to "proof-of-concept" this, but I couldn't even get the thing to log in to an ejabberd instance I set up, even after changing multiple configuration settings. So, we'll save that for another day, I guess.

Conclusion

Overall, my personal opinion is that this project is an ironclad example of a great project in concept mangled by immaturity, mismanagement, and poor development practices. Coding patterns teetering on the edge of security nightmares, indecipherable versioning systems, meaningless acronyms for the hell of it, and throwing shade at both yourself, your own developers, and other developers in the arena at the same time, the open-shut copyright violation from the carbon-copy Skype UI implementation, publicly shaming your developers for not doing things your way - it all leads to the perfect storm of a disastrous project.

This is all I can hope for, as someone who's seen many projects in this arena die out before:

This isn't a personal attack on the developers - moreso the project and the methods of management and administration that I've seen. I'm voicing my opinion - and I'm willing to speak with the Skymu team if they have any questions as to how they can improve.

I'd like to revisit this some day - especially if my recommendations get taken into account - and see what's changed and what hasn't.