A Tale of Two Routes

6 min read Original article ↗

While browsing Twitter—sorry, X—I realized something that had never crossed my mind before: All profile pages live under https://x.com/:username. And it turns out that this is actually a major design flaw.

The official account of X on Twitter. Address bar points to https://x.com/X

Route Hijacking

The problem with X’s routes is that they’re flat. Too flat.

  1. https://x.com/home takes you to your home page ✅
  2. https://x.com/messages takes you to your DMs ✅
  3. https://x.com/:username takes you to @username’s account ⚠️

So what if a username happens to match a reserved route? For example, what’s stopping someone from claiming @home and getting assigned the profile URL of https://x.com/home? Well, apparently nothing:

Clicking the link for @home’s profile page takes you to /home.

You remember your old friend @Settings, right?

Clicking the link for @Settings’s profile page takes you to /settings.

Achievement unlocked: Hidden (or extremely popular) profiles. Depending on which route is registered last, you’re going to see one of two behaviors:

  • Legitimate routes like /home point to the hijacker’s profile.
  • Legitimate routes work, but you can no longer view the conflicting profiles.

Although the second approach is not ideal, it’s the lesser of these two evils. The first issue appears to have been patched, so you can no longer navigate to these profile pages directly because the reserved route always takes precedence. That leaves only two options for following these “hidden” accounts:

  • Find or compose a tweet mentioning the account and hover over the mention.
  • Find the account in someone else’s follows list.

If you try to view @settings’s followers, you’ll get a routing error:

Twitter UI on settings/verified_followers. The UI shows the typical settings page, but the right-hand pane has an error that reads: "Hmm...this page doesn't exist. Try searching for something else."

But you can still view @home’s followers, at least for now. And there are lots of them: 1.1 million at the time when I wrote this, to be exact.

Viewing the followers for @home on Twitter

The issue hasn’t been fully patched, though, because if you click a mention for @logout, you’ll get a prompt to—you guessed it—log out. Likewise, mentions for @home and @settings will take you to /home or /settings, respectively.

Tweet by @soapbox_ on Nov 22, 2024 that reads: 'hey press this cool blue text @logout.' The popover for the account shows that it's suspended.
It's a feature, not a bug!

Some usernames—like tos, messages, notifications, and privacy—appear to have been reserved correctly. Their mentions aren’t even hyperlinked:

Tweet from @woomy_irl that reads: "Wow @home @logout @notifications @explore @messages"

Why It Matters

Users should never be able to hijack static routes during the account creation process, as this affects security and usability.

For example, at one point this bug prevented users from logging out of the mobile app because the legitimate /logout link would take them to @logout’s profile:

Hey @elonmusk, when you try to logout on the website on mobile it takes you to the @logout account instead

That may seem annoying, and it is. But it’s a mild regression compared to opening the app and seeing @home’s profile instead of your feed—an actual bug that was reported by multiple users in February 2024:

Post on r/Twitter that reads: 'Does anyone know why my app suddenly opens these random account pages when I boot it up?' It includes a screenshot of the @home profile page.
Post on r/Twitter: "Does anyone know why my app suddenly opens these random account pages when I boot it up?"
Tweet by @laxxedd on Feb 17, 2024 that reads: 'Either my X acc is hacked or @elonmusk needs to fix this Geneia@home glitch.' Several of the replies, one as recent as Jan 3 2025, are similarly confused.
Source: @laxxedd on X

Now, imagine if the owner of the @home profile had included a malicious link in their bio or masqueraded as an official person. Sure, the account would’ve eventually been flagged and suspended, but maybe it would’ve managed to trick enough users to click the link before getting caught. That’s no longer an oopsie-woopsie: It’s a security loophole.

This bug also reveals an apparent disconnect between how users think navigation works and how it’s actually implemented on the web. Ordinary people don’t think of URLs the same way developers do, and the lines become even blurrier in a client-side-routed app like Twitter that tries its best to feel native. You open the “app” and you expect to see your timeline. You click a “tab”—not a link, mind you—and expect to see a new user interface without a full page reload. So you can’t really fault someone for logging in, seeing @home’s profile page, and thinking the site or their account has been “hacked.”

Why It Happened

Using dev tools, I verified that all responses on Twitter include the x-powered-by: Express header, which means the back end uses the popular Express.js framework:

Firefox dev tools Network tab for x.com/home shows an x-powered-by: Express response header.

With that in mind, we can assume that Twitter’s router looked something like this at the time when this bug was originally reported:

javascript code snippet
router.get('/:username', (req, res) => {});
router.get('/home', (req, res) => {});
router.get('/settings', (req, res) => {});
router.get('/logout', (req, res) => {});

In Express, route handling is done on a first-come, first-serve basis: The first route handler that matches a requested path will be the only one to run. This means that in the above example, a GET request for the /home route would call the handler for /:username because that route was defined first and matches the requested path. Effectively, this broke the app for everyone, showing them @home’s profile page instead of their feed.

Without seeing the source code, we can only make educated guesses, but it’s likely that Twitter’s solution was to flip the order of the routes and prioritize static ones:

javascript code snippet
router.get('/home', (req, res) => {});
router.get('/settings', (req, res) => {});
router.get('/logout', (req, res) => {});
router.get('/:username', (req, res) => {});

While that fixes the original bug, now nobody can view @home’s, @logout’s, and others’ profiles that happen to coincide with reserved routes.

As a best practice in API design, if you allow users to create public-facing accounts, their profile pages should live under a more specific path, like /user/:username, to minimize the risk of collisions with static routes:

javascript code snippet
router.get('/home', (req, res) => {});
router.get('/settings', (req, res) => {});
router.get('/logout', (req, res) => {});
router.get('/user/:username', (req, res) => {});

If you don’t do this, then you at least need to reserve these usernames so that users can’t claim them when signing up for an account or changing their handle.

Username Parking and Eviction

You might be thinking: Well, Twitter already fixed this bug, who cares? But suppose you’ve had @username for years, and then one day X decides to add https://x.com/:username as an internal route that just happens to conflict. If this route is registered with a lower priority than username routes, it’ll point to your profile page—the @home fiasco all over again. But if it’s implemented correctly, nobody will be able to view your profile from that point onward. Effectively, this one flaw in X’s routing forces you to change your handle. It also means you can compile a list of handles that may one day trigger route collisions and create an account for any that don’t already exist, “parking” them in the hopes of hijacking routes in the future.

Conclusion

All of this could’ve been avoided if Twitter had used URLs like /profile/:username or /user/:username for profile pages, as many social media sites do. I suspect that Twitter’s decision to use flat URLs was motivated by aesthetics: making profile URLs short instead of safe. Technically, that would’ve still worked if the developers had reserved these handles during account creation. But because they didn’t, I’m now a proud follower of @home and @settings.