You Get Hacked Because of Assumptions, Not Technology
System safe for 3 years not because good, but because not attacked yet. 30 minutes into a startup with 2FA + WAF + encryption. Not weak tech - wrong assumptions. IP whitelist, frontend validation, dependencies, logs, 2FA.
0xNN · · 10 min read
I only recently realized why the system I built for 3 years was "safe": not because I was good, but because no one had attacked it yet. When I audited another startup's system that had 2FA, WAF, encryption at rest, and annual pentests - I got in within 30 minutes. Not because their tech was weak. Their assumptions were wrong.
That was the moment I realized security isn't about tools. Not about bcrypt, not about JWT, not about the OWASP checklist. Security is about which assumptions you hold, and which ones make you fragile. This article isn't about "X rules of security." It's about the assumptions that silently make your system easy to break, even when you've implemented "best practices."
---
Assumption #1: "Only I Know About This Endpoint"
I once found an internal API endpoint exposed to the internet: /api/internal/sync-db. Auth? IP whitelist. The dev team thought "only the office can access it." They forgot: the office VPN was shared with freelancers, the VPN config had leaked to a public GitHub repo, and one freelancer resigned but their VPN wasn't revoked.
I tested from a Singapore VPS, using an IP not whitelisted. Blocked. Using an Indonesia IP routed through the still-active office VPN? In. 30 minutes.
The code was safe. Auth existed. What was wrong: the assumption "IP whitelist is solid." An IP whitelist is only as strong as how the IPs are distributed. If the VPN leaks, the whitelist is an illusion.
The philosophy I learned: you can't secure what you don't have an inventory of. Before asking "is this endpoint safe?", first ask: "who can reach this endpoint, and what paths do they take?" Inventory first, then secure. The assumption "only I know" is the most dangerous because you don't list who can actually reach it.
---
Assumption #2: "Validating Input from the Client Once Is Enough"
Many devs validate input on the frontend. Then on the backend, they use it directly. "Ah, the frontend already validates, no need to repeat."
That assumption makes your system easy to bypass. The frontend is convenience, not security. The browser doesn't have to run your JavaScript. curl can send any body. Postman can edit the request before it reaches the backend. Burp Suite can intercept and modify.
Concrete example I found in an audit: a register form, the frontend validates email with regex, password min 8 chars. Backend directly does INSERT INTO users (email, password) VALUES (...). I bypassed the frontend, sent password: "a" (1 char), backend accepted. The bcrypt hash ran, the user was created with a 1-char password. Next, I brute-force the login: 1 char = only 62 attempts (alphanumeric). How many accounts can I take over?
Wrong assumption: "frontend = security layer." The frontend is never a security layer. The frontend is a UX layer. The security layer must be on the backend, in the database, at every boundary your data crosses.
As I read from the backend article earlier: defense in depth. Not "add layers for the sake of having more layers." But: each layer has its own responsibility, and doesn't trust the previous layer. The client isn't trusted. The API gateway isn't trusted. Even internal services don't trust other services.
// Frontend - UX only, not security
if (!email.includes('@')) return alert('invalid email')
// Backend - this is security
if (!isValidEmail(email)) return res.status(400).json({ error: 'invalid email' })
if (password.length < 8) return res.status(400).json({ error: 'password too short' })
Frontend can be bypassed in 5 seconds. Backend can't. That's the difference.
---
Assumption #3: "Popular Libraries Are Safe"
I once used lodash in a project. Version 4.17.4. Popular, right? Millions of downloads per week. Must be safe.
Three months later, a CVE dropped: prototype pollution. An attacker could inject properties into Object.prototype via _.merge or _.set. The effect? XSS, RCE, depending on how you use lodash.
What made me feel stupid: I never audited my dependencies. I assumed "npm install = safe." When in fact the npm ecosystem is the wild west. In 2024 there were 13,000+ malicious packages pulled down after npm audit. Packages got hijacked (maintainer account taken over), malicious code was injected, 2 hours later it was downloaded millions of times before anyone noticed.
Wrong assumption: "popular = audited." Popular only means many people use it, not many people audit it. The left-pad incident of 2016 - one 11-line package, broke thousands of projects because of the dependency tree. event-stream in 2018 - maintainer handed over to an unknown person, that person injected a crypto wallet stealer.
What I do now:
Audit dependencies every week
npm audit
Lock exact versions, not caret
"dependencies": {
"lodash": "4.17.21" // not ^4.17.21
}
Use Renovate or Dependabot for auto-PR security updates
Review new packages before install - who's the maintainer? commit history? download count?
The philosophy I learned: a dependency is an assumption you outsource. You assume the maintainer is honest, the code is audited, the updates are safe. That assumption is valid until it isn't. You still need a process to detect when your assumption is wrong.
---
Assumption #4: "Logs Don't Contain Sensitive Data"
This is the assumption that makes breach impact 10x worse. You encrypt the database at rest. You encrypt the connection with TLS. You hash passwords with bcrypt. Great.
But your logs?
I once saw the production logs of a fintech startup. Every request was logged with the full body. Including the /login request with body:
{ "email": "user@startup.com", "password": "PlainTextPassword123" }
Plain-text password in logs. The database is encrypted, the password is bcrypt-hashed. But the logs? Plain text. An attacker who gets access to the log aggregation system (Datadog, CloudWatch, ELK) = gets all user credentials.
Wrong assumption: "logs aren't sensitive, I can write anything for debugging." Logs are the most under-rated data leak vector. I've found:
• JWT tokens in logs (can be replayed)
• Credit card numbers in logs (PCI compliance violation)
• Session IDs in logs (can lead to session hijacking)
• Third-party API keys in logs (can be stolen)
The rule I hold now: assume logs will be read by an attacker. Don't write anything you don't want an attacker to read.
// Wrong - password, token, PII in logs
req.log.info({ body: req.body }, 'incoming request')
// Right - redact sensitive fields before logging
const safeBody = redact(req.body, ['password', 'token', 'creditCard'])
req.log.info({ body: safeBody }, 'incoming request')
// Or use a library like pino with built-in redaction
const logger = pino({
redact: ['req.body.password', 'req.headers.authorization', '*.creditCard']
})
Philosophy: you can't control who will read your logs 6 months from now. New DevOps, vendor, attacker who breached the log system. What you can control: what you write to logs. Assume logs = public document.
---
Assumption #5: "2FA Is a Silver Bullet"
Many startups are proud "we have 2FA." Then they stop thinking about security. 2FA = done.
Wrong. 2FA doesn't save you from:
• Phishing that simulates a login page - the user enters their password + 2FA code into a fake page, the attacker relays it to the real site, the session is stolen in real-time. This is called reverse proxy phishing, a tool like Evilginx. SMS or TOTP 2FA doesn't stop this.
• Session hijacking - 2FA only validates at login. After login, a session cookie or JWT is issued. An attacker who gets the session cookie (XSS, log leak, MITM) doesn't need 2FA. They're already authenticated.
• SIM swap attack - SMS 2FA? The attacker socially engineers the telecom to move the number to a new SIM. The 2FA code goes to the attacker's SIM. Account taken over.
• Stored credential - the browser stores the password + the device is "trusted" after the first 2FA. An attacker who gets device access = access without 2FA.
Wrong assumption: "2FA = system is safe." 2FA = 1 layer. A layer that can be bypassed with social engineering (phishing), session theft, or device compromise. 2FA reduces risk, it doesn't eliminate it.
What's right: 2FA + strict session management (short-lived tokens, refresh rotation) + device fingerprinting + anomaly detection (login from new IP, geolocation mismatch) + phishing-resistant 2FA (WebAuthn / passkey, which can't be phished because it's domain-bound).
The philosophy I learned: no single control is enough. Security is layers that each address a different attack vector. 2FA addresses password theft. But not session theft. Session management addresses session theft, but not device compromise. Each layer has a gap. You stack layers not to "be more secure," but to cover the gaps of the other layers.
---
The Most Dangerous Assumption: "I'm Not a Target"
I once talked to a small startup founder: "we're small, no one will attack us." 6 months later, their server became a crypto mining botnet. They weren't a target. They were a target of opportunity.
Attackers don't scan specific targets. They scan the internet - Shodan, Censys, masscan - looking for mass vulnerabilities. "Who has Elasticsearch exposed without auth?" Find 10,000 servers. All compromised automatically. You don't need to be important. You just need to be vulnerable.
The assumption "I'm not a target" = the assumption "I won't get rained on because I'm at home." Even though your roof is leaking. Automated attackers don't care who you are. They care whether you can be compromised.
The philosophy I learned: on the internet, you are always being scanned. Every second. Automated bots looking for open ports, default credentials, vulnerable software. You don't need to be a "target" to get hit. You just need to be "breakable."
---
What I Learned About Mindset
Security isn't about technology. Technology is implementation. Security is about which assumptions you hold, and which can be falsified.
Every design decision you make, ask yourself: "what assumption makes this safe?" Then ask: "what happens if that assumption is wrong?"
• "IP whitelist is safe" → assumption: VPN never leaks. If it leaks? What then?
• "Frontend validation is enough" → assumption: user uses your browser. What if curl?
• "Popular library is safe" → assumption: maintainer is honest. What if the account is hijacked?
• "Logs aren't sensitive" → assumption: logs won't be read by an attacker. What if breached?
• "2FA = done" → assumption: 2FA can't be bypassed. What about phishing?
Every assumption is an attack surface. Every assumption you don't explicitly identify = a blind spot. The attacker doesn't attack your technology. The attacker attacks your assumptions.
---
An Honest Closing
I'm not writing this as "5 assumptions that get you hacked" - that's template thinking. I'm writing this because these 5 assumptions are what I find over and over in audits, and every time the assumption is falsified, the system falls. Not because the code is bad. Because the devs were confident the assumption was valid, when it was never tested.
The philosophy I bring to every line of code now: assume you're already hacked. What now? That's the "assume breach" mindset senior security engineers hold. You don't design to "keep attackers out" - you design to "reduce impact when the attacker is already in." Encrypt data at rest for when the DB is dumped. Short-lived tokens for when tokens are stolen. Immutable audit logs for when the attacker alters data. Rate limits for when credentials leak.
If you still think "security = implement bcrypt + JWT + OWASP checklist," you've only scratched the surface. The checklist is a starting point, not the goal. What matters: understand your assumptions, and know how to test if they're wrong.