The most common early-stage security disasters aren't sophisticated hacks — they're simple mistakes: an API key accidentally made public, a database left open to the internet, or a form that accepts any input. These are all preventable.
If Cartara flagged this in your diff, you may have added API calls, authentication code, or database access that touches security-sensitive patterns.
The Most Important Rule: Secrets Out of Code
An API key is like a password that gives access to a paid service. If someone gets your OpenAI key, they can run up thousands of dollars in charges on your account. Bots scan GitHub constantly for leaked keys and use them within minutes.
Never put an API key directly in your code:
```ts
// ❌ Never do this
const apiKey = "sk-abc123yourrealkey";
```
Always use environment variables instead:
```ts
// ✅ Do this
const apiKey = process.env.OPENAI_API_KEY;
```
The actual key lives in a .env file that never gets committed to Git.
Setting Up Environment Variables
The .env File
Create a .env file in your project root and put secrets there:
```
OPENAI_API_KEY=sk-abc123yourrealkey
STRIPE_SECRET_KEY=sk_live_abc123
DATABASE_URL=postgresql://user:password@host/dbname
```
Immediately add .env to your .gitignore file:
```
.gitignore
.env
.env.local
.env.production
```
Commit a .env.example with the keys but fake values so teammates know what's needed:
```
.env.example
OPENAI_API_KEY=your-key-here
STRIPE_SECRET_KEY=your-key-here
DATABASE_URL=your-database-url-here
```
When You Deploy
Set environment variables through your hosting platform's dashboard (Vercel, Railway, Render, Fly.io all have an "Environment Variables" section). Never upload your .env file — use the dashboard instead.
If You Accidentally Commit a Secret
Git remembers forever. Even if you delete the file and recommit, the secret is still in the history.
- Immediately revoke and regenerate the key in the service's dashboard
- Assume the key is already compromised — act accordingly
Frontend vs. Backend: A Critical Distinction
This is the most important security concept to understand.
Frontend = code that runs in the user's browser. Anyone can see it, inspect it, and manipulate it.
Backend = code that runs on your server. Users can't see or touch it directly.
Secrets must only exist on the backend. If your API key is in frontend JavaScript, every user of your app has your API key.
```ts
// ❌ Never call paid APIs from frontend code
// This key is visible to anyone who opens DevTools
const response = await fetch('https://api.openai.com/v1/...', {
headers: { 'Authorization': Bearer ${process.env.NEXT_PUBLIC_OPENAI_KEY} }
});
// ✅ Create a backend API route instead
// frontend calls your route, your route calls OpenAI
const response = await fetch('/api/generate', { method: 'POST', body: ... });
```
Database Security
Don't leave your database open to the internet. Default settings on many managed databases allow connections from any IP. Restrict access to your app's server only.
Use separate databases for dev and production. A mistake in development should never affect real user data. Most platforms make this easy — create a separate development database.
Enable Row Level Security (RLS) in Supabase. Without it, any logged-in user can potentially read all other users' data. Ask your AI assistant: "Enable RLS on this table so users can only see their own rows."
Authentication: Use a Provider
Building your own login system is one of the most common sources of security vulnerabilities. Password handling, session management, and token security are complex and easy to get subtly wrong.
Use an existing auth provider:
- Clerk — easiest to set up, great UI components
- Supabase Auth — built-in if you're already using Supabase
- Auth0 — more powerful, slightly more setup
These handle passwords, sessions, OAuth (Google/GitHub login), and security updates automatically. See Authentication and Authorization.
Rate Limiting
If your app lets users trigger actions that cost you money (AI calls, emails, SMS), add rate limiting. Without it, a single user — or a bot — can drain your budget quickly.
```ts
// Basic rate limiting: max 10 requests per minute per user
const rateLimiter = new RateLimiter({ max: 10, windowMs: 60_000 });
app.post('/api/generate', rateLimiter.check, async (req, res) => {
// only reaches here if under the limit
});
```
What You'll See in Your Code
Security issues commonly surface in Cartara diffs as:
- An API key variable — check it reads from
process.env, not a hardcoded string - A new API route — ensure authentication is checked before processing
- Database queries — ensure user data is filtered by the current user's ID
- Frontend code calling external APIs — those calls should be in a backend route instead
If Something Goes Wrong
If you think a secret has been leaked:
- Revoke it immediately in the service dashboard — don't wait
- Generate a new key and update it in your hosting platform's environment variables
- Check your usage on the affected service for unexpected activity
- Review your git history for any other accidentally committed secrets
Speed matters. Keys found by bots are used within minutes.