Architecture Security Risks: Exposed Database Strings, Missing Rate Limiting & More
Your app's architecture decisions have security implications. Exposed connection strings, missing rate limits, and insecure data flows create vulnerabilities.
By Gabriel CA · Kraftwire Software
· 9 min readBeyond Code Vulnerabilities
Most security guides focus on code-level issues like XSS, injection, and exposed secrets. Those are important. But some of the most dangerous vulnerabilities come from how your application is put together, not from individual lines of code.
Architecture decisions determine how data flows through your system, who can access what, and what happens when things go wrong. Bad architecture creates vulnerabilities that no amount of code-level security can fix.
This guide covers the architectural security risks we find most often in AI-generated and vibe-coded applications.
Exposed Database Connection Strings
This is the most critical architecture issue we encounter. AI-generated apps frequently include database connection strings in frontend code or environment variables that get bundled into the client.
What a Connection String Reveals
A typical database connection string looks like this:
postgresql://admin:password123@db.example.com:5432/production
This single string contains:
**Username and password** for direct database access
**Server hostname** revealing your infrastructure
**Port number** confirming the service is accessible
**Database name** identifying the production data store
Why This Happens
AI coding tools generate the simplest working connection. When you ask for database functionality, the AI puts the connection string where the code needs it. If that code runs in the browser, the connection string is publicly visible.
Even in server-side code, connection strings often end up committed to git repositories. The AI generates a working configuration file, the developer commits it, and the credentials are now in the git history permanently.
The Damage Potential
With a database connection string, an attacker can:
**Read all data** in every table, including user credentials, payment information, and private messages
**Modify records** including changing prices, granting admin access, or altering transaction histories
**Delete entire databases** with a single DROP DATABASE command
**Use the server as a pivot point** to attack other systems on the same network
**Exfiltrate data slowly** over time, making detection difficult
How to Fix It
Never include database connection strings in frontend code
Store connection strings in environment variables that are only accessible server-side
Use connection pooling services (like Supabase, PgBouncer, or RDS Proxy) instead of direct connections
Rotate database passwords regularly
Use separate database users with limited permissions for different parts of your application
Enable SSL/TLS for all database connections
Missing Rate Limiting
Rate limiting controls how many requests a client can make in a given time period. Without it, your application is vulnerable to several types of attacks that exploit unlimited access.
Why AI-Generated Apps Skip Rate Limiting
AI coding tools focus on making features work. Rate limiting is an infrastructure concern that requires understanding traffic patterns, choosing appropriate limits, and implementing the limiting logic. AI models rarely add this automatically.
What Attackers Do Without Rate Limits
**Brute force attacks:** An attacker tries thousands of password combinations per minute against your login endpoint. Without rate limiting, they can test every common password in a dictionary within hours.
**Credential stuffing:** Attackers use lists of username/password combinations leaked from other services. They try each combination against your login page. If any of your users reused passwords from breached services, their accounts get compromised.
**API abuse:** An attacker calls your API endpoints thousands of times per second to:
Scrape your entire database through paginated list endpoints
Trigger expensive operations that run up your hosting costs
Overwhelm your server, causing it to become unresponsive for legitimate users
Burn through your third-party API quotas (OpenAI, Stripe, email services)
**Enumeration attacks:** Repeatedly calling endpoints to discover valid usernames, email addresses, coupon codes, or other enumerable resources.
How to Implement Rate Limiting
**At the application level:**
// Simple in-memory rate limiter
const rateLimits = new Map();
function rateLimit(ip: string, limit: number, windowMs: number): boolean {
const now = Date.now();
const windowStart = now - windowMs;
const requests = rateLimits.get(ip) || [];
const recentRequests = requests.filter(t => t > windowStart);
if (recentRequests.length >= limit) return false;
recentRequests.push(now);
rateLimits.set(ip, recentRequests);
return true;
}
**Recommended limits by endpoint type:**
Login: 5 attempts per minute per IP
Registration: 3 accounts per hour per IP
Password reset: 3 requests per hour per email
General API: 100 requests per minute per user
File upload: 10 uploads per hour per user
Search: 30 queries per minute per IP
At the infrastructure level:
Use Cloudflare, AWS WAF, or similar services for DDoS protection
Configure your CDN to rate limit by IP address
Use your hosting platform's built-in rate limiting features
Implement CAPTCHA on public-facing forms after failed attempts
Client-Side Security Reliance
One of the most dangerous architectural patterns is relying on the frontend for security decisions. AI-generated apps frequently implement access control in the UI rather than on the server.
How This Manifests
**Hiding UI elements instead of enforcing access control:**
// This is NOT security - it is UX
{user.role === "admin" && (
<AdminPanel />
)}
The admin panel component might be hidden from the UI, but the API endpoints it calls are still accessible. An attacker can call those endpoints directly without going through the UI.
**Client-side data filtering:**
// Fetching all data and filtering client-side
const allOrders = await fetch("/api/orders");
const myOrders = allOrders.filter(o => o.userId === currentUser.id);
The API returns all orders for all users. The frontend filters them. But the complete dataset travels over the network and is visible in browser DevTools.
**Frontend role checks:**
// Checking roles in localStorage - trivially bypassable
const isAdmin = localStorage.getItem("role") === "admin";
Anyone can open the browser console and type `localStorage.setItem("role", "admin")` to bypass this check.
The Fix: Server-Side Everything
Every security decision must happen on the server:
**Authentication** must be verified on every API request, not just when the page loads
**Authorization** must check permissions on the server before returning data or executing actions
**Data filtering** must happen in the database query, returning only records the user is authorized to see
**Input validation** must happen on the server, even if the frontend also validates
**Role checks** must query the database, not trust client-side storage
Missing Security Headers
Security headers are HTTP response headers that tell browsers how to handle your content. They prevent entire classes of attacks at the browser level. Most AI-generated apps ship with zero security headers configured.
Essential Headers Every App Needs
**Content-Security-Policy** prevents XSS by controlling which scripts can execute
**Strict-Transport-Security** forces HTTPS connections
**X-Frame-Options** prevents clickjacking by blocking iframe embedding
**X-Content-Type-Options** prevents MIME sniffing attacks
**Referrer-Policy** controls information leakage through referrer headers
**Permissions-Policy** restricts browser API access (camera, microphone, etc.)
Why Headers Matter Architecturally
Security headers are an infrastructure concern, not a code concern. They need to be configured at the web server or CDN level, and they protect your entire application regardless of what your code does. This makes them one of the highest-impact security improvements you can make.
Monolithic Secret Management
AI-generated apps often use a single API key or service account for everything. This means that if any part of the application is compromised, the attacker has access to everything.
The Principle of Least Privilege
Each component of your application should have access to only what it needs:
**Frontend** should only have publishable or anon keys
**API routes** should have their own keys with limited permissions
**Admin functions** should use separate credentials from user-facing functions
**Background jobs** should have their own service accounts
**Different environments** (development, staging, production) should use different keys
How to Implement Proper Secret Management
Use your platform's secret management (Vercel Environment Variables, Supabase Secrets)
Create separate API keys for each service integration
Use the most restrictive permissions possible for each key
Set up billing alerts on all third-party services
Rotate keys regularly and after any suspected compromise
Never share keys between environments
No Monitoring or Logging
AI-generated apps almost never include security monitoring. Without logging, you cannot detect attacks, investigate incidents, or understand how your application is being used.
What to Log
**Authentication events** (login attempts, failures, password changes)
**Authorization failures** (users trying to access resources they should not)
**Unusual patterns** (high request rates, unusual hours, geographic anomalies)
**Error rates** (sudden spikes might indicate an attack)
**Data access patterns** (bulk downloads, unusual queries)
What Not to Log
Passwords (even failed ones)
Full credit card numbers
Session tokens
Personal health information
Any data subject to regulatory restrictions
Architecture Security Checklist
No database connection strings in frontend code or client bundles
Rate limiting configured on all sensitive endpoints
All security decisions enforced server-side, not client-side
Security headers configured on all responses
Separate API keys and credentials for each service and environment
Authentication verified on every API request
Authorization checked before every data operation
Monitoring and logging implemented for security events
Error messages do not reveal architecture details
File uploads validated and sandboxed
Scan Your Architecture
SimplyScan checks for architecture-level vulnerabilities including exposed connection strings, missing rate limiting indicators, client-side security reliance, and missing security headers.
[Scan your app now](/)
Related Guides
[Performance as a Security Risk](/blog/performance-security-guide)
[CSRF Protection and Security Headers](/blog/csrf-security-headers-guide)
[Supabase Security Checklist](/blog/supabase-security-checklist)
[Security Audit Checklist](/blog/security-audit-checklist)