cyber securitygetting startedvibecodingsolopreneur

Top security mistakes Saas founders make

You have developed some Saas service, but not sure is it safety to deploy it to live? Here is must have checklist, which will help you to avoid most common mistakes. Especially usefull for solopreneurs and vibe-coders.

V
Vladislavs K.
13 min read
Top security mistakes Saas founders make

You built your SaaS, it works, and you are ready to ship. Before you point the domain at production, take 30 minutes to run through this list. These are the 15 mistakes that get early-stage founders hacked — not because the attacks are sophisticated, but because the fixes are easy to skip when you are moving fast.


Mistake 1 — Secrets committed to git

You should be extra careful about where you store secrets for your application. Once committed to GitHub, there is no option to erase them from history — secrets stay forever, and the only fix is to rotate them immediately.

For an early-stage SaaS, there are a few secure and inexpensive ways to store them:

The fix: 1. Keep secrets in a local .env file on your production server — never committed to git. 2. Inject them at deploy time using GitHub Actions secrets, AWS Secrets Manager, or Doppler. If you already committed a secret — rotate it first, then clean up the history.


Mistake 2 — Missing authorization: BOLA / IDOR

Authentication (proving who you are) and authorization (proving you can access this resource) are different checks. Many early apps verify the first and skip the second.

Example: User A is authenticated and requests GET /api/invoices/1042. That invoice belongs to User B. If your code only checks "is the user logged in?" — User A gets User B's data.

The fix: Every time your code looks something up by ID, also check that the record belongs to the requesting user. Write a test where one user tries to access another user's resource — if it succeeds, you have the bug.


Mistake 3 — JWT accepted without signature verification

JWTs have three parts: header, payload, and signature. The signature is what makes them trustworthy. Some libraries, when misconfigured, accept alg: none — meaning no signature at all. An attacker can forge any token with any claims and your server will trust it.

The fix: Use a well-maintained JWT library and always verify the signature against a secret or public key. Explicitly reject the none algorithm. Never parse the payload before the signature is verified.


Mistake 4 — JWT stored in localStorage

localStorage is accessible to any JavaScript running on your page — including scripts injected via XSS. Storing your auth token there means a single XSS vulnerability exposes every active session.

The fix: Store tokens in HttpOnly cookies. The browser sends them automatically and JavaScript cannot read them, so XSS cannot steal them. Set Secure and SameSite=Strict alongside HttpOnly.


Mistake 5 — No rate limiting on auth endpoints

Without rate limiting, an attacker can try thousands of password combinations against your login endpoint in minutes, or flood your password reset to enumerate valid emails. Auth endpoints are the highest-value targets and the most common place to skip this protection.

The fix: Add rate limiting to /login, /register, /reset-password, and any OTP or email verification endpoint. A limit of 5–10 requests per minute per IP is a reasonable starting point. Use express-rate-limit, Nginx limit_req, or a gateway-level rule.


Mistake 6 — Overly permissive CORS

Access-Control-Allow-Origin: * with credentials is rejected by browsers — but a wildcard origin with no credentials still lets any site read your API responses. A sloppy allowlist that reflects the Origin header back unconditionally is just as dangerous.

The fix: Explicitly whitelist only the origins that need access (your frontend domain). In production, verify the allowlist is environment-specific — dev origins must never appear in the prod config.


Mistake 7 — SQL injection / DB corruption

String-concatenating user input into SQL queries lets attackers read your entire database, modify records, or drop tables. It is the oldest vulnerability in web development and still one of the most common.

Example: "SELECT * FROM users WHERE email = '" + email + "'" — if email is ' OR '1'='1, every user record is returned.

The fix: Always use parameterized queries or a well-maintained ORM. Never build query strings by concatenating user input. Validate and sanitize input at the system boundary even when using an ORM.


Mistake 8 — Secrets leaked in the client bundle

In Next.js, any environment variable prefixed with NEXT_PUBLIC_ is bundled into client-side JavaScript and visible to anyone who opens DevTools. Server-only variables can still leak if accidentally passed to a component or API response.

The fix: Never put secret keys, internal service URLs, or sensitive config in NEXT_PUBLIC_ variables. Server-only secrets belong in server components, API routes, or backend services — never in anything that reaches the browser.


Mistake 9 — Weak password hashing (MD5 / SHA-1)

MD5 and SHA-1 are fast — designed for checksums, not passwords. A modern GPU can compute billions of MD5 hashes per second, making brute-force attacks trivial. If your database leaks, weak hashes give attackers plaintext passwords within hours.

