
Security Vite
- 86 installs
- 125 repo stars
- Updated February 4, 2026
- igorwarzocha/opencode-workflows
Audit Vite SPAs for VITE_ secret exposure, build-time secrets, source maps, and dev-server proxy/host binding risks.
About
A security-audit skill for Vite apps and dev servers. A developer uses it to catch secrets leaked via the VITE_ prefix, scan bundles in dist/, and review proxy and host binding config.
- Explains the VITE_ footgun: prefixed vars are bundled into client JS
- Scans dist/ bundles and audits SPA server-side auth vs client route guards
Security Vite by the numbers
- 86 all-time installs (skills.sh)
- +4 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #1,064 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/igorwarzocha/opencode-workflows --skill security-viteAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 86 |
|---|---|
| repo stars | ★ 125 |
| Last updated | February 4, 2026 |
| Repository | igorwarzocha/opencode-workflows ↗ |
What it does
Audit Vite SPAs for VITE_ secret exposure, build-time secrets, source maps, and dev-server proxy/host binding risks.
Files
<overview>
Security audit patterns for Vite applications focusing on environment variable exposure, build-time secrets, and SPA-specific vulnerabilities.
</overview>
<rules>
Environment Variable Exposure
The VITE_ Footgun
VITE_* → Bundled into client JavaScript → Visible to everyone
No prefix → Only available in vite.config.ts → Safe for secretsAudit steps: 1. grep -r "VITE_" . -g "*.env*" 2. Check import.meta.env.VITE_* usage in source 3. Common mistakes:
VITE_API_SECRET(SHOULD be server-only)VITE_DATABASE_URL(MUST NOT use)VITE_STRIPE_SECRET_KEY(only publishable keys)
Env Files Priority
Vite loads in this order (later overrides earlier):
.env # Always loaded
.env.local # Always loaded, gitignored
.env.[mode] # e.g., .env.production
.env.[mode].local # e.g., .env.production.local, gitignoredCheck: Are .env.local and .env.*.local in .gitignore?
envPrefix Overrides
If envPrefix is configured, Vite exposes any variables with those prefixes. Treat envPrefix as a security-sensitive setting.
</rules>
<vulnerabilities>
Build-Time vs Runtime
Dangerous: Secrets in vite.config.ts
// ❌ Secret in config (ends up in bundle)
export default defineConfig({
define: {
'process.env.API_KEY': JSON.stringify(process.env.API_KEY),
},
});
// The above makes API_KEY available in client code!Safe Pattern
// Only use VITE_ prefix for truly public values
export default defineConfig({
define: {
'__APP_VERSION__': JSON.stringify(process.env.npm_package_version),
},
});
// Keep secrets on server (use a backend API)Dev Server Security
Open to Network
// ❌ Exposes dev server to network
export default defineConfig({
server: {
host: '0.0.0.0', // or host: true
},
});This is dangerous on shared networks. Check if intentional.
Proxy Misconfiguration
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
// ❌ Missing secure options for production-like setup
},
},
},
});SPA Security Issues
Client-Side Auth Only
// ❌ "Protection" only in React Router
const ProtectedRoute = ({ children }) => {
const { user } = useAuth();
if (!user) return <Navigate to="/login" />;
return children;
};
// API calls still need server-side auth!
// This is UI convenience, not security.Secrets in Bundle
# Check the built bundle for secrets
rg -a "(sk_live|sk_test|AKIA|api[_-]?key)" dist/Source Maps in Production
// Check vite.config.ts
export default defineConfig({
build: {
sourcemap: true, // ❌ Exposes source code in production
},
});</vulnerabilities>
<severity_table>
Common Vulnerabilities
| Issue | Where to Look | Severity |
|---|---|---|
| VITE_* secrets | .env*, source files | CRITICAL |
| Secrets in define | vite.config.ts | CRITICAL |
| Source maps in prod | vite.config.ts | MEDIUM |
| Dev server exposed | vite.config.ts server.host | MEDIUM |
| Client-only auth | Route guards without API auth | HIGH |
| API keys in bundle | dist/ directory | CRITICAL |
</severity_table>
<commands>
Quick Audit Commands
# Find VITE_ secrets
grep -r "VITE_" . -g "*.env*"
# Find import.meta.env usage
rg 'import\.meta\.env' . -g "*.ts" -g "*.tsx" -g "*.vue"
# Check define in config
rg 'define:' vite.config.*
# Scan built bundle for secrets
rg -a "(sk_live|AKIA|ghp_|api[_-]?key['\"]?\s*[:=])" dist/
# Check for source maps
fd '\.map$' dist/</commands>
<checklist>
Hardening Checklist
- [ ] No secrets in
VITE_*variables - [ ]
.env.localand.env.*.localin.gitignore - [ ]
sourcemap: falsein production build - [ ]
server.hostis not0.0.0.0ortrue(unless intentional) - [ ] All sensitive API calls go through a backend (not direct from browser)
- [ ] No secrets in
vite.config.tsdefine block
</checklist>
#!/usr/bin/env bash
# Vite Security Scanner - First-pass automated detection
# Usage: ./scan.sh [directory]
set -euo pipefail
DIR="${1:-.}"
FOUND=0
echo "=== VITE SECURITY SCAN ==="
echo "Directory: $DIR"
echo "Timestamp: $(date -Iseconds)"
echo ""
if ! command -v rg &> /dev/null; then
echo "[ERROR] ripgrep (rg) required"
exit 1
fi
report() {
local severity="$1"
local title="$2"
local file="$3"
local line="${4:-}"
echo "[$severity] $title"
if [[ -n "$line" ]]; then
echo " File: $file:$line"
else
echo " File: $file"
fi
echo ""
FOUND=$((FOUND + 1))
}
echo "=== CRITICAL: VITE_ Secrets ==="
echo ""
# Check .env files for suspicious VITE_ vars
for envfile in $(find "$DIR" -name ".env*" -type f 2>/dev/null); do
while IFS=: read -r line_num content; do
[[ -z "$content" ]] && continue
if echo "$content" | grep -qiE 'VITE_.*(SECRET|KEY|PASSWORD|TOKEN|PRIVATE|API_KEY)'; then
report "CRITICAL" "Suspicious VITE_ variable (bundled into client)" "$envfile" "$line_num"
fi
done < <(grep -n 'VITE_' "$envfile" 2>/dev/null || true)
done
echo "=== HIGH: Secrets in vite.config ==="
echo ""
VITE_CONFIG=$(find "$DIR" -maxdepth 1 -name "vite.config.*" 2>/dev/null | head -1)
if [[ -n "$VITE_CONFIG" && -f "$VITE_CONFIG" ]]; then
# Check for define with process.env
if rg -q 'define:.*process\.env' "$VITE_CONFIG" 2>/dev/null; then
report "HIGH" "process.env in define block (may expose secrets to client)" "$VITE_CONFIG"
fi
# Check for envPrefix overrides
if rg -q 'envPrefix' "$VITE_CONFIG" 2>/dev/null; then
report "MEDIUM" "envPrefix configured (verify only public prefixes are exposed)" "$VITE_CONFIG"
fi
# Check for 0.0.0.0 binding
if rg -q 'host.*0\.0\.0\.0|host.*true' "$VITE_CONFIG" 2>/dev/null; then
report "MEDIUM" "Dev server exposed to network" "$VITE_CONFIG"
fi
# Check for sourcemaps in production
if rg -q 'sourcemap.*true' "$VITE_CONFIG" 2>/dev/null; then
report "MEDIUM" "Source maps enabled (check if production)" "$VITE_CONFIG"
fi
fi
echo "=== MEDIUM: .env files ==="
echo ""
# Check if .env.local is in gitignore
if [[ -f "$DIR/.env.local" ]]; then
if [[ -f "$DIR/.gitignore" ]]; then
if ! grep -q '\.env\.local' "$DIR/.gitignore" 2>/dev/null; then
report "MEDIUM" ".env.local exists but may not be in .gitignore" "$DIR/.env.local"
fi
else
report "MEDIUM" ".env.local exists and no .gitignore found" "$DIR/.env.local"
fi
fi
# Check if any .env.*.local files are gitignored
ENV_LOCAL_FILES=$(find "$DIR" -name ".env.*.local" -type f 2>/dev/null | head -5 || true)
if [[ -n "$ENV_LOCAL_FILES" ]]; then
if [[ -f "$DIR/.gitignore" ]]; then
if ! grep -q '\.env\.\*\.local' "$DIR/.gitignore" 2>/dev/null && ! grep -q '\.env\.local' "$DIR/.gitignore" 2>/dev/null; then
report "MEDIUM" ".env.*.local exists but may not be in .gitignore" "$DIR/.gitignore"
fi
else
report "MEDIUM" ".env.*.local exists and no .gitignore found" "$DIR/.gitignore"
fi
fi
echo "=== INFO: Check Built Bundle ==="
echo ""
if [[ -d "$DIR/dist" ]]; then
echo "[INFO] Built bundle found at $DIR/dist"
echo "Scanning for exposed secrets in bundle..."
BUNDLE_SECRETS=$(rg -a '(sk_live_|sk_test_|AKIA[0-9A-Z]{16}|ghp_|api[_-]?key)' "$DIR/dist" 2>/dev/null || true)
if [[ -n "$BUNDLE_SECRETS" ]]; then
report "CRITICAL" "Secrets found in built bundle!" "$DIR/dist"
echo "$BUNDLE_SECRETS" | head -10
echo ""
fi
# Check for source maps
MAPS=$(find "$DIR/dist" -name "*.map" 2>/dev/null | head -5)
if [[ -n "$MAPS" ]]; then
report "MEDIUM" "Source map files in dist/ (exposes source code)" "$DIR/dist"
fi
fi
echo "=== SUMMARY ==="
if [[ $FOUND -gt 0 ]]; then
echo "[!] Found $FOUND potential issues. Review above."
exit 1
else
echo "[✓] No obvious Vite security issues detected"
exit 0
fi