startupbricks logo

Startupbricks

Secure Vibe Coding: Build AI Apps Without Leaking Secrets

Secure Vibe Coding: Build AI Apps Without Leaking Secrets

2025-01-20
9 min read
AI & Modern Stacks

The notification came at 3:47 AM.

"Our API keys are exposed," the message read. "Someone has been using our OpenAI account for the past six hours. The bill is $4,200."

Raj stared at his phone in horror. He was a solo founder, three weeks from launch, running on fumes and determination. That $4.2K charge wasn't just money—it was his runway.

How did this happen?

He checked his GitHub repository. There it was, in a commit from last Tuesday: OPENAI_API_KEY="sk-..." committed directly to the public repo.

Sixteen minutes after the commit, the first API call. Within an hour, automated bots had found it. By morning, nearly 50,000 requests had drained his account.

Raj's story isn't unique. Every day, developers—just like you—expose secrets to GitHub, and every day, automated scanners find them within minutes.

This is the dark side of vibe coding. When you're moving fast, it's easy to make mistakes that cost thousands of dollars, compromise user data, or destroy trust with your customers.

The solution isn't to slow down. It's to build security into your vibe coding workflow from day one.


Why Vibe Coding Creates Security Risks

Vibe coding changes the fundamental relationship between developer and code. When you're writing every line, you develop an intimate knowledge of what your code does—and doesn't do. You know where the secrets are, because you put them there.

When AI writes the code, that knowledge disappears. The AI might generate a file that contains an API key in plain text. It might import a module that logs environment variables. It might make an HTTP request that exposes credentials in headers.

The problem isn't that AI is careless. It's that AI optimizes for the wrong thing: getting the code to work, not getting the code to be secure.

The Four Horsemen of Vibe Coding Security

1. Credential Exposure

AI generates code that includes hardcoded secrets:

javascript
// ❌ DANGEROUS: AI might generate this
const apiKey = "sk-ant-api03-abc123...";
const client = new OpenAI({ apiKey });
// ✅ SAFE: Load from environment
const apiKey = process.env.OPENAI_API_KEY;
const client = new OpenAI({ apiKey });
**2. Verbose Logging**
AI might add logging statements that reveal sensitive data:
```javascript
// ❌ DANGEROUS: Exposes credentials in logs
console.log("API call with key:", apiKey);
// ✅ SAFE: Never log secrets
console.log("API call initiated");
**3. Insecure Defaults**
AI might generate code with insecure configurations:
```javascript
// ❌ DANGEROUS: Development settings in production
app.use(cors({ origin: true, credentials: true }));
// ✅ SAFE: Explicit whitelist
app.use(
cors({
origin: process.env.ALLOWED_ORIGINS?.split(",") || [],
credentials: true,
})
);
**4. Missing Validation**
AI might skip input validation, creating security vulnerabilities:
```javascript
// ❌ DANGEROUS: No validation
const userId = req.params.id;
const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`);
// ✅ SAFE: Proper validation
const userId = parseInt(req.params.id, 10);
if (isNaN(userId)) {
throw new Error("Invalid user ID");
}
const user = await db.query("SELECT * FROM users WHERE id = $1", [userId]);

The Real Cost of Exposed Secrets

According to research from GitHub's security team, exposed secrets are found in 2% of all commits to public repositories. Of those, 20% are discovered by malicious actors within an hour.

The consequences are severe:

  • Financial: Unauthorized API usage can cost thousands of dollars quickly
  • Data: Exposed database credentials can lead to data breaches
  • Legal: Compliance violations (GDPR, HIPAA, SOC2) can result in fines
  • Reputational: Breaches destroy customer trust
  • Operational: Incident response takes time away from building

Environment Variables Done Right

Environment variables are the first line of defense for secrets in vibe coding. But they're only effective when used correctly.

The .env File Pattern

The standard approach uses a .env file to store secrets:

bash
# .env (NEVER commit this file!)
OPENAI_API_KEY="sk-ant-api03-..."
ANTHROPIC_API_KEY="sk-ant-..."
DATABASE_URL="postgresql://..."
STRIPE_SECRET_KEY="sk_live_..."
JWT_SECRET="your-super-secret-key"

