All insights
Article · 9 min

The Ordinary Security Gaps That Actually Cause Breaches

Most breaches don't come from sophisticated zero-days. They come from weak auth, outdated dependencies, and secrets sitting in plaintext. Here's how to find and prioritize the fixes that matter.

Hasnain Ahmed KhanSystems Architect ·
  • Security
  • Application Security
  • Engineering

The Ordinary Security Gaps That Actually Cause Breaches

When founders imagine getting breached, they picture a sophisticated attacker exploiting some obscure vulnerability nobody could have anticipated. In practice, the large majority of real breaches come from a short list of well-understood, well-documented problems that simply never got fixed: a password reset flow with a predictable token, a dependency three major versions behind with a known CVE, an API key committed to a public repository two years ago and never rotated. Nothing exotic. Just gaps that existed, were discoverable, and eventually got found.

This matters because it changes where you should spend security effort. You don't need a red team simulating nation-state attackers before you've closed the gaps a routine automated scanner would flag in an afternoon. Prioritizing the ordinary stuff first isn't the less rigorous approach — it's the one that actually reduces your real exposure fastest.

Weak authentication and session handling

This is the single largest category of real-world breach causes, and it shows up in a handful of recurring forms:

  • Passwords stored with weak or no hashing. Plaintext passwords are rarer now than they used to be, but weak hashing (unsalted MD5, or a fast hash algorithm never designed for password storage) is still common enough to matter. If your system was built more than a few years ago and hasn't been audited since, this is worth checking directly.
  • No rate limiting on login or password reset endpoints. Without it, credential stuffing and brute force attacks are trivial to run at scale, and they run constantly against any public login form whether or not anyone has specifically targeted you.
  • Predictable or long-lived session tokens. Session identifiers that are sequential, short, or that never expire give an attacker a much longer window to exploit a leaked token.
  • Password reset tokens that don't expire or aren't single-use. A reset link that works indefinitely is a standing account-takeover vector, especially if it ever ends up in a logging system, an email archive, or a browser history that gets compromised separately.
  • Missing or weak multi-factor authentication on privileged accounts. Admin panels, financial systems, and infrastructure consoles are exactly where MFA matters most and, in our experience, exactly where it's most often skipped because it's "just for internal use."

Outdated dependencies with known vulnerabilities

Every major dependency ecosystem — npm, PyPI, RubyGems, Maven — publishes vulnerability disclosures continuously, and once a CVE is public, exploiting it doesn't require any skill at finding the vulnerability, only skill at scanning for systems that haven't patched yet. This is why unpatched dependencies are attractive to attackers: the hard work of discovery is already done and published, and the only question is who hasn't updated.

The gap widens for a specific reason: dependency updates get deprioritized because they don't visibly change the product. A feature ships, gets demoed, gets celebrated. A dependency bump just... happens, if it happens at all, and often breaks something subtly enough that teams learn to dread running it. The result is codebases running dependency versions two or three years stale, with a dozen known CVEs sitting in the tree, none of them urgent-feeling until one of them is exploited.

The fix isn't heroic — it's a standing process: automated dependency scanning wired into CI, a routine (weekly or monthly, not "whenever someone remembers") for reviewing and applying updates, and a clear escalation path for anything flagged as critical severity. The organizations that get breached this way aren't usually the ones without scanning tools. They're the ones where the scanner flags the issue and nobody owns acting on it.

Secrets in plaintext, in code, or in the wrong place

This category covers a range of specific mistakes that all reduce to the same root cause — a secret that should be tightly controlled ends up somewhere it's readable by more people or systems than intended:

  1. API keys and database credentials committed to version control. Even in a private repository, this is a liability — repos get made public by accident, get cloned onto more machines than anyone tracks, and every contributor who's ever had access retains the ability to find that secret in the git history, whether or not they still have current access to the system it protects.
  2. Secrets stored in plaintext environment files that get shared over Slack or email. Convenient, and completely outside any access control or rotation process.
  3. Production credentials reused in staging or local development. This means a much less carefully guarded environment now holds the keys to production.
  4. No secret rotation policy. A credential leaked two years ago that was never rotated is still a live credential today.

The practical fix is a proper secrets manager — even a modest one — plus a hard rule that no credential goes into source control, ever, with automated scanning to catch violations before they merge rather than after.

How to prioritize the fixes

When we do security hardening work, we're not running a penetration test looking for novel exploits — we're doing a structured pass through exactly the categories above, because that's where the actual risk concentrates for most businesses. A practical prioritization order looks like this:

  1. Anything exposing customer data directly. Unauthenticated endpoints returning personal data, broken access controls that let one user read another user's records, exposed database backups — these go first regardless of how they were found.
  2. Anything with a public, known exploit. A dependency with a published CVE and available exploit code is being actively scanned for right now by automated tools that don't care how small your company is.
  3. Authentication and session weaknesses. These are the most common actual entry point, so closing rate limiting gaps, fixing session handling, and enforcing MFA on privileged accounts comes next.
  4. Secrets exposure. Rotate anything found in version control history immediately — rotation matters more than removal, because removal from the current codebase doesn't invalidate a credential still sitting in git history or in someone's local clone.
  5. Everything else — hardening headers, dependency updates without known CVEs, defense-in-depth improvements — gets scheduled as ongoing maintenance rather than emergency work.

Why this is different from a pen test

A penetration test is valuable, but it answers a narrower question than most businesses actually need answered first: can a skilled tester break in during a fixed engagement window. Practical hardening answers a different, usually more urgent question: are the well-known, cheaply-exploited gaps closed at all. Most businesses we work with haven't had that second question answered yet, which means a pen test at this stage would mostly just rediscover problems a straightforward audit already would have found for less.

Where this connects to the rest of your engineering practice

Security gaps rarely exist in isolation from the rest of a codebase's health. A system with a tangle of undocumented legacy code is also usually the one with unpatched dependencies and unclear ownership of who's responsible for auth. If your security review keeps turning up issues that trace back to a codebase nobody fully understands anymore, it's worth reading about staged legacy modernization alongside the security work, since untangling ownership of a system is often a prerequisite to actually closing the gaps found in it.

The organizations that avoid breaches aren't the ones with no vulnerabilities — every system has some. They're the ones that treat the ordinary, well-documented gaps as the priority, fix them on a standing schedule, and don't wait for a scare to take the list seriously.

Working on something similar?

I write these from real client work. If you're facing the same problem, it's usually faster to just talk it through.