Why is there PII and a password hash in my browsers local storage?
Christopher Talke Buscaino
The Backstory
I spend a decent amount of time poking around in the developer console on random sites.
At this point it's just habit, I like seeing how things are built, what data is flowing around etc. In some aspects it's like looking under the hood of car, sometimes you see elegant engineering, sometimes you see... well, something else.
Recently, we were browsing an accommodation website, because the website had somewhat of sleek and fast interface, it prompted me to open the dev tools to take a peak at what they used to build the site. Completely out of curiosity, nothing nefarious, really. I clicked over to the "Network" tab, nothing too interesting... "Application" tab, oh yeah ReactJS
and an Express
backend... I had a peak at "Storage," and started looking at the Local Storage entries.
And that's when I saw it.
Amidst the usual fluff I saw there was some UI state being cached – there was a key. Within that key, tucked away in a seemingly innocuous JSON blob, right there in plain text, was something labelled... "token." And the value next to it?
It started like this $2a$10$...
.
For anyone who knows a little about web development and security, that $2a$
prefix generally means it's a BCrypt hash. In modern web development, It's a pretty common password hashing algorithm. And there it was, sitting right there in my browser's Local Storage, my password hash, somewhere where it definitely should be.
I mean I was authenticated, so I decided to logout, refreshed the page and had a look at localStorage again... same deal. Email address, name, and... the hash. Sensitive user data, just... left behind.
I sat there for a moment, "Seriously?" I thought. "How does this even happen?". How did the developer(s) allow the backend to dump the entire user object to the frontend? Why is a state management library misconfigured this way?
Honestly, at this point, the how is almost less important than the that it happened.
Why This is a Problem
Should a password hash ever leave the server? No. Absolutely not. Even if it's a strong hash like BCrypt.
Security is about layers. It's about defense-in-depth. A salted, strong hash makes offline cracking harder, yes. But it doesn't make it impossible, especially if someone uses a weak password. Giving an attacker access to that hash removes a layer of defense. If they somehow get access to enough hashes, they can potentially run large-scale cracking operations offline.
When I step back and take a look, the hash itself being there wasn't even the biggest thing that bothered me. The biggest thing was the sloppiness. Leaving all of this PII in local storage, especially after they log out? That's not just a bug; that's a complete disregard for user data privacy and basic security hygiene. It tells you something fundamental is wrong with the development process or the security mindset of the entire team.
Again, if I could find an attack vector this quickly, I'm very sure that with a little bit of poking and proding you could find some much larger blunders.
Taking it to the Community
I really wanted to share this finding.
After all, this wasn't just a quirky bug; this felt like a genuine security blunder that other developers needed to see. I posted it on a popular web development subreddit /r/webdev
, fully expecting a chorus of "Holy crap, that's bad!" and discussions about why it happened and how to prevent it.
And sure, some people got it immediately. They saw the hash and knew it was wrong. But then... the other comments started coming in, here are some highlights:
I am at a loss to understand exactly what is so insecure about this... your wasting your time if you can’t trust the browser environment. Your barking at clouds.
Bad practice, little harm.
Passing a hashed password around is stupid and there is 0 reason to even pass it around much less send it to the client. But it's a bcrypt hash with a difficulty 10, so it could be worse... MOST sites send your email back inside of JWT, which usually has a ton of other information too.
"Just a trivial mistake, It's not that bad."
This completely floored me. "Trivial mistake"? Leaving a user's password hash and PII lying around in the browser post-logout is trivial?
But thankfully, amidst those surprising reactions, many others in the community did recognize the seriousness of the issue, offering validation, technical explanations, and sharing similar concerns.
Storing any password that isn't behind a secure system is so beyond stupid.
Data that should never leave a secure server/database... Massive security issue.
The server sent back hash + salt of password to the user, which there is absolutely no reason to ever do. Very sloppy on several levels.
If you're not even going out of the way to ensure a BCrypt isn't being returned to the client, then what else are you f@#$ing up.
Now The Scary Part Isn't the Bug, It's the Attitude.
Even with the security minded reactions, the opposite reaction from majority of this communicaty is what scared me the most.
The hand-waving. The downplaying. It feels like a large chunk of the average development community isn't prioritizing security as much as they should be.
We are dealing with people's sensitive data. Their identities, their credentials, their privacy. And when something like this happens, something that clearly violates basic, established security best practices, seriously, check the OWASP cheatsheets! The reaction from some is just to shrug and say "eh, not that big a deal."
You know what that signals to developers? It signals that shortcuts are okay. It signals that maybe it's not worth the extra effort to filter API responses or properly configure state persistence or rigorously test logout procedures. And if this mistake is considered "trivial," what else is being overlooked? What if it wasn't a BCrypt hash but an easily crackable hash like MD5 because a developer grabbed an old code snippet? What if this data isn't just in local storage, but cached elsewhere with fewer protections?
Seeing this reaction, this willingness to call a fundamental security lapse "not that bad," makes me question the overall security consciousness in our industry. It makes me worry about what other "trivial mistakes" are out there on the websites and apps we use every day, potentially putting millions of users at risk.
Be. Security. Conscious. It matters.
Look, I'm not saying everyone's perfect. Mistakes happen, especially under pressure. But the response to finding a mistake like this shouldn't be "it's trivial." It should be "okay, that's bad practice, let's learn why and make sure it doesn't happen again." It should spark curiosity about the process that allowed it, not defensiveness about the severity.
This isn't gatekeeping; it's about raising the standard. We handle sensitive information. That comes with a responsibility. Let's take it seriously.
If this post makes just one person double-check what they're storing client-side, or question an API endpoint that seems to return too much data, then sharing this was absolutely worth it. Consider it a sanity check.
Use the OWASP HTML5 Security Cheat Sheet, talk about security with your team, and please, for the love of digital privacy if you're someone who handwaves mistakes like this, please stop calling password exposure "trivial."
Anyway, just needed to get that off my chest.
I won't be surprised if this websites database ends up being tweeted out by Troy Hunt after it gets uploaded to Have I Been Pwned.