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 thisconst apiKey = "sk-ant-api03-abc123...";const client = new OpenAI({ apiKey });// ✅ SAFE: Load from environmentconst 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 logsconsole.log("API call with key:", apiKey);// ✅ SAFE: Never log secretsconsole.log("API call initiated");**3. Insecure Defaults**AI might generate code with insecure configurations:```javascript// ❌ DANGEROUS: Development settings in productionapp.use(cors({ origin: true, credentials: true }));// ✅ SAFE: Explicit whitelistapp.use(cors({origin: process.env.ALLOWED_ORIGINS?.split(",") || [],credentials: true,}));**4. Missing Validation**AI might skip input validation, creating security vulnerabilities:```javascript// ❌ DANGEROUS: No validationconst userId = req.params.id;const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`);// ✅ SAFE: Proper validationconst 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:
- Never commit
.envto version control - Always ignore
.envin.gitignore:
gitignore# .gitignore.env.env.local.env.*.local
- Create a template for other developers:
bash# .env.example (commit this!)# Copy this to .env and fill in the valuesOPENAI_API_KEY=""ANTHROPIC_API_KEY=""DATABASE_URL=""STRIPE_SECRET_KEY=""JWT_SECRET=""
Using dotenv Properly
Install dotenv in your project:
bashnpm install dotenv
Load environment variables at the top of your entry file:
javascript// index.js or app.js - load FIRSTimport dotenv from "dotenv";dotenv.config();// Now you can use process.env.ANY_VARIABLEconst apiKey = process.env.OPENAI_API_KEY;
For TypeScript projects, use dotenv-flow:
bashnpm install dotenv-flow
typescriptimport dotenvFlow from "dotenv-flow";dotenvFlow.config();// Type-safe accessconst 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=developmentDEBUG=trueDATABASE_URL="postgresql://localhost:5432/myapp"# .env.test (CI/CD)NODE_ENV=testDATABASE_URL="postgresql://test:test@localhost:5432/testdb"# .env.productionNODE_ENV=productionDEBUG=falseDATABASE_URL="postgresql://prod-db.example.com:5432/myapp"
Loading the right environment:
javascript// Load based on NODE_ENVimport 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 sessionexport OPENAI_API_KEY="sk-ant-..."export ANTHROPIC_API_KEY="sk-ant-..."# Run with specific variablesOPENAI_API_KEY="sk-ant-..." npx vibekit claude "Build the feature"# Add to your shell profile for persistenceecho 'export OPENAI_API_KEY="sk-ant-..."' >> ~/.zshrcsource ~/.zshrc
Vite Environment Variables
If you're using Vite, environment variables must be prefixed with VITE_:
bash# .envVITE_API_URL="https://api.yourapp.com"OPENAI_API_KEY="sk-ant-..." # Server-side only
javascript// In client-side code, only VITE_ variables are accessibleconsole.log(import.meta.env.VITE_API_URL); // Worksconsole.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 CLInpm install -g infisical# Initialize in your projectcd your-projectinfisical init# Logininfisical login
Managing secrets via CLI:
bash# Set a secretinfisical secrets set "OPENAI_API_KEY" "sk-ant-..."# Get a secretinfisical secrets get "OPENAI_API_KEY"# List all secretsinfisical secrets ls# Inject secrets into .envinfisical run -- env > .env && source .env# Use in scriptsinfisical run -- npm run dev
For Node.js projects:
javascriptimport { 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.ymlservices:app:image: your-appenv_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 CLIbrew install 1password-cli# Sign inop signin# Connect to your 1Password vault
Using 1Password for secrets:
bash# Get a secretop item get "API Keys" --fields "OpenAI API Key" --format json# Use in scriptseval $(op env --signin)export OPENAI_API_KEY=$(op item get "API Keys" --field "OpenAI API Key")
1Password with environment files:
bash# Generate .env from 1Passwordop 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 modevault server -dev# Set a secretvault kv put secret/openai/api-key value="sk-ant-..."# Read a secretvault kv get secret/openai/api-key# Use in applicationsexport OPENAI_API_KEY=$(vault kv get -field=value secret/openai/api-key)
For Kubernetes:
yaml# Use Vault Secrets OperatorapiVersion: secrets.hashicorp.com/v1beta1kind: VaultSecretmetadata:name: openai-api-keyspec:vaultRef:path: secret/data/openai/api-keydestination:name: openai-api-keycreate: 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 CLIgh secret set OPENAI_API_KEY --body "sk-ant-..."# Via GitHub UI# Repository Settings → Secrets and variables → Actions
Using in workflows:
yaml# .github/workflows/ci.ymlname: CIon:push:branches: [main]jobs:build:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- name: Install dependenciesrun: npm ci- name: Run testsenv: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:
- Sandbox execution: Code runs in isolated environments
- Network isolation: Controlled network access
- 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 dockervibekit 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 E2Bexport E2B_API_KEY="your-api-key"# Run with E2B sandboxvibekit 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 Daytonaexport DAYTONA_API_KEY="your-api-key"# Run with Daytona sandboxvibekit 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 domainsvibekit 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 logsvibekit logs --agent claude --lines 100# View analyticsvibekit analytics --days 7# Export audit trailvibekit logs --format json --output audit.json
VibeKit Configuration
Create a vibekit.config.js for project-specific security:
javascriptexport default {sandbox: {type: "docker",allowedHosts: ["api.openai.com", "api.anthropic.com"],noNetwork: false,timeout: 300, // secondsmemoryLimit: "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 |
|---|---|---|---|
| default | Prompts each time | Prompts each time | Maximum security |
| acceptEdits | Auto-approves | Prompts each time | Experienced users |
| skipPermissions | Auto-approves | Auto-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 everythingclaude "Fix the bug"# Accept edits automaticallyclaude --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 operationsclaude "Refactor all files in src/auth/ directory"# Use MCP for safe file operationsclaude "Use the filesystem MCP to analyze this directory"# Review before applyingclaude "Show me the changes" > changes.patchgit apply changes.patch
Configuration file:
Create ~/.claude/settings.json for defaults:
json{"permissionMode": "default","model": "sonnet","think": false,"maxOutputTokens": 4096}
Security best practices:
- Never use
--dangerously-skip-permissionsin production - Use sandboxed environments for untrusted code
- Review all changes before committing
- Set up git hooks to prevent accidental commits of secrets:
bash# .git/hooks/pre-commit#!/bin/bash# Check for potential secretsif git diff --cached --name-only | xargs grep -l "api_key\|apikey\|secret\|password" 2>/dev/null; thenecho "Potential secrets found in staged files!"echo "Please review before committing."exit 1fi
Production Deployment Security
When your vibe-coded app goes to production, security requirements intensify.
Environment-Specific Configurations
Development vs Production:
javascript// config.jsconst config = {development: {logLevel: "debug",authDisabled: false,rateLimit: false,},production: {logLevel: "error",authDisabled: false,rateLimit: {windowMs: 15 * 60 * 1000, // 15 minutesmax: 100, // limit each IP to 100 requests per windowMs},},}[process.env.NODE_ENV || "development"];export default config;
Secret injection in production:
bash# Kubernetes secretkubectl create secret generic openai-api-key \--from-literal=OPENAI_API_KEY="sk-ant-..."# Pod specenv:- name: OPENAI_API_KEYvalueFrom:secretKeyRef:name: openai-api-keykey: OPENAI_API_KEY
Cloud provider services:
Provider | Service | Use For |
|---|---|---|
| AWS | Secrets Manager | Enterprise secrets |
| GCP | Secret Manager | GCP-integrated secrets |
| Azure | Key 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 repogh secret-scanning list
Gitleaks for local scanning:
bash# Install gitleaksbrew install gitleaks# Scan for secretsgitleaks detect --source=. --verbose
GitHub Actions workflow for scanning:
yamlname: Security Scanon:push:branches: [main]pull_request:branches: [main]jobs:secrets:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- name: Run Gitleaksuses: gitleaks/gitleaks-action@v2env:GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}- name: Check npm dependenciesrun: npm audit --audit-level=high
Monitoring production access:
javascript// Log access to sensitive endpointsapp.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:
- Revoke immediately in the provider's dashboard
- Rotate all related credentials (not just the exposed one)
- Check usage logs to understand what was accessed
- Deploy new versions with updated secrets
- Audit access logs for suspicious activity
- Update all places where the secret was used
- Review git history to find when it was exposed
- 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
.envfiles 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.exampleexists with all required variables.env.localor.env.productionconfigured- 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:
- Assume AI will make mistakes - and review everything
- Never commit secrets - even in private repos
- Use tools that enforce security - make mistakes impossible
- Monitor continuously - catch issues before they become breaches
- 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
- Infisical - Open-source secrets management
- 1Password CLI - Developer-focused secrets
- HashiCorp Vault - Enterprise secrets management
- OpenCode - Open-source CLI alternative with security focus
Security Scanning
- Gitleaks - Secret detection
- trufflehog - Comprehensive scanning
Environment Variables
- dotenv - Node.js environment variables
- dotenv-flow - TypeScript support
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:
- Vibe Coding in 2025: Complete Guide to AI-Powered Development Tools - The companion guide to this article
- Technical Debt: Smart Strategy or Startup Killer? - Making smart trade-offs
- How to Build an MVP Without Writing a Code - Fast development with security in mind
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.
