2026 Infra Guide for AI Tool Builders - Part 3: The 18 Common Security Mistakes and How to Fix Them
Published: January 20, 2026
You deploy an AI tool for a client. Next morning, your OpenAI credits are gone. Vanished. You set up a data dashboard. Log in the next day - tables deleted. You launch your own server. Two weeks later, CPU spikes to 100%, server crashes. Bot attack. Thousands of login attempts from rotating proxies.
If you're coming from a data science background like me, security wasn't part of the training. You learned models and pipelines, not fail2ban configurations and IP hashing. But two years into building AI tools for small businesses, I've made most of these mistakes. Some I caught early. Others cost me money and sleepless nights.
Security is a vast topic with endless nuances specific to each app. Not a comprehensive guide. These are the 18 mandatory checkpoints I learned the hard way - the ones that will get you if you skip them. Coming from analytics into full-stack development, these were the gaps I didn't know existed until they hit me.
In Part 1 I covered AI Coders - How Claude Code handles the full deployment chain. Part 2 was on Deployment & Hosting - Hetzner, Coolify, Vercel, Cloudflare. Part 3 covers essential security.
I've made most of these mistakes. Lost money, lost sleep, scrambled to fix production issues at 2am. But one disaster I haven't faced: complete server hijack.
When I set up my Hetzner VPS the first time almost two years back, client tools would be hosted there. That anxiety made me extra careful. I watched YouTube guides multiple times. Asked Chat GPT to explain every step. Verified each configuration before moving forward. Learned about SSH keys, disabling password login, creating non-root users. I knew client data would live on this server, so I followed instructions properly
That caution saved me. Bot attacks came within hours of going live - I watched fail2ban logs fill up with banned IPs. But they never got in. The server was locked down from day one because I was too anxious to skip steps.
Everything else on this list? I learned by breaking it first.
Server Security
Mistake 1: You Set a Root Password and Stopped There
When you spin up a Hetzner server, you set a root password during setup. You get SSH keys. Then what?
The mistake: leaving password access enabled for root login.
The moment your server goes live, bots start scanning. Immediately. If your password is something like 'Admin1234' or 'P@ssw0rd' or any common variant, you're compromised in hours. Even with strong passwords, brute-force attacks will try millions of combinations.
The fix: Disable password authentication entirely. SSH keys only. No password access to the server. I set this up from day one. The bots can keep attacking - they're locked out at the door.
Mistake 2: SSH Access Without fail2ban
Even with SSH keys only, bots will hammer your server with login attempts. They don't know your auth method - they just keep trying. This floods your logs and can overwhelm the server itself.
The mistake: not setting up fail2ban.
fail2ban monitors failed login attempts and bans IPs after a threshold. You configure how many bad attempts trigger a ban and how long the ban lasts.
My current fail2ban settings:
- maxretry: 5 attempts
- findtime: 3600 seconds (1-hour window)
- bantime: 86400 seconds (24-hour ban)
I learned this after getting hit. Server load spiked, had to restart. I had fail2ban running but with loose parameters (10 attempts, 10-minute bans). The attack didn't stop. Tightened the config to what you see above.
Results since tightening:
- Currently banned: 157 IPs
- Total banned since last restart (one week ago): 1,223 IPs
- Total failed attempts blocked: 6,082
- No issues since.
Mistake 3: Leaving Unnecessary Ports Open
By default, many ports might be open on your server. You only need three: 22 (SSH), 80 (HTTP), 443 (HTTPS).
The mistake: not configuring your firewall to close everything else.
At the Hetzner firewall level, I restrict to these three ports only. Nothing else is accessible from outside. This is basic hygiene but easy to overlook if you're new to server management.
Mistake 4: Running Everything as Root
When you first set up the server, you're logged in as root. The temptation is to just keep using root for everything.
The mistake: not creating a non-root user with sudo access.
If a vulnerability gets exploited, an attacker running as root owns your entire system. With a non-root user, they're limited to that user's permissions.
The fix: Create a non-root user, add them to the sudo group, disable root password login. Run your apps under this user, not root.
Did this during initial setup following RJ Reynolds' wonderful YouTube guide. These are standard practices but not obvious if you've never managed a server before.
Hetzner + Coolify Setup Guide - Covers Hetzner + Coolify security hardening, server setup, installation, deployment workflows.
Frontend Security
Mistake 5: API Keys in Your React Code
This is the big one. Your OpenAI credits vanish overnight because your API key was sitting in your React app, visible to anyone who opens the browser console.
The mistake: putting API keys in frontend code or public environment variables.
In React, anything with REACT_APP_ or VITE_ or NEXT_PUBLIC_ is bundled into your JavaScript and sent to the browser. Even if you don't console.log it, anyone can inspect the network tab and see your API calls - including the key.
If you push this to a public GitHub repo, it gets scanned immediately. Your API key is compromised in hours, sometimes minutes.
The fix: All sensitive API calls must go through a backend or serverless function.
I use Vercel serverless functions for this. The API key lives in the backend environment (not exposed to the browser). The frontend calls your serverless endpoint, which then calls OpenAI/Anthropic/whatever. The user never sees the actual API key.
This works even for simple HTML/JavaScript apps - you don't need a full React setup. Create a serverless function in Vercel, put your API key there, call it from your frontend.
Your AI coder will likely warn you about this. Listen to it.
Mistake 6: No .gitignore - Sensitive Files in Git History
Early on, I didn't fully understand .gitignore. I'd start a new project, code for days, then realize I'd committed API tokens, client documents, or config files to the repo.
Private repos feel safe until they're not. You make them public later. You share access with a contractor. Someone forks it. And even if you delete the file afterward, it's still there - sitting in git commit history, permanently.
Scary Learning I had client-specific data files sitting in a repo. Realized too late they'd been committed across multiple commits. Had to scrub git history and restart the repo clean.
The mistake: not setting up .gitignore as the very first action in any new project.
Now it's automatic. New directory, new project - first thing I do is ask Claude Code to add .gitignore with standard Python/Node patterns plus project-specific additions. Client data, test datasets - none of it touches git.
.gitignore is absolutely essential. It's the first line of defense against accidentally exposing what should stay private.
Mistake 7: Thinking CORS Provides Complete Security
You enable CORS, restrict your API to your domain, and assume you're protected.
The mistake: treating CORS as your primary security layer.
CORS only controls browser requests. Anyone can still write a Python script or curl command to hit your API directly. If you're relying solely on CORS without token authentication, your endpoints are wide open.
CORS is a helpful additional layer - it prevents random websites from calling your API from their browser. But even with CORS restricted to your domain and API keys hidden in serverless functions, someone can still use curl or a Python script to hit your serverless endpoint directly - bypassing CORS entirely and using your serverless as a free proxy to your backend. Rate limiting slows this abuse but doesn't prevent it. For sensitive endpoints, you need JWT validation at the serverless layer before forwarding requests to your backend.
Not enough on its own. Token-based authentication or API keys are mandatory, with CORS as a supplementary control.
I enable CORS for client tools where the frontend domain is fixed. But the API always requires tokens regardless.
Mistake 8: Not Monitoring Your API Endpoints
You deploy an app. Things seem fine. Then you check your logs a week later and discover thousands of failed requests, error spikes, or unusual traffic patterns.
The mistake: no monitoring or logging infrastructure.
I built a custom monitoring system for this. It's not enterprise-grade, but it works:
- Created a PyPI package: tigzig-api-monitor (published at pypi.org)
- FastAPI middleware that logs every request
- Logs: app name, endpoint, HTTP method, status code, response time, hashed IP (not raw IP - privacy by design), user agent, origin, referer
- All logs go to a central Neon database
- Built a simple React dashboard on Vercel to view API success/failure rates, response times, endpoint usage
Why a package? I was copying the same logging code across 30+ backends. Package means import once, use everywhere.
The logging is non-blocking and async. If logging fails, the API still works. And I hash IPs before storage - can't reverse-engineer the original IP. Privacy by design.
I monitor daily for errors and unusual traffic, especially on client endpoints (where the logging is more detailed with personal identifiers). Use the same app for server space and container health check - shows which containers are running and current disk usage.
Mistake 9: Exposing Backend URLs in Frontend Code
Early on, my frontend made direct API calls to the backend. The URL was hardcoded: https://api.mybackend.com/endpoint. The API key was in backend environment variables. Rate limits were in place.
Then I migrated to a serverless proxy architecture. Frontend now calls /api/query on its own domain. Vercel serverless forwards to the actual backend with the API key. More secure, cleaner separation.
The problem: I forgot to remove the old hardcoded backend URL from the frontend code. It wasn't being used anymore - calls went through the serverless proxy. But the URL was still sitting there in the source code.
Anyone opening DevTools could see it. Old implementation, leftover code, but still visible.
The Risk: even with API key protection and serverless proxy, exposing backend URLs reveals:
- Your infrastructure endpoints (attack surface mapping)
- Which services/providers you use
- Endpoints to target for vulnerability testing
- URLs that can be used in web scraping techniques
This is how attackers map your infrastructure. You've handed them the blueprint.
The fix: audit frontend code after architecture changes. When you migrate to serverless proxy, remove old backend URLs entirely. Frontend should only know about its own domain endpoints.
This came up during a Claude Code security audit. I thought: "Calls go through serverless now, old URL doesn't matter." The audit pointed out: "The URL is documentation for attackers."
Now as default: all API calls route through serverless. Backend URLs stay server-side only. When refactoring, I explicitly ask Claude Code: "What's leftover in frontend that shouldn't be there?"
Web scraping tools work this way - find exposed backend URLs, replay requests with captured headers. Even if the URL isn't actively used, its presence is information leakage.
I do this for all client apps without fail. Some of my earlier public apps still have exposed URLs and other gaps (but at backend I still have rate limits) New builds: backend URLs hidden by default.
Backend/API Security
Mistake 10: No Rate Limiting on Public Endpoints
Your API is public or low-security (no strict auth). Bots discover it. Suddenly you're getting hammered with thousands of requests.
The mistake: not implementing rate limits.
Even with a low-risk API, high request volume can overwhelm your server or rack up cloud costs.
The fix: Rate limiting is mandatory hygiene. I use SlowAPI for FastAPI backends.
For fully public endpoints, I set rate limits between 50-200 requests per minute depending on the endpoint's compute cost. This prevents abuse while allowing legitimate usage.
Rate limiting alone won't stop determined attackers, but it stops casual abuse and automated scrapers from breaking your infrastructure.
Mistake 11: No Token Authentication on Sensitive Endpoints
You build an internal tool or client dashboard. You skip authentication because 'only we will use it' or 'it's not public.'
The mistake: assuming obscurity equals security.
If the URL is accessible, it will be found. Bots scan, search engines index, someone shares the link accidentally.
The fix: All sensitive endpoints require token authentication. The token can be a simple API key, a time-based JWT, or full OAuth depending on complexity.
For proprietary tools and client work, authentication is mandatory. I default to Auth0 because it provides robust functionality with a generous free tier. Clerk , Supabase & Neon Auth are simpler to set up (faster with AI coders), but Auth0 gives more control and customization options. Where the requirements are simpler and I just want simpler access control, then I go with Clerk - AI Coders can set that up for you in minutes.
For long-running tasks (over 5 minutes) where Vercel serverless won't work, I use time-based tokens. The backend generates a temporary token for that specific task, which the frontend uses for polling updates. The token expires after the task completes.
Mistake 12: Thinking Adding Auth Means You're Secure
All my clients tools are Auth protected. However, Auth on the frontend is not enough. It needs to be validated on the server side too.
I made this mistake twice before it stuck.
First time: DuckDB admin dashboard with Auth0. Frontend had login protection. But the Vercel serverless proxy wasn't validating JWT tokens.
The architecture: Frontend calls /api/duckdb on my frontend domain. This serverless function acts as a proxy - it holds the secret backend API key and forwards requests to the actual FastAPI backend (whose URL is never exposed to the browser).
The problem: the proxy accepted any request without checking if the user was actually logged in.
Anyone could open DevTools, see the proxy endpoint, run: curl -X DELETE "https://my-frontend.app/api/duckdb?action=admin-delete&filename=important.duckdb"
Files gone as no valid JWT required. The proxy blindly forwarded the request using its server-side API key. The attacker never needed to know the backend URL - they just used my own proxy as an ready made entry point.
I fixed it. Added JWT verification to the serverless proxy - now it validates Auth0 tokens before forwarding anything to the backend. Thought I learned the lesson.
Weeks later, different app. This time with Clerk auth instead of Auth0. Built a serverless proxy endpoint for SQL queries. Frontend protected with Clerk login. The proxy wasn't verifying Clerk tokens - just forwarding requests to the backend with its API key.
Had Claude Code do a security audit, and it caught it: Anyone could open DevTools, copy the API call to the proxy, replay it with curl. The proxy would forward SQL queries to the backend because the attacker hit the right endpoint - no token validation required.
Same mistake, different implementation. The pattern: assuming frontend auth automatically protects server-side operations.
The mistake: Frontend auth controls what users see. Server-side validation controls what they can actually do. If your serverless proxy or backend doesn't verify tokens, anyone who discovers the endpoint bypasses the UI entirely.
The fix: JWT/token verification at your server layer. For Auth0: validate JWT against Auth0's public keys. For Clerk: verify Clerk session tokens. For any auth system: server must independently verify identity.
Architecture note: My setup uses a serverless proxy layer (Vercel functions) between frontend and backend. Auth validation happens at the proxy - it checks JWT tokens before forwarding requests to the actual FastAPI backend. The backend validates an API key to ensure requests are coming from my proxy, not directly from the internet.
This is basic protection: frontend login + proxy auth check + backend API key. For my scale and threat model, this is sufficient.
Advanced protection would add JWT validation at the backend layer too - so even if the proxy is compromised or the backend accidentally exposed, auth is still enforced. High-security apps do this. For small business tools with limited attack surface, proxy-level validation is good.
The key: your backend should not be publicly accessible. If your architecture exposes the backend directly to the internet, you need auth validation there too, not just at the proxy.
This pattern repeated because I was moving fast, building multiple apps in parallel. Frontend had visible login gates. Server-side auth is invisible - easy to forget, easy to skip during rapid development.
Both cases caught by Claude Code during security audits before relese. Neither was exploited.
Now my process also includes a manual check: After security audit, I open the app, go to Network tab in DevTools, interact with the UI. Watch what API calls fire, what data gets sent, what responses come back. If I see tokens, credentials, or sensitive data in clear text - something's wrong. If I can copy an API call and replay it without auth - something's wrong.
The AI audit is critical. The Network tab shows reality. Both are needed. The concept - 'Inspect what you expect'
Mistake 13: Not Sanitizing SQL Query Endpoints
You build a text-to-SQL tool or a database query interface. Users can run SELECT queries to analyze data.
The mistake: not validating and sanitizing those queries.
Even if you restrict to read-only access, users (or attackers) might try injection attacks or destructive queries.
The fix: For SQL query endpoints, I sanitize inputs by:
- Rejecting queries containing DELETE, DROP, INSERT, UPDATE, ALTER
- Using a sanity checker to strip dangerous SQL keywords
- Providing a separate protected endpoint for write access (if needed), with stricter auth
This is the only place I do heavy request sanitization. For other endpoints, I rely on auth and rate limiting.
Mistake 14: Exposing Stack Traces and Database Errors
Your API throws an error. The response includes a trace, database connection strings, or internal paths.
The mistake: leaking sensitive information in error messages.
Attackers use error messages to map your infrastructure. A PostgreSQL connection error tells them you're using Postgres. A file path error reveals your directory structure.
The fix: Sanitized error responses. Log the full error internally for debugging, but return generic messages to users: 'An error occurred' or 'Invalid request' - Nothing that reveals your backend architecture.
I set this up in FastAPI exception handlers. Full stack trace goes to logs. User gets a clean error.
Database Security
Mistake 15: Using Admin Credentials in Production
You set up a new database. Test with admin credentials. Deploy to production. Forget to change the credentials.
The mistake: admin keys in production apps.
If those credentials get exposed (frontend leak, compromised server), attackers have full write and delete access.
The fix: The moment you create a database, create separate credentials:
- One set with full write access for your backend
- One set with read-only access for dashboards or user-facing tools
Never use admin credentials outside of database management tasks. Store the admin key separately and only use it for schema changes or emergency fixes.
I learned this early. I keep Admin credentials in a GPG-encrypted secrets file. And for production apps, I use limited-access credentials only.
Mistake 16: Database Credentials in Frontend or Public Environment Variables
You build a dashboard. You make database calls directly from the frontend using connection strings stored in public env vars (REACT_APP_, NEXT_PUBLIC_).
The mistake: putting database credentials anywhere the browser can access them.
This is the same as Mistake 5 but worse - instead of losing API credits, you lose your entire database.
The fix: All database calls must go through a backend or serverless function. The connection string lives only in backend environment variables, never exposed to the frontend.
For text-to-SQL tools, I use a separate data connection layer that sits entirely in the backend. The frontend sends natural language queries, the backend generates SQL, executes it, and returns results. The frontend never sees the database credentials.
Mistake 17: No Connection Pooling
Your app makes repeated database calls. Every call opens a new connection. Under load, you hit connection limits and the database refuses new connections.
The mistake: not implementing connection pooling.
I initially thought connection pooling was just for efficiency. Turns out it also prevents connection exhaustion attacks (accidental or deliberate).
The fix: Use connection pooling with limits on active and idle connections. Monitor the pool to ensure connections are being released properly.
I added this to all my backends after running into connection limit errors during testing. It's now a standard part of my setup.
Security & AI
Mistake 18: Trusting AI Coders to Handle Security Without Verification
Claude Code builds my entire stack. Part 1 of this series is about how it handles everything from React frontends to database migrations. So you'd think security would be covered, right?
Partially.
AI coders are excellent at flagging obvious security gaps. Claude Code consistently warns me:
- "Don't put API keys in frontend code"
- "Database credentials should be in backend environment variables"
- "This needs token authentication"
It catches the basic stuff reliably.
But it misses nuanced gaps. The auth-on-frontend-not-backend issue? Claude Code built that. The missing .gitignore in a new project? Created the repo without it. SQL sanitization that looked complete but had edge case holes? Passed initial review.
The mistake: assuming AI coders handle security comprehensively without independent verification.
They don't. They're fast, they're capable, but they're not infallible on security nuances.
The fix: security audit before every deployment, especially client apps.
Here's my current process:
- Fresh session with Claude Code (not in the coding context)
- Ask it to audit the codebase for vulnerabilities
- Specific prompt: "Find attack vectors. Assume someone is trying to exploit this app. Check auth flows, API endpoints, data handling, exposed credentials."
- Request detailed vulnerability report
Results: 7 out of 10 times it finds issues I missed. 2 out of 10 times it finds serious ones.
That DuckDB admin endpoint with no backend auth? Found during one of these audits.
AI coders make everything faster. But doesn't mean that you disregard security. They make security audits faster - but you still need to get it done.
This is the hugest lesson behind the earlier 18 mistakes. I rely hugely on AI coders. The solution isn't using AI Coders less - it's treating AI output the same way you'd treat a team member's code: inspect what you expect
What's Working, What's Next
This list covers basic hygiene. I haven't implemented:
- Malware scanning on file uploads (just type and size checks)
- CVE tracking or automated dependency scanning
- Backup restore testing (Hetzner backup is enabled but never tested recovery)
- API versioning (I just break old versions - small user base makes this viable)
Some of these are on the to-do list. Others are not priorities yet given the scale I operate at.
The 18 mistakes above are the bare minimum. They are the difference between waking up to a working system or waking up to vanished credits, deleted data, and a crashed server.
Security is never complete. Every few weeks I learn something new. Another gap, another attack vector I had not considered.
Major organizations, private and government, with dedicated security teams get breached:
Yahoo (2013) - 3 billion accounts compromised. Every single Yahoo account that existed in 2013. Names, emails, passwords, security questions. Attributed to state-sponsored actors. Four years later, Verizon knocked $350 million off Yahoo's acquisition price because of it.
Equifax (2017) - 147.9 million Americans. Social Security numbers, birth dates, addresses, driver's licenses. Settlement: $575 million. Total cost to Equifax: $1.4 billion. Attributed to Chinese military hackers.
OPM (2015) - 22.1 million U.S. federal employees and contractors. Security clearance background checks, fingerprints, personnel files. Compromised intelligence officers. Called one of the worst breaches in U.S. history. Attributed to Chinese state-sponsored hackers.
Marriott/Starwood (2014-2018) - 500 million guest records. Passport numbers, credit cards, travel history. Breach ran undetected for 4 years. £18.4 million fine from UK regulators. Attributed to Chinese intelligence.
Capital One (2019) - 100 million customers (30% of U.S. population). Cloud misconfiguration on AWS. Credit scores, Social Security numbers, bank accounts exposed. $80 million fine.
SolarWinds (2020) - Compromised U.S. Treasury, Commerce, Defense, Homeland Security, State, Justice, Energy departments. ~18,000 organizations affected. Attributed to Russian state-sponsored actors.
If a dedicated attacker with the right competence targets your system specifically, odds are they will find a way in.
That does not mean you skip the basics. You do the mandatory minimum. SSH-only access, fail2ban, token auth, rate limiting, credentials out of frontend. These 18 checkpoints will not stop a nation-state actor. They will stop the 99% of attacks that are opportunistic, automated, scanning for easy targets.
Security is ongoing work, not a one time checklist thing. I continue learning. These 18 mistakes are like core level, not the complete picture. But they got to be done.
Two years into this, coming from a data science background, the lesson is clear: security is basic infrastructure hygiene. You can skip advanced measures depending on your riskprofile. You cannot skip the mandatory minimum. Next in this series: More on Agent setups, LLM Choices, internal tools, build vs buy decisions.
Resources
- Part 1: AI Coders - How Claude Code handles the full deployment chain
- Part 2: Deployment & Hosting - Hetzner, Coolify, Vercel, Cloudflare
- Hetzner + Coolify Setup Guide - RJ Reynolds' guide covering security hardening, server setup, installation, deployment workflows