🎁 Free starter workshopHaving a SW issue?
Back to blog
TechnologySecurityBehind the ScenesLegacy CodeFixes

Security Holes We Fixed After Previous Vendors

Lukáš HusoApril 23, 20266 min read
Security Holes We Fixed After Previous Vendors
Photo: FLY:D / Unsplash

Taking over someone else's project is a bit like moving into an apartment after the previous tenant. Everything looks fine at first glance. Then you open the closet and discover there's a chunk of wall missing behind it. And behind that wall, things are living that you really don't want to know about.

Over the years, we've inherited dozens of projects from other vendors. Some were in reasonable shape. Some were disasters. And then there was a special category -- projects where the security audit woke us up in the middle of the night in a cold sweat.

Here are a few stories. Names and details have been changed, obviously, but the technical issues are unfortunately very real.

"We Commented Out Auth for Testing"

We took over a mid-sized e-commerce platform. Everything looked functional at first glance -- nice frontend, working cart, payments going through. Then we opened the admin panel.

More precisely: anyone could open it. Just type the admin URL and you were in. No login, no authorization check.

In the code, we found the authentication middleware. It was there. Carefully written. And entirely commented out. Above it glowed a comment: // TODO: uncomment after testing. That comment had been there for eight months. For eight months, anyone who knew or guessed the admin URL could access orders, customer data, pricing -- everything.

The worst part? The client knew about the admin panel and actively used it. It never occurred to them that not having to log in was a problem. "It just worked," they told us.

The API That Returned Absolutely Everything

Another project, a mobile app with a REST API. The client complained about slowness, so we looked at the backend. The user search endpoint accepted a query parameter. When you sent a normal query, you got reasonable results.

When you sent an empty string? The API returned a complete dump of every user in the database. Including email addresses, phone numbers, home addresses. And yes -- including hashed passwords. And yes, those hashes were unsalted MD5.

For those not into security: unsalted MD5 hashes can be cracked in minutes using rainbow tables. It was essentially the same as storing passwords in plain text.

The kicker? That endpoint was also called by the public registration page to check if a username already existed. So anyone who opened browser developer tools could download the entire user database without any authentication whatsoever.

The Payment Gateway as an Open Book

An online store with a custom payment gateway integration. The client had issues with chargebacks and wanted us to look at the transaction history. When we opened the logs, we went pale.

The application was logging complete requests and responses from the payment gateway. Including full credit card numbers. Including CVV codes. Including cardholder names. All in plain text, in log files that were -- of course -- accessible via the web because nobody had moved them outside the public directory.

This isn't just a security hole. This is a PCI DSS violation that carries fines in the millions. And if a regulator found out, the client would lose the ability to accept card payments entirely.

We treated the fix as a crisis situation. Delete the logs immediately, secure the endpoint, implement tokenization. And then a long, uncomfortable conversation with the client about what happened and what it meant.

JWT Secret: "secret123"

A web application with JWT authentication. It looked modern and clean. Login, tokens, refresh tokens -- all by the book. Except for one detail.

The JWT secret -- the key used to sign all authentication tokens -- was set to secret123. Literally. This wasn't in a test environment. This was in production. For eighteen months.

With this secret, anyone could forge their own valid JWT token. With any user ID. With any role. Including administrator. All you needed was jwt.io, enter the secret, and generate your own token. The entire authentication system was essentially theater.

When we asked the previous vendor (the client connected us), the answer was: "Yeah, we meant to change that, but then we forgot."

User Roles in a Cookie

A corporate document management system. Three roles: reader, editor, admin. You'd logically expect the role to be stored in the database and verified server-side.

Instead, it was stored in a cookie. An unencrypted, unsigned cookie. Literally role=reader. Changing it to role=admin in the browser required exactly five seconds and zero technical knowledge.

Add to this that the system stored contracts, financial statements, and personnel documents. Any employee with reader access could edit or delete anything. All they had to do was modify a single cookie.

Upload Whatever You Want

A content management system with file upload capability. Images, documents, attachments. Standard feature. What wasn't standard was the implementation: zero validation of uploaded files. Absolutely none.

File format? Not checked. File size? Not checked. File name? Not checked. Where does the file go? Into a public directory, directly executable by the server.

This meant anyone could upload a PHP file, access it via URL, and execute arbitrary code on the server. In security jargon, this is called Remote Code Execution, and it's approximately the worst thing that can happen.

When we informed the client, they replied: "But the upload form is hidden, nobody knows about it." Security through obscurity. A classic.

8 months
commented-out auth in production
18 months
JWT secret 'secret123'
5 seconds
to change cookie role to admin
millions $
potential PCI DSS fines

What We Learned

Every one of these stories shares a common denominator: security wasn't a priority. It was the thing that would "get done later," "be sorted in the next version," "is fine for now."

But security doesn't get done later. Either it's part of development from the start, or it isn't there at all.

A few practical takeaways:

A security audit isn't a luxury -- it's a fundamental part of every project. It doesn't matter how small the application is or how few users it has. A single unprotected endpoint can cause millions in damages.

Test configurations must never remain in production. Commented-out authorization, default passwords, debug modes -- all of this needs to be on the deployment checklist.

Never trust the client. In the security sense -- never store sensitive data where the client (browser, cookie, URL) can access and manipulate it.

Log wisely. Logs are essential for debugging. Sensitive data has no place in them. Ever. Under any circumstances.

And if you're taking over a project from another vendor? Security audit as step one. Not step two, not "when there's time." Step one. Because that commented-out auth middleware might have been waiting there for eight months already.

Inheriting someone else's code? Start with this checklist: 1) Check authentication and authorization — are they active and server-side? 2) Look for sensitive data in logs, cookies, and API responses. 3) Verify the JWT secret is not a default value. 4) Check upload endpoints — do they validate file type and size? 5) Run an OWASP ZAP scan on the entire application.

Get your custom price

Our configurator shows you an indicative price for your project in 2 minutes.

Related Articles

We Inherited the Code: Worst Findings in Legacy Projects
TechnologyLegacy CodeRefactoring

We Inherited the Code: Worst Findings in Legacy Projects

Plaintext passwords, 5000-line files, SQL injection galore. Real legacy code horrors we've actually encountered.

February 26, 20266 min read
AI writes bad, insecure code? It writes exactly the code you ask for
TechnologyAIDevelopment

AI writes bad, insecure code? It writes exactly the code you ask for

AI code isn't inherently low quality — it's exactly as good as the instructions, orchestration and verification around it. A breakdown of the most common skeptic arguments and the process that makes AI a production-grade tool.

July 7, 20267 min read
A Database Full of Surprises: Migration Stories
TechnologyDatabaseMigration

A Database Full of Surprises: Migration Stories

A table with 500 columns, dates as strings in 12 formats, address fields full of phone numbers. Real database migration stories.

March 19, 20267 min read