The Security Basics Small Teams Keep Getting Wrong
Most security incidents in small teams are not caused by exotic attacks. They are caused by the same handful of basics, missed for the same handful of reasons. Here is the short list that matters.
When small teams ask us to review their security posture, we expect to find interesting, creative weaknesses. We almost never do. What we find, over and over, is the same short list of basic issues that have been on the OWASP Top 10 for the past decade.
This is not a criticism. Small teams are under-resourced and these issues are easy to miss. But they are also easy to fix once you know to look, and fixing them gets you past the overwhelming majority of real-world attacks. The people trying to break into your app are not running a bespoke zero-day. They are running a script that checks for the basics.
Here is what we keep finding.
Authorisation Checks on the Wrong Layer
This is the single most common serious issue we see. The code authenticates the user — "are you logged in?" — but does not authorise the specific action — "are you allowed to do this?".
A typical shape: an endpoint that updates a record takes an ID from the request, looks up the record, and updates it. There is no check that the record belongs to the user making the request. If an attacker can guess or enumerate IDs, they can modify other users' data.
The fix is to do the ownership check at the data layer, every time, without exception. Not in middleware. Not only in the happy-path service method. At the point where the data is fetched.
OWASP's guidance has listed Broken Access Control as the number one web application risk for several years running. It is number one because it is both extremely common and extremely consequential.
Secrets in Places Secrets Should Not Be
Hardcoded API keys in client-side JavaScript. Database passwords in a .env.example checked into git. Stripe secret keys in a commented-out line someone forgot to delete. We see all of these regularly.
The specific issue varies; the root cause is almost always the same: there is no single, consistent place secrets live, and there is no habit of asking "where does this secret come from" every time one gets added.
The fix is process, not technology. Pick a secrets manager — AWS Secrets Manager, GCP Secret Manager, Vault, Doppler, even a dedicated .env file that is properly gitignored — and use it for everything. Run a tool like gitleaks or trufflehog in CI to catch regressions.
Once a secret has been committed, even to a private repository, rotate it. You cannot un-leak a key by deleting the commit.
Rate Limiting That Is Not There
Most small teams we review have no rate limiting on their API at all. Sign-up works. Login works. Password reset works. All of them will happily accept ten thousand requests a minute from the same IP.
The consequences range from annoying to catastrophic. Password reset endpoints that flood users with emails. Login endpoints that enable credential-stuffing attacks. Expensive read endpoints that enable denial of service on a budget.
The fix is boring: put rate limiting in front of anything that is public-facing. For most teams this is a few lines of middleware and a Redis instance, or a Cloudflare rule. Cloudflare's rate limiting will handle the common cases with no code at all.
Session Handling That Has Never Been Thought Through
"How long does a session last?" is a question most small teams cannot answer, because the answer is "however long the default was in whatever library we used, and nobody has ever looked".
Common problems we see: sessions that never expire, sessions that are invalidated on logout only on the client, password changes that do not revoke existing sessions, JWT tokens with no revocation mechanism at all.
Each of these matters. A stolen session that never expires is a permanent compromise. A JWT that cannot be revoked means that the only way to kick a user out is to wait for the token to expire.
OWASP's session management cheat sheet is a good baseline. The short version: sessions should have a reasonable maximum lifetime, logout should revoke on the server, password changes should invalidate all other sessions, and if you are using JWTs you need a revocation list.
Dependency Updates That Never Happen
Small teams often have a dependency update cadence of "when something breaks". This means their lockfile contains packages with known, published vulnerabilities that have had fixes available for months or years.
The attacker's workflow here is mechanical: scan for known vulnerable versions, find targets, exploit. Your app does not need to be interesting to be compromised this way. It just needs to be running an old version of something.
The fix is to automate it. Dependabot or Renovate will open pull requests against your dependencies automatically. Set them to group patch updates together so you are not drowning in noise, and set aside time each week to review and merge them.
You will not catch everything, but you will catch the vast majority of what actually gets exploited in the wild, because what actually gets exploited is almost always a known CVE in an outdated package.
Logging That Logs Nothing Useful
When something goes wrong in production — a real incident, not just a bug — the question is: what happened, when, and to whom? If the answer is "we do not know", you have a logging problem.
What we see: applications that log errors but not the context around them. No request IDs. No user IDs. No timestamps with timezones. Logs that are rotated away before anyone has time to look at them. Logs with sensitive data in them that should not be stored at all.
The fix is a logging standard for the team. Log at boundaries — entry and exit of request handlers, key state transitions, all errors with context. Include a request ID that follows the request through the system. Keep logs long enough to investigate incidents, which for most teams means at least 30 days. Do not log sensitive data.
The Boring Conclusion
None of this is exciting. None of it is a zero-day. None of it is the subject of a conference talk. It is all in the first chapter of any security book, and it is all what actually takes small teams down.
If you do one thing this week: audit your authorisation checks on the endpoints that mutate data. The bug is almost certainly there, and it is almost certainly the one that will hurt you first. The same attention to boundary behaviour applies when designing APIs more generally.
Want a second set of eyes on your app's security posture? Get in touch — audits like this are part of our tech consultancy, and we can tell you where the real issues are, not the theatrical ones.