Skip to content

Web Apps Denial of Service

Last week we got a report of a critical bug affecting Xojo Web apps. A malformed percent-encoded URL was enough to crash the web app server. Something as small as ?x=% in a query string was all it took. We shipped a fix in 2026r1.2, and Xojo Cloud has been patched at the platform level to cover users who can’t upgrade right away. Here’s what happened, what we did about it, and what you should do depending on how your apps are deployed.

Am I affected? What do I do?

If you only read one section, read this one.

  • On Xojo Cloud (Web 1 or Web 2)? You’re covered. Nothing to do.
  • Using Lifeboat? Update Lifeboat and redeploy.
  • Self-hosted behind Apache or Nginx? Drop in the filtering rule below. If you’re on Web 2, also upgrade to 2026r1.2 if possible.
  • Self-hosted with the Xojo app exposed directly? On Web 2, upgrade to 2026r1.2 if possible, otherwise put a reverse proxy in front. On Web 1, put a reverse proxy in front (which is what we’d recommend either way).

The full details follow.

Timeline

  • Thursday, the 23rd. A public issue and a private message landed on the same day, both pointing at the same bug.
  • Thursday through Monday. We tracked down the root cause, wrote the fix, and tested it.
  • Tuesday, the 28th. 2026r1.2 shipped with the patch.
  • Wednesday, the 29th. Xojo Cloud patched at the platform level.

Under a week from the report to a patched release, with the platform-level mitigation following the next day.

What Happened

A request with a malformed percent-encoded sequence in a query parameter could crash a running Xojo Web app. The smallest reproducer is something like ?x=%. That’s a % not followed by two hex digits, which is invalid percent-encoding and isn’t decodable.

The bug lives in DecodeURLComponent. When the method ran into invalid input, it raised an exception instead of handling the input gracefully. Because the framework calls this method while parsing incoming requests, anyone could crash a web app by sending a single malformed URL. No authentication, no special headers, no payload. Just a bad query string.

A few things worth being upfront about:

  • The bug has probably been there since DecodeURLComponent was introduced. Every Xojo release that ships the method might be affected.
  • Both Web 1 and Web 2 are known to be affected. The Web framework has been calling this method on incoming requests across both generations.
  • It’s not just a Web problem. DecodeURLComponent is a general-purpose method, and the same crash can happen in Desktop and other project types if you call it on attacker-controlled input. Web is the obvious target because requests arrive from the network, but the underlying issue isn’t Web-specific.

The Fix

In 2026r1.2, DecodeURLComponent no longer crashes on invalid input. Instead, it returns an empty String when the input contains a malformed percent-encoded sequence.

We thought about a few approaches and went with this one on purpose, for being consistent with the other sanity checks we do on this method. An empty return value lets existing code keep running rather than letting an exception bubble up through request handling, which is what you want from a method that’s frequently called on untrusted input. If your code already wraps DecodeURLComponent with its own validation, the new behavior gives you a clean signal to act on (an empty result) instead of an exception to catch.

Xojo 2026r1.2 is the only release with the fix. Web 1 won’t be getting a backported patch. What that means in practice is covered below.

What Xojo has already done for you

Xojo Cloud has been patched at the platform level. As of Wednesday 29th, Xojo Cloud rejects requests with malformed percent-encoding before they ever reach your app, returning a 400 Bad Request from the front-end web server. This protection doesn’t care which Xojo version your app was built with, and it covers Web 1 and Web 2 the same way.

If your app runs on Xojo Cloud, you don’t need to redeploy, upgrade, or change anything. We rolled this out specifically so that users who can’t upgrade right away are covered, including Web 1 users who can’t upgrade to a fixed version at all.

What you should do

The right move depends on which Web version you’re on and where your app runs.

If you’re on Web 2

Upgrade to 2026r1.2 and redeploy if you can. That’s the cleanest fix and it addresses the issue at the source.

We know “just upgrade” isn’t always realistic. You might be on an older version because your license has expired, because a third-party plugin you rely on hasn’t caught up yet, because a newer Xojo release introduced a regression you can’t ship around, or for any number of other reasons. If that’s you, the hosting-based mitigations below will protect your app in the meantime, and they work regardless of which Xojo version you built with.