The fix: Use bcrypt, argon2, or scrypt with an appropriate cost factor (bcrypt work factor ≥ 12). These are intentionally slow and defeat brute-force at scale. Never use fast hashes for passwords.


Mistake 10 — Missing security headers

Security headers are free, take minutes to add, and prevent entire classes of attacks. Without them your app is vulnerable to clickjacking, MIME-sniffing exploits, and content injection.

The most important ones:

  • Content-Security-Policy — controls what scripts and resources the browser can load
  • X-Frame-Options: DENY — prevents clickjacking via iframes
  • X-Content-Type-Options: nosniff — stops MIME-type sniffing attacks
  • Strict-Transport-Security — forces HTTPS on return visits
  • Referrer-Policy: strict-origin-when-cross-origin — limits referrer data leakage

The fix: Add these in your web server config, Next.js next.config.js headers block, or a middleware layer. Check what is missing at securityheaders.com.


Mistake 11 — Management endpoints exposed publicly

Frameworks ship with debug and management endpoints: Spring Boot's /actuator, Node's /__health, admin panels at /admin, metrics at /metrics. Left public, they expose internals — environment variables, thread dumps, heap info — or allow dangerous operations with no auth required.

The fix: Block management endpoints at the network level (firewall, reverse proxy) or require authentication. In Spring Boot, restrict /actuator to an internal interface. Never expose these on the public port.


Mistake 12 — Verbose error messages in production

A stack trace in an API response tells an attacker your framework version, file structure, ORM, and sometimes the exact failing query. That is free reconnaissance.

The fix: Catch exceptions globally and return generic messages to clients ("Something went wrong"). Log the full stack trace server-side where only you can see it. Never forward raw exception details to API responses in production.


Mistake 13 — No CSRF protection on API routes

If your app uses cookie-based sessions, a malicious website can trick a logged-in user's browser into making state-changing requests to your API. The browser automatically attaches the session cookie — your server sees an authenticated request it never originated.

The fix: Use SameSite=Strict or SameSite=Lax on your session cookies — this is the simplest mitigation and supported in all modern browsers. For older compatibility, add CSRF token validation on all state-changing endpoints (POST, PUT, PATCH, DELETE).


Mistake 14 — Outdated or vulnerable dependencies

Old packages with known CVEs are documented, searchable, and exploitable by automated scanners. Lock files freeze specific versions, so you do not benefit from security patches unless you actively update.

The fix: Run npm audit and address at least the high/critical findings before launch. Set up Dependabot or Renovate for automated security update PRs. For Java, add the OWASP Dependency Check plugin to your CI pipeline.


Mistake 15 — No audit logging for sensitive operations

Without logs you cannot answer "who changed this?", "when was this deleted?", or "did that payment actually process?". When something goes wrong — and it will — you are debugging blind.

The fix: Log who did what and when for every sensitive operation: user creation/deletion, role changes, payment processing, data exports, and admin actions. Include actor ID, timestamp, affected resource, and action taken. Store logs where your application cannot overwrite them.


AI Prompt for a full security audit

Here is a ready-made prompt our team uses to run a thorough AI-assisted security review. Paste your codebase files into any capable AI tool alongside it:

