
The Redirect Bug Nobody Would Have Noticed
A few weeks ago I was going through old parts of the MyTreda codebase, not looking for anything in particular, just reviewing code I hadn't touched in a while. I came across something that wasn't causing any visible problems. The app worked exactly as expected: users logged in and got redirected back to where they meant to go. Everything seemed fine.
But after taking a closer look, I realized there was a hidden security risk sitting inside the authentication flow.
The Setup
Like many web apps, MyTreda allows users to be redirected after authentication. A typical flow looked something like this:
/auth/login?callbackUrl=/dashboard
After a successful login, the app would redirect the user to the URL specified in `callbackUrl`. This makes for a smoother experience, since users land directly on the page they were trying to reach in the first place. The implementation seemed straightforward.
The Problem
The issue was that the app trusted whatever value was passed in `callbackUrl`. That meant someone could craft a URL like this:
/auth/login?callbackUrl=https://malicious-site.com
The flow would look like this:
- The user clicks the link.
- The user sees the legitimate MyTreda login page.
- The user logs in successfully.
- The app redirects them to the attacker's website.
The login is real, and the domain is trusted.
The only part that isn't legitimate is where you actually end up. This type of vulnerability is known as an open redirect.
Why Open Redirects Matter
At first glance, an open redirect doesn't seem all that serious. The attacker isn't getting into your database or bypassing authentication.
But open redirects are a common tool in phishing attacks. An attacker can lean on the trust users already have in your domain to make a malicious link look legitimate.
Instead of sending someone straight to:
https://malicious-site.com
they send:
https://mytreda.com/auth/login?callbackUrl=https://malicious-site.com
Most people will trust the second URL because it starts with a domain they recognize. That trust is exactly what gets exploited.
How I Found It
I wasn't responding to a bug report or chasing down an incident. I was just reading old code.
As a product evolves, it's easy for assumptions made early on to go unexamined for a long time. In this case, the assumption baked into the code was: The callback URL will always be one of our routes.
Assumptions aren't validation, though. Any value coming from a user should be treated as untrusted until you've actually checked it.
The Fix
The fix itself was simple: instead of allowing arbitrary URLs, I restricted redirects to internal application routes only.
✅ Allowed:
- /dashboard
- /products
- /settings
❌ Rejected:
- https://malicious-site.com
- http://example.com
- //attacker-site.com
One case worth calling out is rejecting URLs that start with `//`. They look like relative paths at a glance, but browsers actually treat them as protocol-relative URLs pointing to an external domain.
Here's a simplified version of the validation:
function isValidCallbackUrl(url: string): boolean {
return url.startsWith('/') && !url.startsWith('//');
}
Now users can only be redirected to routes within the application itself.
Lessons Learned
Not every important issue breaks functionality. This one didn't.
The app worked perfectly. No one was reporting problems, and nothing looked wrong from the outside. But the vulnerability was still sitting there, waiting to be used.
It's easy, when maintaining software, to spend most of your attention on features, performance, bug fixes, and UI polish. Security tends to live in the assumptions you forget to question. And sometimes the fixes that matter most are the ones nobody ever notices.
Final Thoughts
Shipping features is only half the job. The other half is going back and questioning decisions you already made.
As MyTreda grows, I'm trying to spend more time auditing existing code and reducing risk alongside building new things. This particular fix took a few minutes to write. Finding it took slowing down and actually looking at the code with fresh eyes.
That's usually where the real improvements come from.