Z-Audit: Security Audit for Vibe-Coded Projects
"2026 is the year of vibe-coding. Ship fast, audit faster."
Overview
A comprehensive, adaptive security audit that detects your stack and runs targeted checks. Designed for developers who moved fast and need to find vulnerabilities before someone else does.
Phase 0: Stack Detection (ALWAYS RUN FIRST)
Before auditing, detect what you're working with. This determines which checks to run.
0.1 If Given URLs (Remote Audit)
# Fetch frontend and identify framework
curl -s "[FRONTEND_URL]" | grep -oE '(react|vue|svelte|next|nuxt|astro|vite)'
# Find JS bundles
curl -s "[FRONTEND_URL]" | grep -oE '[^"]+\.(js|mjs)"' | head -10
# Identify API type from URL patterns
# .workers.dev = Cloudflare Workers
# .vercel.app = Vercel Serverless
# .netlify.app = Netlify Functions
# .supabase.co = Supabase direct
# .firebaseapp.com = Firebase
0.2 If Given Local Codebase
# Detect from config files
ls -la | grep -E "package.json|requirements.txt|go.mod|Cargo.toml|wrangler.toml|vercel.json|netlify.toml|supabase|firebase"
# Check package.json for framework
cat package.json | grep -E '"(react|vue|svelte|next|nuxt|hono|express|fastify|elysia)"'
# Check for auth libraries
cat package.json | grep -E '"(@supabase|firebase|@clerk|@auth0|better-auth|lucia|next-auth)"'
0.3 Stack Detection Matrix
| Signal | Stack | Special Checks |
|---|---|---|
wrangler.toml |
Cloudflare Workers | Check secrets, KV, D1 |
vercel.json |
Vercel | Check env vars, edge functions |
supabase/ dir |
Supabase | Check RLS, anon key permissions |
firebase.json |
Firebase | Check Firestore rules, Auth |
next.config.js |
Next.js | Check API routes, middleware |
nuxt.config.ts |
Nuxt | Check server routes, nitro |
hono in deps |
Hono API | Check middleware chain |
express in deps |
Express | Check middleware order |
Phase 1: Secrets & Credentials Audit
1.1 Frontend Bundle Scan (CRITICAL)
For Remote Sites:
# Get main bundle URL
BUNDLE=$(curl -s "[FRONTEND_URL]" | grep -oE 'src="[^"]+\.js"' | head -1 | cut -d'"' -f2)
curl -s "[FRONTEND_URL]$BUNDLE" > /tmp/bundle.js
# Scan for secrets
Universal Secret Patterns:
# API Keys by provider
grep -oE 'sk-[a-zA-Z0-9]{20,}' /tmp/bundle.js # OpenAI
grep -oE 'sk_live_[a-zA-Z0-9]+' /tmp/bundle.js # Stripe Live
grep -oE 'sk_test_[a-zA-Z0-9]+' /tmp/bundle.js # Stripe Test
grep -oE 'pk_live_[a-zA-Z0-9]+' /tmp/bundle.js # Stripe Publishable
grep -oE 'AKIA[A-Z0-9]{16}' /tmp/bundle.js # AWS Access Key
grep -oE 'ghp_[a-zA-Z0-9]{36}' /tmp/bundle.js # GitHub PAT
grep -oE 'gho_[a-zA-Z0-9]{36}' /tmp/bundle.js # GitHub OAuth
grep -oE 'glpat-[a-zA-Z0-9\-]{20}' /tmp/bundle.js # GitLab PAT
grep -oE 'xox[baprs]-[a-zA-Z0-9\-]+' /tmp/bundle.js # Slack Token
grep -oE 'ya29\.[a-zA-Z0-9_-]+' /tmp/bundle.js # Google OAuth
grep -oE 'AIza[a-zA-Z0-9_-]{35}' /tmp/bundle.js # Google API Key
grep -oE 'eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*' /tmp/bundle.js # JWTs
# Supabase specific
grep -oE 'eyJ[a-zA-Z0-9_=-]+\.[a-zA-Z0-9_=-]+\.[a-zA-Z0-9_=-]+' /tmp/bundle.js # Supabase anon/service key
grep -oE 'https://[a-z]+\.supabase\.co' /tmp/bundle.js # Supabase URL
# Firebase specific
grep -oE 'AIza[a-zA-Z0-9_-]{35}' /tmp/bundle.js # Firebase API Key
grep -oE '[a-z]+-[a-z0-9]+\.firebaseapp\.com' /tmp/bundle.js
# Generic high-entropy strings (potential secrets)
grep -oE '"[a-zA-Z0-9_-]{32,64}"' /tmp/bundle.js | head -20
# Hardcoded passwords
grep -oiE '(password|passwd|pwd|secret)["\x27\s]*[=:]["\x27\s]*["\x27][^"\x27]{4,}["\x27]' /tmp/bundle.js
1.2 Local Codebase Secrets Scan
# Check for .env files committed
find . -name ".env*" -not -path "./node_modules/*" 2>/dev/null
# Check git history for secrets
git log -p --all -S 'password' --since="1 year ago" -- '*.js' '*.ts' '*.json' 2>/dev/null | head -50
git log -p --all -S 'secret' --since="1 year ago" -- '*.js' '*.ts' '*.json' 2>/dev/null | head -50
git log -p --all -S 'sk-' --since="1 year ago" 2>/dev/null | head -50
# Scan source files
grep -rn --include="*.ts" --include="*.js" --include="*.tsx" \
-E '(api_key|apikey|api-key|secret|password|token)\s*[=:]\s*["\x27][^"\x27]+["\x27]' . 2>/dev/null
# Check for hardcoded in config
grep -rn --include="*.json" --include="*.yaml" --include="*.yml" \
-E '(key|secret|password|token)' . 2>/dev/null | grep -v node_modules
Phase 2: Authentication & Authorization
2.1 Client-Side Auth Detection
# localStorage/sessionStorage auth (weak)
grep -oE 'localStorage\.(get|set)Item\(["\x27][^"\x27]*(auth|session|token|user|login)[^"\x27]*' /tmp/bundle.js
grep -oE 'sessionStorage\.(get|set)Item\(["\x27][^"\x27]*(auth|session|token|user|login)[^"\x27]*' /tmp/bundle.js
# Check for client-side password validation (CRITICAL vulnerability)
grep -oE 'if\s*\([^)]*===\s*["\x27][^"\x27]+["\x27]\s*\)' /tmp/bundle.js | head -10
# Find auth context/provider
grep -oE '(AuthContext|AuthProvider|useAuth|isAuthenticated|isLoggedIn)' /tmp/bundle.js
2.2 API Authentication Testing
API_URL="[YOUR_API_URL]"
# Test endpoints without auth
ENDPOINTS=("users" "projects" "tasks" "items" "data" "config" "settings" "admin" "me" "profile")
for ep in "${ENDPOINTS[@]}"; do
echo "=== /api/$ep ==="
curl -s -w "\nHTTP: %{http_code}\n" "$API_URL/api/$ep" | tail -5
done
# Test with malformed auth
curl -s "$API_URL/api/users" -H "Authorization: Bearer null"
curl -s "$API_URL/api/users" -H "Authorization: Bearer undefined"
curl -s "$API_URL/api/users" -H "Authorization: Bearer [object Object]"
curl -s "$API_URL/api/users" -H "X-API-Key: null"
curl -s "$API_URL/api/users" -H "Cookie: session=admin"
# Test path traversal
curl -s "$API_URL/api/../../../etc/passwd"
curl -s "$API_URL/api/users/../../admin"
2.3 Authorization (Access Control)
# If you can get a valid token, test accessing other users' data
# IDOR (Insecure Direct Object Reference)
curl -s "$API_URL/api/users/1" -H "Authorization: Bearer [your-token]"
curl -s "$API_URL/api/users/2" -H "Authorization: Bearer [your-token]"
# Test role escalation
curl -s "$API_URL/api/admin" -H "Authorization: Bearer [regular-user-token]"
curl -s -X PUT "$API_URL/api/users/me" -H "Authorization: Bearer [token]" \
-d '{"role":"admin"}'
Phase 3: API Security
3.1 CRUD Access Without Auth
API_URL="[YOUR_API_URL]"
# CREATE
curl -s -X POST "$API_URL/api/projects" \
-H "Content-Type: application/json" \
-d '{"name":"z-audit-test","test":true}'
# READ (already tested above)
# UPDATE
curl -s -X PUT "$API_URL/api/projects/1" \
-H "Content-Type: application/json" \
-d '{"name":"hacked"}'
# DELETE
curl -s -X DELETE "$API_URL/api/projects/1"
# If any of these succeed without auth = CRITICAL
3.2 Input Validation
# SQL Injection
curl -s "$API_URL/api/users?id=1' OR '1'='1"
curl -s "$API_URL/api/users?id=1; DROP TABLE users;--"
# NoSQL Injection
curl -s -X POST "$API_URL/api/login" \
-H "Content-Type: application/json" \
-d '{"email":{"$gt":""},"password":{"$gt":""}}'
# XSS payloads in input
curl -s -X POST "$API_URL/api/projects" \
-H "Content-Type: application/json" \
-d '{"name":"<script>alert(1)</script>"}'
# Command Injection
curl -s "$API_URL/api/export?filename=test;ls"
3.3 Rate Limiting
# Send 100 requests rapidly
for i in {1..100}; do
curl -s -o /dev/null -w "%{http_code}\n" "$API_URL/api/projects" &
done
wait
# Should see 429 (Too Many Requests) eventually
# If all return 200 = no rate limiting
3.4 Error Handling
# Trigger errors and check for info leakage
curl -s "$API_URL/api/projects/undefined"
curl -s "$API_URL/api/projects/null"
curl -s "$API_URL/api/projects/NaN"
curl -s -X POST "$API_URL/api/projects" -H "Content-Type: application/json" -d 'invalid-json'
# Check if stack traces are exposed
# Red flags: file paths, line numbers, package versions
Phase 4: Infrastructure-Specific Checks
4.1 Cloudflare Workers
# If you have wrangler access
ACCOUNT_ID="[account-id]"
WORKER_NAME="[worker-name]"
# List secrets (names only, not values)
wrangler secret list --name $WORKER_NAME
# Check for missing critical secrets
# Should have: AUTH_SECRET, JWT_SECRET, or similar
# List KV namespaces (might contain sensitive data)
wrangler kv namespace list
# Check D1 databases
wrangler d1 list
# Download worker code for review
curl -s "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/scripts/$WORKER_NAME" \
-H "Authorization: Bearer [CF_API_TOKEN]" > worker.js
4.2 Vercel
DOMAIN="[your-domain.vercel.app]"
# Check for exposed env files
curl -s "https://$DOMAIN/.env"
curl -s "https://$DOMAIN/.env.local"
curl -s "https://$DOMAIN/.env.production"
# Check for source maps
curl -s "https://$DOMAIN/_next/static/chunks/main.js.map"
# Check for exposed API routes
curl -s "https://$DOMAIN/api"
curl -s "https://$DOMAIN/api/auth"
# Check Next.js specific
curl -s "https://$DOMAIN/_next/data/[build-id]/index.json"
4.3 Supabase
PROJECT_URL="https://[project-ref].supabase.co"
ANON_KEY="[anon-key-from-frontend]"
# Test anon key permissions (should be limited)
# List all tables
curl -s "$PROJECT_URL/rest/v1/" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY"
# Try to read users table
curl -s "$PROJECT_URL/rest/v1/users?select=*" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY"
# Try to read auth.users (should always fail)
curl -s "$PROJECT_URL/rest/v1/auth.users?select=*" \
-H "apikey: $ANON_KEY"
# Check if RLS is enabled by trying to insert
curl -s -X POST "$PROJECT_URL/rest/v1/[table]" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"test": "data"}'
# Check storage buckets
curl -s "$PROJECT_URL/storage/v1/bucket" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ANON_KEY"
4.4 Firebase
# Check Firestore rules
# If rules are: allow read, write: if true; = CRITICAL
# Test direct database access
curl -s "https://[project].firebaseio.com/.json"
# Check storage rules
curl -s "https://firebasestorage.googleapis.com/v0/b/[project].appspot.com/o/"
Phase 5: Security Headers & CORS
5.1 Security Headers Check
curl -sI "[FRONTEND_URL]" | grep -iE '^(strict-transport|content-security|x-frame|x-content-type|x-xss|referrer-policy|permissions-policy):'
# Expected headers:
# Strict-Transport-Security: max-age=31536000
# Content-Security-Policy: default-src 'self'
# X-Frame-Options: DENY
# X-Content-Type-Options: nosniff
# Referrer-Policy: strict-origin-when-cross-origin
5.2 CORS Check
# Check if API allows any origin
curl -sI "$API_URL/api/projects" \
-H "Origin: https://evil.com" | grep -i "access-control"
# If Access-Control-Allow-Origin: * = potential issue
# If it reflects back the evil origin = CRITICAL
Phase 6: Dependency Audit
6.1 NPM/Yarn
# Check for known vulnerabilities
npm audit --json 2>/dev/null | jq '.metadata.vulnerabilities'
# Or with yarn
yarn audit --json 2>/dev/null | head -50
# Check for outdated packages
npm outdated
6.2 Python
# Using pip-audit
pip-audit
# Or safety
safety check
Phase 7: Sensitive Data Exposure
7.1 API Response Analysis
# Check what data is returned
curl -s "$API_URL/api/users" | jq 'if type == "array" then .[0] else . end | keys'
# Red flags in responses:
# - password / password_hash / hashed_password
# - credit_card / card_number
# - ssn / social_security
# - api_key / secret_key
# - internal_id that reveals info
# - created_by showing other user IDs
7.2 Error Message Analysis
# Intentionally trigger errors
curl -s "$API_URL/api/users/99999999"
curl -s -X POST "$API_URL/api/users" -d '{}'
# Red flags:
# - Full stack traces
# - Database table/column names
# - File system paths
# - Package versions
Severity Classification
| Level | Criteria | Examples |
|---|---|---|
| 🔴 CRITICAL | Immediate exploitation, data breach possible | Hardcoded passwords, no API auth, exposed secrets |
| 🟠 HIGH | Significant risk, needs quick fix | Exposed API keys, missing auth on some routes, IDOR |
| 🟡 MEDIUM | Should fix soon | Verbose errors, weak rate limiting, missing security headers |
| 🟢 LOW | Best practice improvements | Outdated dependencies (no known exploits), missing CSP |
Report Template
# 🔐 Z-Audit Security Report
**Target:** [Project Name / URLs]
**Date:** [Date]
**Stack Detected:** [e.g., Next.js + Supabase + Cloudflare Workers]
---
## Executive Summary
[2-3 sentences: Overall posture, critical issues count, immediate actions needed]
---
## 🔴 Critical Findings
### C1: [Title]
- **Location:** [File/URL/Endpoint]
- **Issue:** [Description]
- **Impact:** [What an attacker could do]
- **Evidence:** [Screenshot/curl command/code snippet]
- **Remediation:** [How to fix]
---
## 🟠 High Findings
[Same format as Critical]
---
## 🟡 Medium Findings
[Same format]
---
## 🟢 Low Findings / Recommendations
[Same format]
---
## ✅ What's Secure
- [Positive finding 1]
- [Positive finding 2]
---
## 📋 Prioritized Action Plan
1. **Immediate (today):** [Critical fixes]
2. **This week:** [High fixes]
3. **This month:** [Medium fixes]
4. **Backlog:** [Low priority improvements]
---
## Appendix: Tools Used
- Z-Audit skill
- curl, grep, jq
- [Other tools]
Stack-Specific Fix Guides
Supabase Auth Setup
// 1. Enable RLS on all tables
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
// 2. Create policies
CREATE POLICY "Users can only see own data" ON your_table
FOR SELECT USING (auth.uid() = user_id);
// 3. Frontend auth
const { data: { session } } = await supabase.auth.getSession()
Better Auth Setup
// auth.ts
import { betterAuth } from 'better-auth'
export const auth = betterAuth({
emailAndPassword: { enabled: true },
socialProviders: { google: { clientId, clientSecret } }
})
// Protect API routes
app.use('/api/*', auth.middleware)
Cloudflare Workers Auth
// Add auth middleware
app.use('/api/*', async (c, next) => {
const token = c.req.header('Authorization')?.split(' ')[1]
if (!token) return c.json({ error: 'Unauthorized' }, 401)
try {
const payload = await verify(token, c.env.JWT_SECRET)
c.set('user', payload)
await next()
} catch {
return c.json({ error: 'Invalid token' }, 401)
}
})