You are a senior application security engineer with expertise across web, API, and cloud architectures. I need you to audit my codebase for security vulnerabilities. I will share files or directory listings as we go. You will adapt your analysis to whatever stack, language, and framework you find — do not assume anything before seeing the code. Your task Scan everything I share for security vulnerabilities. For each issue found, report: - File path and line number (if determinable) - Severity: CRITICAL / HIGH / MEDIUM / LOW - What exactly is wrong and why it is dangerous - A concrete code fix in the same language/framework as the original — not generic advice, actual corrected code --- ### CRITICAL 1. Secrets and credentials in source code Hardcoded passwords, API keys, tokens, private keys, connection strings, or secrets in any source file, config file, or committed environment file. 2. Broken object-level authorization (BOLA / IDOR) Any endpoint that fetches, updates, or deletes a resource by ID without verifying the requesting user owns that specific resource. 3. Authentication bypass Signature verification skipped or misconfigured on tokens. Accepting unsigned or weakly signed tokens. Missing authentication middleware on protected routes. 4. Sensitive data in insecure client storage Tokens or session identifiers stored in localStorage, sessionStorage, insecure cookies (missing HttpOnly, Secure, SameSite), or exposed in URLs. 5. No rate limiting on authentication and sensitive endpoints Login, registration, password reset, OTP, and payment endpoints with no rate limiting at any layer. --- ### HIGH 6. Injection vulnerabilities SQL, NoSQL, LDAP, OS command, or template injection. Any place where user-supplied input is concatenated into a query or command string. 7. Missing or insufficient input validation Endpoints that accept request bodies or parameters without schema validation, type checking, or constraint enforcement. 8. Overly permissive cross-origin policy CORS configured with wildcard origins combined with credentials, or overly broad origin allowlists that include non-production domains. 9. Sensitive data exposed to the client Server-side secrets, environment variables, internal configuration, or backend infrastructure details passed to or rendered in the client. 10. Weak or broken password / credential storage Passwords hashed with MD5, SHA-1, or any fast hash. Plaintext passwords. Missing work factor on bcrypt/argon2/scrypt. --- ### MEDIUM 11. Missing security headers HTTP responses lacking CSP, X-Frame-Options, X-Content-Type-Options, Strict-Transport-Security, or Referrer-Policy. 12. Verbose error messages and stack traces Error responses that include stack traces, framework internals, database schema details, file paths, or library versions visible to the client. 13. Missing CSRF protection State-changing endpoints using cookie-based sessions without CSRF token validation or SameSite cookies. 14. Overly privileged database or service accounts Application connecting to databases using admin, root, or superuser credentials. 15. Debug mode or development configuration in production Debug flags, verbose logging, development middleware, test routes, or seed data scripts present in production artifacts. --- ### LOW 16. Outdated or vulnerable dependencies Lock files containing packages with known CVEs or significantly outdated versions of security-critical libraries. 17. Missing audit logging on sensitive operations Functions that create, modify, or delete sensitive entities with no audit trail. 18. Missing field-level protection for PII Model fields for SSN, tax ID, bank account numbers, or health data without encryption at rest. 19. Unrestricted file upload File upload handlers that do not validate MIME type, enforce size limits, or store files in web-accessible directories. 20. No request size limits API endpoints without configured limits on request body size. --- ### EXTRA — Advanced checks 21. Account enumeration — Different responses based on whether an account exists on login/reset flows. 22. Insecure direct rendering of user content — User-supplied content rendered as raw HTML without sanitization (dangerouslySetInnerHTML, innerHTML, etc.). 23. Refresh token / session reuse vulnerability — Token refresh logic that does not invalidate the previous token on use. 24. Insecure deserialization — User-controlled data passed into deserialization functions without field restrictions. 25. Server-side request forgery (SSRF) — Endpoints that accept a URL from the user and make a server-side HTTP request to it without an allowlist. 26. Missing method-level authorization — Business logic functions that modify privileged data reachable without role/permission checks at the function level. 27. Sensitive data in logs — Logging statements that output passwords, tokens, full card numbers, SSNs, or PII. --- How to work through the audit Step 1 — Discovery. Before asking for any code, ask me about the application's purpose, tech stack, authentication mechanism, and existing security tooling. Step 2 — Config and dependency files first. Dependency manifests, lock files, environment/config files, web server config, auth config, CI/CD pipeline definitions. Step 3 — Application code by layer. Auth and authorization logic → API route handlers → service/business logic → data access layer → frontend → background jobs → utilities. Step 4 — Report as you go. After each batch of files, report findings immediately: FINDING #[N] Severity : [CRITICAL / HIGH / MEDIUM / LOW] File : [path/to/file.ext : line N] Issue : [one-line description] Why : [why this is dangerous] Vulnerable code: [exact snippet] Fixed code: [corrected code in the same language] Step 5 — Running tally. After every batch, update the count of CRITICAL / HIGH / MEDIUM / LOW findings. Step 6 — Final report. Executive summary, full findings sorted by severity, prioritized remediation plan with estimated effort, and positive observations on controls already in place. Ground rules: No false positives — flag uncertainty as "Needs manual review". No generic advice — every fix must match the exact language and framework. No assumptions — if you cannot determine whether a control exists without more files, ask for them first. Ready. Please start with Step 1 and ask me the discovery questions.

Conclusion

It is not possible to be 100% secure at the early stage, but implementing these 15 fixes will make your system significantly more resilient. When your SaaS starts getting real customers, consider bringing in a professional for a penetration test — the cost is far lower than recovering from a breach.

All systems can be attacked. Your goal is to make the attack not worth the attacker's time.

Ready to Start Your Affiliate Journey?

Join AFFY today and start building your successful affiliate program.