Critical rules for .env files:

  1. Never commit .env to version control
  2. Always ignore .env in .gitignore:
gitignore
# .gitignore
.env
.env.local
.env.*.local
  1. Create a template for other developers:
bash
# .env.example (commit this!)
# Copy this to .env and fill in the values
OPENAI_API_KEY=""
ANTHROPIC_API_KEY=""
DATABASE_URL=""
STRIPE_SECRET_KEY=""
JWT_SECRET=""

Using dotenv Properly

Install dotenv in your project:

bash
npm install dotenv

Load environment variables at the top of your entry file:

javascript
// index.js or app.js - load FIRST
import dotenv from "dotenv";
dotenv.config();
// Now you can use process.env.ANY_VARIABLE
const apiKey = process.env.OPENAI_API_KEY;

For TypeScript projects, use dotenv-flow:

bash
npm install dotenv-flow
typescript
import dotenvFlow from "dotenv-flow";
dotenvFlow.config();
// Type-safe access
const config = {
apiKey: process.env.OPENAI_API_KEY,
databaseUrl: process.env.DATABASE_URL,
};

Environment-Specific Variables

Different environments need different secrets:

bash
# .env.development (local)
NODE_ENV=development
DEBUG=true
DATABASE_URL="postgresql://localhost:5432/myapp"
# .env.test (CI/CD)
NODE_ENV=test
DATABASE_URL="postgresql://test:test@localhost:5432/testdb"
# .env.production
NODE_ENV=production
DEBUG=false
DATABASE_URL="postgresql://prod-db.example.com:5432/myapp"

Loading the right environment:

javascript
// Load based on NODE_ENV
import dotenvFlow from "dotenv-flow";
const env = process.env.NODE_ENV || "development";
dotenvFlow.config({ node_env: env });

Shell Export Commands

For CLI tools and development, use shell exports:

bash
# Set environment variables for a session
export OPENAI_API_KEY="sk-ant-..."
export ANTHROPIC_API_KEY="sk-ant-..."
# Run with specific variables
OPENAI_API_KEY="sk-ant-..." npx vibekit claude "Build the feature"
# Add to your shell profile for persistence
echo 'export OPENAI_API_KEY="sk-ant-..."' >> ~/.zshrc
source ~/.zshrc

Vite Environment Variables

If you're using Vite, environment variables must be prefixed with VITE_:

bash
# .env
VITE_API_URL="https://api.yourapp.com"
OPENAI_API_KEY="sk-ant-..." # Server-side only
javascript
// In client-side code, only VITE_ variables are accessible
console.log(import.meta.env.VITE_API_URL); // Works
console.log(import.meta.env.OPENAI_API_KEY); // Undefined!

Secrets Management Tools: Beyond .env Files

For teams and production deployments, .env files aren't enough. You need centralized secrets management.

Infisical: Open-Source Secrets Management

Infisical provides a complete secrets management platform that's developer-friendly and open-source.

Why Infisical stands out:

  • Secret synchronization across environments
  • End-to-end encryption for security
  • Audit logs for compliance
  • CLI tool for automation
  • GitOps integration for workflows

Installation and setup:

bash
# Install CLI
npm install -g infisical
# Initialize in your project
cd your-project
infisical init
# Login
infisical login

Managing secrets via CLI:

bash
# Set a secret
infisical secrets set "OPENAI_API_KEY" "sk-ant-..."
# Get a secret
infisical secrets get "OPENAI_API_KEY"
# List all secrets
infisical secrets ls
# Inject secrets into .env
infisical run -- env > .env && source .env
# Use in scripts
infisical run -- npm run dev

For Node.js projects:

javascript
import { InfisicalClient } from "@infisical/sdk";
const infisical = new InfisicalClient({
clientId: process.env.INFISICAL_CLIENT_ID,
clientSecret: process.env.INFISICAL_CLIENT_SECRET,
});
async function getSecret() {
const secret = await infisical.getSecret({
environment: "production",
path: "/",
secretName: "OPENAI_API_KEY",
});
return secret.secretValue;
}

