I want to be clear upfront: this post is not about SQL injection or not sanitising user input. Senior developers know those. What I'm talking about are the subtler issues — the ones that require knowledge to understand but happen because of incorrect assumptions, time pressure, or conceptual gaps in how we think about security boundaries.

1. Trusting Internal Services Completely

The architecture pattern: service A calls service B via an internal API. Service A is authenticated with the outside world. So naturally, service A trusts everything that comes from the internal network, and service B trusts everything coming from service A.

The problem: this one compromise from any point in your internal network becomes a free pass everywhere. The assumption that "if it's on the internal network, it's trusted" breaks down the moment you add a vulnerability anywhere in the system — a misconfigured third-party service, a compromised container, a developer's laptop on the VPN.

The fix is zero-trust networking, which sounds complicated but in practice often just means: service-to-service calls should use short-lived tokens, not just network position, as their authentication credential.

2. Not Rotating Secrets That "Never Change"

Secrets that live in production for years are a significant risk. The longer a secret is live, the higher the probability it's been seen by a developer who's since left, saved in a local file somewhere, or included in a log line. Most breaches involve credentials that were valid for months or years before being used.

Setting up automated secret rotation is annoying to set up once and invisible thereafter. Not setting it up means your application has a slowly-growing risk surface that most developers never think about because "nothing changed."

3. Inadequate Rate Limiting on Authentication Endpoints

Most developers add rate limiting to their API endpoints. Fewer add strong rate limiting specifically to authentication flows. The standard "100 requests per minute per IP" rate limit is nearly useless against a distributed credential stuffing attack using 50,000 residential proxies. Modern credential stuffing attacks hit at two to three requests per hour per IP, specifically to stay under the detection threshold of typical rate limits.

Effective authentication protection requires at minimum: IP-based rate limiting, per-account login attempt limits, detection of distributed attempts across IPs, and CAPTCHA challenges for suspicious patterns — not just raw request volume.

4. Verbose Error Messages in Production

This one gets experienced developers because we're trained to write good error messages. In development, "User not found with email john@example.com" is helpful. In production, it tells an attacker which email addresses are registered with your service and which aren't.

The correct production behaviour: identical error messages for "wrong email" and "wrong password." Identical response times too — timing attacks are real, and returning "wrong email" 5ms faster than "wrong password" leaks information.

5. Not Auditing What Third-Party Code Can Actually Do

We've become very good at evaluating whether a library does what we want. We're much less systematic about evaluating what else it might do. The npm and PyPI ecosystems have had enough supply chain attacks at this point that this should be a standard part of dependency review — but it rarely is.

Before adding a new dependency: check what permissions it needs, what network requests it makes, and whether there's a smaller alternative that does less. Libraries with access to your environment that make outbound network calls are a meaningful supply chain risk, and the attack surface grows with every dependency you add.

The Pattern Here

None of these are obscure. They appear in OWASP documentation and security textbooks. But they happen in experienced teams because they require specific attention to architecture decisions, not just code review. Security needs to be part of design conversations, not just something you check at the pull request stage.