If you’re on Web 1

There’s no Xojo release that patches DecodeURLComponent for Web 1, so the fix has to live outside your app. The good news is that if you follow Xojo’s standard hosting recommendations, you’re fully covered against this specific issue:

  • Stay on Xojo Cloud. You’re already protected.
  • Use Lifeboat. Tim Parnell shipped a Lifeboat update that catches malformed percent-encoding before it reaches your app. Update Lifeboat and redeploy.
  • Run behind Apache or Nginx with the rules in the next section.

Separately, and on a longer horizon: Web 1 isn’t receiving framework patches anymore in general, so if you’re still on it, this is a reasonable moment to start thinking about a migration to Web 2. That’s a much bigger conversation than this post, but worth flagging.

Hosting-level mitigations

These rules reject malformed percent-encoded URLs at the web server, before the request reaches your Xojo app at all. They work for Web 1 and Web 2, and they don’t depend on the Xojo version you built with.

Apache:

# Reject malformed percent-encoding in the URI.
RewriteCond %{THE_REQUEST} %(?![0-9A-Fa-f]{2})
RewriteRule .* - [R=400,L]

Nginx:

# Reject malformed percent-encoding in the URI.
if ($request_uri ~ "%(?![0-9A-Fa-f]{2})") {
  return 400;
}

Reload your web server after updating the config (apachectl graceful or nginx -s reload) and check that a request like https://yourapp.example.com/?x=% comes back as 400 Bad Request.

These two snippets aren’t the only way to handle this. The goal is just to block malformed URLs before they reach your app, however you do it. If you’re running mod_security, for example, a rule that rejects URIs containing invalid percent-encoding will get you the same result. Same idea for any WAF, edge filter, CDN, or load balancer in front of your stack: catch the bad request, return a 400, move on.

If your Xojo app is exposed directly to the internet

If your Web app is talking to the internet without a reverse proxy in front of it, your options are narrower. On Web 2, upgrade to 2026r1.2 if you can. On Web 1, or on Web 2 if upgrading isn’t an option, you’ll need to put Apache, Nginx, or Lifeboat in front of your app, or move to Xojo Cloud, our managed hosting solution.

This is also a good moment to revisit the setup more broadly. Xojo recommends always serving Web apps behind a web server, both for performance and as a defense-in-depth measure against bugs like this one. A reverse proxy would have neutralized this specific issue before it reached the framework, and it’ll do the same for the next class of issue too.

If you use DecodeURLComponent in a non-Web project

The crash isn’t unique to Web. If you’re calling DecodeURLComponent on input you don’t control (anything coming from a user, a file, a network response, a clipboard), the same bug can hit a Desktop, Console, or other project type.

Upgrade to 2026r1.2 if you can; that’s the proper fix.

If you can’t upgrade, you’ll need to sanitize the input yourself before calling DecodeURLComponent. The check is simple in principle: every % in the string must be followed by exactly two hexadecimal characters (0-9A-Fa-f). If any % doesn’t meet that condition, treat the input as invalid and don’t pass it to DecodeURLComponent. A small helper that validates the string up front and either returns early or substitutes an empty value will keep your app from crashing on the same kind of malformed input that triggers the Web bug.

Acknowledgments

Thanks to the user who reported this through both public and private channels. That’s exactly the kind of disclosure that lets us turn a fix around in under a week. Thanks also to Tim Parnell for the fast Lifeboat update, which gave self-hosted users a drop-in mitigation almost immediately.

If you find a security issue in Xojo, please report it privately. You can email support@xojo.com or file a confidential issue on the issue tracker. Both reach us, and either one lets us get a fix out before the details become public, protecting the rest of the users.

References

Ricardo has always been curious about how things work. Growing up surrounded by computers he became interested in web technologies in the dial-up connections era. Xojo has been his secret weapon and language of preference since 2018. When he’s not online, chances are he will be scuba diving … or crocheting amigurumis. Find Ricardo on Twitter @piradoiv.