Dozens of Security Findings in One Codebase: Patterns We See in Every Startup Audit
We've audited dozens of startup codebases. The same patterns show up every time — broken auth, exposed databases, unpatched frameworks, no safety net. Here's what to look for in yours.
Productera Team
March 21, 2026
The Same Patterns, Every Time
We've audited dozens of startup codebases over the years — different industries, different stacks, different stages. The specifics vary, but the patterns are remarkably consistent.
Most of these companies built their product the same way: a small team (often contractors, sometimes AI tools) shipping features as fast as possible. The product works. Users are paying. But underneath, the same categories of problems show up again and again.
This post walks through the most common patterns we find, composited from multiple engagements. If you're a founder running a product that was built fast, there's a good chance some of these are in your code right now.
The Front Door Was Unlocked
The single most common critical finding: the backend doesn't verify who is making requests. The API accepts a user ID from the request body — "I am user #123" — and the server just... believes it. No check against the login token. No verification.
We've seen this as a one-off bug in a single endpoint, and we've seen it as a system-wide pattern where the framework's built-in auth is explicitly disabled. The worst case: a codebase where every endpoint had authentication turned off via a decorator, even though the framework provided it out of the box. The authentication system was decorative — it existed, but it wasn't enforced.
The practical impact: anyone who can make an HTTP request can read or modify any user's account. Change someone's password. Access their payment information. Update their profile.
What to check in your codebase: Search for patterns where user identity comes from the request body instead of the authentication token. If you see request.data.get('user_id') or request.body.userId being used to determine whose data to return, you have an IDOR vulnerability. The user's identity should always come from the server-side session or token — never from something the client sends.
The Admin Backdoor
A pattern we see more often than you'd expect: debug or admin endpoints that were built during development and never locked down. Token generation endpoints with no admin check. Password reset flows that skip verification. User role changes that anyone can call.
In one audit, we found an endpoint that generated a valid login token for any user in the system — no admin password required. It was presumably built for internal debugging. But it was live in production, accessible to anyone, and would give an attacker full account takeover in a single request.
What to check: Search your codebase for any endpoint that generates auth tokens, resets passwords, or changes user roles. Make sure every one of them checks that the caller has admin privileges. Then check again — admin verification is the kind of thing that gets accidentally removed during refactoring.
Passwords You Can Read
Industry standard for password storage is one-way hashing — bcrypt, Argon2, or PBKDF2. You hash the password when it's created, and when someone logs in, you hash what they typed and compare the hashes. The original password is never stored and can never be recovered.
We regularly find codebases that use symmetric encryption instead — meaning the passwords can be decrypted back to plain text if the encryption key is compromised. The login flow decrypts the stored password to compare it, which is a fundamental misunderstanding of how password storage should work.
What to check: Look at how your app stores passwords. If there's an encrypt and decrypt function involved, or if you can write code that recovers a user's original password, you have a problem. Every major framework has built-in password hashing — Django's make_password, bcrypt in Node.js, password_hash in PHP. Use them.
The Database Was on the Internet
This one shows up in roughly half the AWS accounts we audit. The database port — 5432 for PostgreSQL, 3306 for MySQL, 27017 for MongoDB — is open to 0.0.0.0/0, meaning every IP address on the internet can attempt to connect.
Usually the security group is named something like launch-wizard-1 or default — which tells you exactly how it was created: someone clicked through the AWS quick-start wizard and never changed the defaults.
The surrounding infrastructure tends to match. SSH open to the world. Staging and production sharing security groups. No CloudTrail (zero audit logging). No GuardDuty (zero threat detection). No VPC flow logs. If someone brute-forced the database password, there would be no record of the intrusion.
What to check: Log into your AWS console. Go to EC2 → Security Groups. Look at every inbound rule. If any rule has a source of 0.0.0.0/0 on a port that isn't 80 or 443, you have a problem. Database ports (5432 for PostgreSQL, 3306 for MySQL, 27017 for MongoDB) should never be open to the internet. SSH should be restricted to your team's IP addresses or — better yet — replaced entirely with AWS Systems Manager Session Manager.
While you're there, check if CloudTrail is enabled. If it's not, you have no audit log. Enable it today — it takes five minutes and it's the single most important thing you can do for your AWS security posture.
The Framework They Paid For But Didn't Use
This one isn't a security vulnerability per se, but it explains why so many vulnerabilities exist. Modern web frameworks — Django, Express, Rails, Laravel, Next.js — come with built-in authentication, permissions, input validation, and security middleware. They handle the boilerplate so developers can focus on business logic.
We routinely find codebases where all of this is bypassed. The framework is installed, but its security features are explicitly disabled or simply ignored. Authentication middleware is turned off. Permissions are skipped. Input validation is ad-hoc. JSON responses are hand-built instead of using serialisers.
The result is essentially a bespoke framework built on top of a real framework — one that replicates all the features but none of the safety. Every endpoint is a handwritten function that accepts raw input and returns a manually constructed response. No validation layer. No consistent error format. No access control.
This is a pattern we see frequently with contractor-built codebases. The developer knows just enough of the framework to make things run, but not enough to use its safety features. The code works — it just doesn't protect anything.
What to check: Whatever framework you're using, it has built-in security features. Are you using them? If your auth logic is custom-written instead of using the framework's middleware, if your input validation is ad-hoc instead of using the framework's validators, if your database queries are hand-built instead of using the ORM's safety features — you're probably reinventing the wheel, and the custom wheel is probably missing some spokes.
The Known Exploits Nobody Patched
Outdated dependencies with published CVEs are in virtually every codebase we audit. The web framework is months or years behind on security patches. Image processing libraries have known code execution vulnerabilities. SSL libraries have revoked certificates. AWS SDKs are years out of date.
The worst cases involve critical remote code execution vulnerabilities — published, well-documented, with patches available — that simply haven't been applied. These are on public vulnerability databases that attackers actively scan for. If your framework version appears on a CVE list, automated scanners are already probing your endpoints.
We typically find 20-40 dependency vulnerabilities in a single project, spanning both frontend and backend. Most are moderate. But there are almost always a handful of critical ones — the kind where a patch exists, it's a version bump away, and it just hasn't been done.
What to check: Run npm audit in your frontend directory and pip-audit (or safety check) in your backend. These are free, take 30 seconds, and will tell you exactly what's vulnerable. If you see critical or high severity findings, update those packages. Most of the time it's a version bump — not a rewrite.
No Safety Net
Beyond the specific vulnerabilities, the most concerning finding was what didn't exist:
- No automated tests. Zero test coverage. Every code change was a gamble.
- No CI/CD pipeline. Code went from a developer's laptop to production with no automated checks.
- No code review process. One person could deploy anything, including malicious code, with no oversight.
- No monitoring or alerting. If the platform went down or errors spiked, nobody was notified.
- No error boundaries. A crash in one component could take down the entire application.
These aren't nice-to-haves. They're the engineering practices that prevent vulnerabilities from being introduced in the first place. Without them, every fix you make today can be undone by tomorrow's deployment.
What to check: Do you have CI/CD? Does a human review code before it reaches production? Do you have any automated tests? Do you have error tracking (Sentry, Datadog, even basic CloudWatch alarms)? If the answer to any of these is no, that's your most important next step — more important than fixing individual vulnerabilities, because without these practices, new vulnerabilities arrive faster than you can fix old ones.
What Would an Auditor Say?
When we map these patterns against SOC 2 Trust Service Criteria and ISO 27001 Annex A controls, the result is consistent: multiple automatic audit failures. Any single one blocks certification.
Disabled authentication fails CC6.1 (Logical Access). Missing audit logging fails CC7.1 (Monitoring) — and without logs, there's literally nothing for an auditor to examine. No CI/CD or code review fails CC8.1 (Change Management). An unpatched critical CVE in production fails CC7.5 (Vulnerability Management).
If you're planning to sell to enterprise customers, raise a Series A, or get acquired, technical due diligence will find these patterns. Better to find them yourself first.
The Good News
Here's the part that usually surprises founders: most of the critical issues are fixable in days, not months.
Locking down a database security group takes 15 minutes. Enabling CloudTrail and GuardDuty takes 30 minutes. Patching critical CVEs is a few hours of dependency updates. Restricting CORS and fixing permission configuration are often single-line changes.
The harder fixes — rebuilding authentication, adding CI/CD, implementing proper testing — take longer. But they're well-understood engineering work, not research problems. Every one of these has been solved thousands of times.
Dozens of findings sounds alarming. And it should — the platform is genuinely at risk. But the gap between "vulnerable" and "solid" is smaller than most founders think. It's a focused sprint with someone who knows what to look for.
Your Move
If any of this sounded familiar — rotating contractors, no code review, framework features being bypassed, infrastructure you set up once and never revisited — you're probably sitting on similar issues.
Start with the free checks: npm audit, pip-audit, and a look at your AWS security groups. If those come back clean, you're ahead of most startups. If they don't, you know where to focus.
For a deeper look, our five-point audit checklist walks you through authentication, injection, secrets, performance, and error handling — all things you can test yourself in an afternoon.
And if you want someone to do the full audit — code, infrastructure, and practices — that's what we do.
Related: How to Audit Your AI-Generated Codebase · SOC 2 Compliance: A Founder's Guide · The Real Cost of Scaling a Vibecoded App
Related Articles
Why Your Contractors Built a Product That Works But Isn't Safe
Your app demos great and users are paying. But the codebase underneath is a liability. This isn't because your contractors were bad — it's because the incentives made it inevitable.
Cognitive Debt Is Eating Your AI-First Startup
96% of developers don't trust AI output. Only 48% verify it. The gap between those numbers is creating a new kind of debt that's harder to fix than bad code.
Ready to ship?
Tell us about your project. We'll tell you honestly how we can help — or if we're not the right fit.