Infisical with Docker/Kubernetes:

yaml
# docker-compose.yml
services:
app:
image: your-app
env_file:
- .env.production
# Or use Infisical sidecar

Pricing:

  • Free tier for small teams
  • Pro plans for growing teams - see Infisical pricing
  • Enterprise for large organizations

1Password CLI for Developers

1Password isn't just for password management—its CLI is powerful for developer secrets.

Setup:

bash
# Install 1Password CLI
brew install 1password-cli
# Sign in
op signin
# Connect to your 1Password vault

Using 1Password for secrets:

bash
# Get a secret
op item get "API Keys" --fields "OpenAI API Key" --format json
# Use in scripts
eval $(op env --signin)
export OPENAI_API_KEY=$(op item get "API Keys" --field "OpenAI API Key")

1Password with environment files:

bash
# Generate .env from 1Password
op inject -o .env

Best for: Teams already using 1Password, organizations with strict security requirements

HashiCorp Vault for Enterprise

HashiCorp Vault is the enterprise standard for secrets management.

Key features:

  • Centralized secret storage
  • Fine-grained access policies
  • Dynamic secrets
  • Encryption as a service
  • Extensive audit logging

Basic usage:

bash
# Start Vault in development mode
vault server -dev
# Set a secret
vault kv put secret/openai/api-key value="sk-ant-..."
# Read a secret
vault kv get secret/openai/api-key
# Use in applications
export OPENAI_API_KEY=$(vault kv get -field=value secret/openai/api-key)

For Kubernetes:

yaml
# Use Vault Secrets Operator
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultSecret
metadata:
name: openai-api-key
spec:
vaultRef:
path: secret/data/openai/api-key
destination:
name: openai-api-key
create: true

Best for: Large enterprises, regulated industries, organizations with existing HashiCorp infrastructure

GitHub Secrets for CI/CD

GitHub Actions secrets keep your CI/CD pipeline secure.

Setting secrets:

bash
# Via GitHub CLI
gh secret set OPENAI_API_KEY --body "sk-ant-..."
# Via GitHub UI
# Repository Settings → Secrets and variables → Actions

Using in workflows:

yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Run tests
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm test

VibeKit Security: Sandboxing and Isolation

VibeKit from SuperAgent includes powerful security features specifically designed for vibe coding.

Understanding VibeKit's Security Model

VibeKit's security approach is built on three pillars:

  1. Sandbox execution: Code runs in isolated environments
  2. Network isolation: Controlled network access
  3. Audit logging: Track all AI actions

Sandboxed Execution

The sandbox feature prevents AI-generated code from accessing your local files, environment, or network inappropriately.

Basic sandbox usage:

bash
# Run with Docker sandbox (most secure)
vibekit claude --sandbox docker "Create a web scraper"
# Use podman instead of docker
vibekit claude --sandbox-type podman "Build the integration"
# Local sandbox (faster but less isolation)
vibekit claude --sandbox-type local "Debug this issue"

Sandbox providers:

VibeKit supports multiple sandbox providers, each with different trade-offs:

E2B Provider:

bash
# Configure E2B
export E2B_API_KEY="your-api-key"
# Run with E2B sandbox
vibekit claude --sandbox e2b "Build and test the new feature"

E2B (e2b.dev) provides cloud sandboxes that are:

  • Instantly provisioned (seconds, not minutes)
  • Fully isolated from your environment
  • Resource-limited to prevent runaway processes

Daytona Provider:

bash
# Configure Daytona
export DAYTONA_API_KEY="your-api-key"
# Run with Daytona sandbox
vibekit claude --sandbox daytona "Implement the feature"

Daytona (daytona.io) offers:

  • Pre-configured development environments
  • Team collaboration features
  • API access for automation

Network Access Controls

VibeKit can restrict network access for sandboxed AI:

bash
# Allow only specific domains
vibekit claude --sandbox docker --allowed-hosts "api.openai.com,api.anthropic.com" "Call the API"
# Block all network access (most secure)
vibekit claude --sandbox docker --no-network "Process local files only"

Audit Logging

Track what AI does in your projects:

