home / skills / igorwarzocha / opencode-workflows / security-vite

npx playbooks add skill igorwarzocha/opencode-workflows --skill security-vite

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
4.7 KB
---
name: security-vite
description: |-
  Review Vite security audit patterns for SPA and dev server security. Use for auditing VITE_* exposure, build-time secrets, and proxy configs. Use proactively when reviewing Vite apps (vite.config.ts present).
  Examples:
  - user: "Audit Vite env vars" → check for secrets with VITE_ prefix
  - user: "Check Vite build config" → verify define block and source maps
  - user: "Review Vite dev server" → check host binding and proxy security
  - user: "Scan Vite bundles" → search dist/ for leaked API keys or secrets
  - user: "Audit Vite SPA auth" → verify server-side auth vs client route guards
---

<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 secrets
```

**Audit 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, gitignored
```

**Check:** 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
```typescript
// ❌ 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
```typescript
// 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
```typescript
// ❌ 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
```typescript
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
```typescript
// ❌ "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
```bash
# Check the built bundle for secrets
rg -a "(sk_live|sk_test|AKIA|api[_-]?key)" dist/
```

### Source Maps in Production
```typescript
// 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

```bash
# 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.local` and `.env.*.local` in `.gitignore`
- [ ] `sourcemap: false` in production build
- [ ] `server.host` is not `0.0.0.0` or `true` (unless intentional)
- [ ] All sensitive API calls go through a backend (not direct from browser)
- [ ] No secrets in `vite.config.ts` define block

</checklist>