bash
# View logs
vibekit logs --agent claude --lines 100
# View analytics
vibekit analytics --days 7
# Export audit trail
vibekit logs --format json --output audit.json

VibeKit Configuration

Create a vibekit.config.js for project-specific security:

javascript
export default {
sandbox: {
type: "docker",
allowedHosts: ["api.openai.com", "api.anthropic.com"],
noNetwork: false,
timeout: 300, // seconds
memoryLimit: "2g",
},
agent: {
type: "claude",
model: "claude-sonnet-4-20250514",
permissionMode: "default", // 'default', 'acceptEdits', 'skipPermissions'
},
logging: {
level: "info",
output: "./vibekit-logs",
},
};

Cursor and Claude Code Security Settings

Both Cursor and Claude Code offer security configurations that balance productivity with protection.

Cursor Security Settings

Permission modes:

Cursor offers three permission modes that control what AI can do:

json
// .cursor/settings.json
{
"permissionMode": "default", // or "acceptEdits" or "skipPermissions"
"autoApprove": false,
"dangerousAllow裸": false
}

Modes explained:

Mode

File Changes

Command Execution

Best For

defaultPrompts each timePrompts each timeMaximum security
acceptEditsAuto-approvesPrompts each timeExperienced users
skipPermissionsAuto-approvesAuto-approves

Trusted environments only

Dangerous settings (avoid in production):

json
{
"dangerousAllow裸": true, // NEVER set to true
"dangerousAllowArbitraryLoad": true // NEVER set to true
}

Cursor Rules for Security:

Add security requirements to your .cursorrules:

markdown
# .cursorrules
# Security Requirements
## Secrets
- NEVER hardcode API keys, passwords, or secrets
- ALWAYS use environment variables: process.env.VARIABLE_NAME
- NEVER log secrets or sensitive data
- Use secrets management (Infisical, 1Password) in production
## Validation
- ALWAYS validate all inputs
- Use parameterized queries for database access
- NEVER concatenate user input into queries or commands
## Dependencies
- Audit dependencies before adding
- Use npm audit or similar tools
- Keep dependencies updated
## Code Review
- Review ALL AI-generated code before committing
- Run linter and tests before commit
- Never commit secrets to version control

Claude Code Security Settings

Permission modes:

Claude Code offers granular control via command-line flags:

bash
# Default - prompts for everything
claude "Fix the bug"
# Accept edits automatically
claude --permission-mode acceptEdits "Refactor this module"
# Skip all prompts (DANGEROUS)
claude --dangerously-skip-permissions "Quick fix"

Safe alternatives to YOLO mode:

Instead of using --dangerously-skip-permissions in production:

bash
# Use default mode but batch operations
claude "Refactor all files in src/auth/ directory"
# Use MCP for safe file operations
claude "Use the filesystem MCP to analyze this directory"
# Review before applying
claude "Show me the changes" > changes.patch
git apply changes.patch

Configuration file:

Create ~/.claude/settings.json for defaults:

json
{
"permissionMode": "default",
"model": "sonnet",
"think": false,
"maxOutputTokens": 4096
}

Security best practices:

  1. Never use --dangerously-skip-permissions in production
  2. Use sandboxed environments for untrusted code
  3. Review all changes before committing
  4. Set up git hooks to prevent accidental commits of secrets:
bash
# .git/hooks/pre-commit
#!/bin/bash
# Check for potential secrets
if git diff --cached --name-only | xargs grep -l "api_key\|apikey\|secret\|password" 2>/dev/null; then
echo "Potential secrets found in staged files!"
echo "Please review before committing."
exit 1
fi

Production Deployment Security

When your vibe-coded app goes to production, security requirements intensify.

Environment-Specific Configurations

Development vs Production:

javascript
// config.js
const config = {
development: {
logLevel: "debug",
authDisabled: false,
rateLimit: false,
},
production: {
logLevel: "error",
authDisabled: false,
rateLimit: {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
},
},
}[process.env.NODE_ENV || "development"];
export default config;

Secret injection in production:

bash
# Kubernetes secret
kubectl create secret generic openai-api-key \
--from-literal=OPENAI_API_KEY="sk-ant-..."
# Pod spec
env:
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: openai-api-key
key: OPENAI_API_KEY

Cloud provider services:

Provider

Service

Use For

AWSSecrets ManagerEnterprise secrets
GCPSecret Manager

GCP-integrated secrets

AzureKey Vault

Microsoft ecosystem

Vercel

Environment Variables

Serverless deployments

Monitoring for Leaked Secrets

GitHub secret scanning:

GitHub automatically scans for known secret patterns:

bash
# View detected secrets in your repo
gh secret-scanning list

Gitleaks for local scanning:

bash
# Install gitleaks
brew install gitleaks
# Scan for secrets
gitleaks detect --source=. --verbose

GitHub Actions workflow for scanning:

yaml
name: Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check npm dependencies
run: npm audit --audit-level=high

Monitoring production access:

javascript
// Log access to sensitive endpoints
app.get("/api/admin/users", (req, res) => {
logger.info("Admin user access", {
userId: req.user.id,
timestamp: new Date().toISOString(),
ip: req.ip,
});
// ... handle request
});

Response Plan for Secret Exposure

If you discover exposed secrets:

  1. Revoke immediately in the provider's dashboard
  2. Rotate all related credentials (not just the exposed one)
  3. Check usage logs to understand what was accessed
  4. Deploy new versions with updated secrets
  5. Audit access logs for suspicious activity
  6. Update all places where the secret was used
  7. Review git history to find when it was exposed
  8. Implement additional monitoring for the future

Your Vibe Coding Security Checklist

Use this checklist before every commit and deployment:

Pre-Commit Checklist

Before every commit, verify:

  • No hardcoded secrets in the code
  • All secrets loaded from environment variables
  • .env files are in .gitignore
  • Linter passes (npm run lint)
  • Tests pass (npm test)
  • No suspicious logging of sensitive data
  • Dependencies audited (npm audit)
  • Git diff reviewed before commit

Environment Setup Checklist

When setting up your environment:

  • .env.example exists with all required variables
  • .env.local or .env.production configured
  • Secrets management tool configured (Infisical, 1Password, or Vault)
  • API keys rotated recently
  • No secrets in CI/CD pipeline configuration

Production Deployment Checklist

Before deploying to production:

  • Secrets injected via secrets management service
  • Environment-specific configurations verified
  • Monitoring and alerting configured
  • Audit logging enabled
  • Rate limiting enabled
  • CORS properly configured
  • Input validation on all endpoints
  • HTTPS enforced
  • Security headers present

AI Tool Configuration Checklist

Configuring AI tools securely:

  • Permission mode set appropriately
  • Sandbox enabled for untrusted operations
  • Audit logs reviewed regularly
  • Project rules document security requirements
  • Team trained on secure vibe coding practices

The Secure Vibe Coding Mindset

Security in vibe coding isn't about preventing AI from doing its job. It's about building guardrails that let AI help you safely.

The most secure vibe coding teams aren't the ones with the most restrictions. They're the ones with the best habits:

  1. Assume AI will make mistakes - and review everything
  2. Never commit secrets - even in private repos
  3. Use tools that enforce security - make mistakes impossible
  4. Monitor continuously - catch issues before they become breaches
  5. Learn from incidents - every near-miss is a learning opportunity

The goal isn't perfect security. It's security good enough that when mistakes happen (and they will), the impact is minimized.


Tools and Resources

Secrets Management

Security Scanning

Environment Variables

Security Testing


Final Thoughts

Vibe coding is here to stay. The productivity gains are too significant to ignore. But speed without security is a recipe for disaster.

The founders who build successful, sustainable companies in 2025 and beyond won't choose between speed and security. They'll build systems that make security automatic, invisible, and unbreakable.

Implement the practices in this guide. Use the tools that enforce security by default. Train your team to think about security as they vibe code, not after.

Your future self—and your customers—will thank you.


Related Reading:


Need Help Building Secure AI Apps?

At Startupbricks, we help startups build secure, scalable applications with AI. From setting up proper secrets management to implementing security guardrails, we can help you vibe code safely.

Let's secure your AI development workflow

Share: