Skip to main content
skill CX Codex

security · alinaqi — Claude Code skill: what it does, how to install

Sets up preventive security rules, blocks secrets from leaking into client bundles, and configures pre-commit hooks. Useful when handling auth, user input, or API keys.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add alinaqi/maggy --skill security -a claude-code
Open on GitHub

What security does

This skill enforces preventive security practices while writing new code. Rather than auditing a legacy repository for existing vulnerabilities, it focuses on guardrails: strict rules for .gitignore, schema validation with zod or pydantic, and separating server secrets from client-exposed prefixes in Vite or Next.js.

The agent works with Read, Glob, Grep, and Bash tools. It sets up scripts/security-check.sh, wires detect-secrets into git hooks, and adds a GitHub Actions workflow with trufflehog. Dependency audits rely on npm audit for Node.js or safety and bandit for Python. The local check will fail immediately if .secrets.baseline is missing when detect-secrets runs.

What it helps with

  • isolate server secrets from client bundles in Next.js and Vite
  • prevent committing .env files and private keys via .gitignore
  • configure git pre-commit checks with detect-secrets and secretlint
  • automate dependency audits with npm audit, safety, and bandit

Example prompts for the agent

  • Check our Vite environment setup and prevent private API keys from leaking into client code
  • Configure pre-commit security checks and scripts to block accidental token commits
  • Add a GitHub Actions workflow to scan dependencies with safety and trufflehog

How to install security

1Skills CLI (Vercel)

npx skills add alinaqi/maggy --skill security -a claude-code

2Manually: put the files in place

~/.claude/skills/security/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/security/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about security

How do I install security in Claude Code?

Run npx skills add alinaqi/maggy --skill security -a claude-code. The file will be placed in ~/.claude/skills/security/SKILL.md.

What is the difference between security and security-audit?

The security skill is preventive and guides you while writing code, managing secrets, and configuring git hooks. For adversarial audits of existing codebases or pull requests, the author specifies using security-audit.

How does this skill handle frontend environment variables?

It restricts prefixes like VITE_* and NEXT_PUBLIC_* strictly to safe public values. Server-only keys remain prefix-free, while environment variables are validated at startup using zod or pydantic.

Source text

SKILL.md · MIT
---
name: security
description: OWASP security patterns, secrets management, security testing
when-to-use: When writing code that handles auth, user input, API keys, or when security review is requested
user-invocable: true
allowed-tools: [Read, Glob, Grep, Bash]
effort: high
---

# Security Skill


Security best practices and automated security testing for all projects.

> This skill is **preventive** — how to write and ship secure code. To *audit*
> an existing codebase or PR for vulnerabilities, use the `security-audit` skill
> (structured, adversarial, evidence-grounded); for a quick pass over the current
> branch, run `/security-review`.

---

## Core Principle

**Security is not optional.** Every project must pass security checks before merge. Assume all input is malicious, all secrets will leak if committed, and all dependencies have vulnerabilities.

---

## Required Security Setup

### 1. Gitignore (Non-Negotiable)

Every project must have these in `.gitignore`:

```gitignore
# Environment files - NEVER commit
.env
.env.*
!.env.example

# Secrets
*.pem
*.key
*.p12
*.pfx
credentials.json
secrets.json
*-credentials.json
service-account*.json

# IDE and OS
.idea/
.vscode/settings.json
.DS_Store
Thumbs.db

# Dependencies
node_modules/
__pycache__/
*.pyc
.venv/
venv/

# Build outputs
dist/
build/
*.egg-info/

# Logs that might contain sensitive data
*.log
logs/
```

### 2. Environment Variables

**Create `.env.example`** with all required vars (no values):
```bash
# .env.example - Copy to .env and fill in values

# Server-side only (NEVER prefix with VITE_ or NEXT_PUBLIC_)
DATABASE_URL=
ANTHROPIC_API_KEY=
SUPABASE_SERVICE_ROLE_KEY=

# Client-side safe (public, non-sensitive)
VITE_SUPABASE_URL=
VITE_SUPABASE_ANON_KEY=
```

### Frontend Environment Variables (Critical!)

**NEVER put secrets in client-exposed env vars:**

| Framework | Client-Exposed Prefix | Server-Only |
|-----------|----------------------|-------------|
| Vite | `VITE_*` | No prefix |
| Next.js | `NEXT_PUBLIC_*` | No prefix |
| Create React App | `REACT_APP_*` | N/A (no server) |

```typescript
// WRONG - Secret exposed to browser bundle!
const apiKey = import.meta.env.VITE_ANTHROPIC_API_KEY;

// CORRECT - Only public values client-side
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;

// CORRECT - Secrets stay server-side only
// In API route or server function:
const apiKey = process.env.ANTHROPIC_API_KEY;
```

**Vercel Environment Variables:**
- In Vercel dashboard, secrets without `VITE_` prefix are server-only
- Only `VITE_*` vars are bundled into client code
- Always verify in browser devtools → Sources → your bundle that secrets aren't exposed

**Validate environment at startup:**
```typescript
// config/env.ts
import { z } from 'zod';

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  ANTHROPIC_API_KEY: z.string().min(1),
  NODE_ENV: z.enum(['development', 'production', 'test']),
});

export const env = envSchema.parse(process.env);
```

```python
# config/env.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    anthropic_api_key: str
    environment: str = "development"

    class Config:
        env_file = ".env"

settings = Settings()
```

---

## Security Tests

### Pre-Commit Security Checks

Add to pre-commit hooks:

**For all projects:**
```yaml
# .pre-commit-config.yaml (add to existing)
repos:
  # Detect secrets
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']

  # Check for security issues in dependencies
  - repo: local
    hooks:
      - id: security-check
        name: security-check
        entry: ./scripts/security-check.sh
        language: script
        pass_filenames: false
```

**TypeScript/JavaScript:**
```json
// package.json scripts
{
  "scripts": {
    "security:audit": "npm audit --audit-level=high",
    "security:secrets": "npx secretlint '**/*'",
    "security:deps": "npx better-npm-audit audit"
  }
}
```

**Python:**
```bash
# Add to dev dependencies
pip install safety bandit

# Commands
safety check           # Check dependencies for vulnerabilities
bandit -r src/        # Static security analysis
```

### Security Check Script

Create `scripts/security-check.sh`:

```bash
#!/bin/bash
set -e

echo "Running security checks..."

# Check for secrets in staged files
echo "Checking for secrets..."
if command -v detect-secrets &> /dev/null; then
  detect-secrets scan --baseline .secrets.baseline
fi

# Check .env is not staged
if git diff --cached --name-only | grep -E '^\.env$|^\.env\.' | grep -v '\.example$'; then
  echo "ERROR: .env file is staged for commit!"
  exit 1
fi

# Check for common secret patterns in staged files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
if echo "$STAGED_FILES" | xargs grep -l -E '(password|secret|api_key|apikey|token|private_key)\s*[:=]\s*["\047][^"\047]+["\047]' 2>/dev/null; then
  echo "ERROR: Possible secrets found in staged files!"
  exit 1
fi

# Language-specific checks
if [ -f "package.json" ]; then
  echo "Checking npm dependencies..."
  npm audit --audit-level=high || echo "Warning: npm audit found issues"
fi

if [ -f "pyproject.toml" ] || [ -f "requirements.txt" ]; then
  echo "Checking Python dependencies..."
  if command -v safety &> /dev/null; then
    safety check || echo "Warning: safety found issues"
  fi
fi

echo "Security checks passed!"
```

```bash
chmod +x scripts/security-check.sh
```

---

## GitHub Actions Security Workflow

Create `.github/workflows/security.yml`:

```yaml
name: Security

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    # Run weekly on Monday at 9am UTC
    - cron: '0 9 * * 1'

jobs:
  secrets-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Detect secrets
        uses: trufflesecurity/trufflehog@main
        with:
          path: ./
          base: ${{ github.event.pull_request.base.sha }}
          head: ${{ github.event.pull_request.head.sha }}

  dependency-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Node.js projects
      - name: Setup Node
        if: hashFiles('package.json') != ''
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        if: hashFiles('package.json') != ''
        run: npm ci

      - name: NPM Audit
        if: hashFiles('package.json') != ''
        run: npm audit --audit-level=high

      # Python projects
      - name: Setup Python
        if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install safety
        if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
        run: pip install safety

      - name: Safety check
        if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
        run: safety check

  codeql:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: ${{ hashFiles('package.json') != '' && 'javascript-typescript' || 'python' }}

      - name: Autobuild
        uses: github/codeql-action/autobuild@v3

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v3
```

---

## Input Validation (OWASP Top 10)

### 1. SQL Injection Prevention

**Never use string concatenation:**
```typescript
// BAD - SQL injection vulnerable
const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`);

// GOOD - Parameterized query
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);

// GOOD - Using ORM (Kysely, Prisma, Drizzle)
const user = await db.selectFrom('users').where('id', '=', userId).execute();
```

```python
# BAD - SQL injection vulnerable
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")

# GOOD - Parameterized query
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))

# GOOD - Using ORM (SQLAlchemy)
user = session.query(User).filter(User.id == user_id).first()
```

### 2. XSS Prevention

```typescript
// Always sanitize user input before rendering
import DOMPurify from 'dompurify';

// BAD - XSS vulnerable
element.innerHTML = userInput;

// GOOD - Sanitized
element.innerHTML = DOMPurify.sanitize(userInput);

// BEST - Use framework's built-in escaping (React does this by default)
return <div>{userInput}</div>;  // Safe in React

// DANGER - Bypasses React's protection
return <div dangerouslySetInnerHTML={{ __html: userInput }} />;  // Avoid!
```

### 3. Input Validation at Boundaries

```typescript
// Validate ALL external input with Zod
import { z } from 'zod';

const CreateUserSchema = z.object({
  email: z.string().email().max(255),
  name: z.string().min(1).max(100).regex(/^[a-zA-Z\s]+$/),
  age: z.number().int().min(0).max(150),
});

// In route handler
app.post('/users', async (req, res) => {
  const result = CreateUserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ error: result.error });
  }
  // result.data is now typed and validated
});
```

### 4. Path Traversal Prevention

```typescript
import path from 'path';

// BAD - Path traversal vulnerable
const filePath = `./uploads/${req.params.filename}`;

// GOOD - Validate and sanitize path
const filename = path.basename(req.params.filename);  // Strips ../
const filePath = path.join('./uploads', filename);

// Verify it's still within allowed directory
if (!filePath.startsWith(path.resolve('./uploads'))) {
  throw new Error('Invalid path');
}
```

---

## Authentication & Authorization

### JWT Best Practices

```typescript
import jwt from 'jsonwebtoken';

// Token generation
function generateToken(userId: string): string {
  return jwt.sign(
    { sub: userId },
    process.env.JWT_SECRET!,
    {
      expiresIn: '15m',      // Short-lived access tokens
      algorithm: 'HS256',
    }
  );
}

// Token verification
function verifyToken(token: string): { sub: string } {
  return jwt.verify(token, process.env.JWT_SECRET!, {
    algorithms: ['HS256'],   // Explicitly specify allowed algorithms
  }) as { sub: string };
}
```

### Password Hashing

```typescript
import bcrypt from 'bcrypt';

const SALT_ROUNDS = 12;  // Minimum 10, recommended 12+

async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, SALT_ROUNDS);
}

async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash);
}
```

```python
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def hash_password(password: str) -> str:
    return pwd_context.hash(password)

def verify_password(password: str, hashed: str) -> bool:
    return pwd_context.verify(password, hashed)
```

### Rate Limiting

```typescript
import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 100,                   // 100 requests per window
  standardHeaders: true,
  legacyHeaders: false,
});

// Apply to auth routes
app.use('/api/auth', rateLimit({
  windowMs: 60 * 1000,  // 1 minute
  max: 5,                // 5 attempts per minute
  message: 'Too many login attempts, please try again later',
}));
```

---

## Security Headers

```typescript
import helmet from 'helmet';

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
  },
}));
```

---

## Security Testing Checklist

Run before every release:

```markdown
## Security Checklist

### Secrets & Environment
- [ ] No secrets in code (run detect-secrets)
- [ ] .env files in .gitignore
- [ ] .env.example exists with all required vars
- [ ] Environment validated at startup

### Dependencies
- [ ] npm audit / safety check passes
- [ ] No known vulnerabilities in dependencies
- [ ] Dependencies up to date (Dependabot enabled)

### Input Validation
- [ ] All API inputs validated with schema (Zod/Pydantic)
- [ ] File uploads restricted by type and size
- [ ] Path traversal prevented

### Authentication
- [ ] Passwords hashed with bcrypt (12+ rounds)
- [ ] JWTs use short expiration
- [ ] Rate limiting on auth endpoints
- [ ] Session tokens rotated on login

### Database
- [ ] Parameterized queries only
- [ ] Least privilege database user
- [ ] Connection strings not logged

### Headers & CORS
- [ ] Security headers enabled (helmet)
- [ ] CORS restricted to known origins
- [ ] HTTPS only in production

### Logging
- [ ] No secrets in logs
- [ ] No PII in logs (or properly masked)
- [ ] Failed auth attempts logged
```

---

## Security Anti-Patterns

- ❌ Secrets in `VITE_*`, `NEXT_PUBLIC_*`, or `REACT_APP_*` env vars (client-exposed!)
- ❌ Secrets in code or config files committed to git
- ❌ .env files without .gitignore entry
- ❌ String concatenation for SQL queries
- ❌ `dangerouslySetInnerHTML` without sanitization
- ❌ `eval()` or `new Function()` with user input
- ❌ Passwords stored as plain text or weak hash (MD5, SHA1)
- ❌ JWTs with no expiration or very long expiration
- ❌ No rate limiting on authentication endpoints
- ❌ Logging sensitive data (passwords, tokens, PII)
- ❌ Using `*` for CORS origins in production
- ❌ Ignoring npm audit / safety check warnings
- ❌ Running as root / admin in production
- ❌ Hardcoded credentials for any environment
- ❌ Disabling SSL/TLS verification

© alinaqi · MIT · alinaqi/maggy

Similar skills

Everything for Security →

security by other authors comes first.

security boshu2 Reviews code, dependencies and prompt surfaces for weaknesses. Useful when you need to know whether code is safe to ship. CX Codex +1 1,400installs security parcadei A security audit skill: scans code for vulnerabilities, checks dependencies and secrets, then re-verifies after fixes. CX Codex +1 530installs security rshankras A security category index for Apple platforms that routes prompts directly to the privacy-manifests skill. CX Codex +1 489installs security cognitedata Walks a Flows app step by step, finds leaked secrets, unsafe DOM calls and auth gaps, then fixes each one in place. CX Codex +1 1,900installs security tartinerlabs Checks dependencies for CVEs, scans the working tree and git history for secrets, wires a scanner into pre-commit, and runs an OWASP pass over the repo. CX Codex +1 531installs security-auditor zhaono1 A skill that hunts vulnerabilities in your code: walks the OWASP Top 10, checks configs, dependencies and leaked secrets. Useful before a release or a review. CX Codex +1 1,000installs Next.js Security Scan sugarforever A skill that scans Next.js and TypeScript/JavaScript projects for XSS, injection flaws, auth holes and hardcoded secrets. Useful before you ship. CX Codex +1 553installs security-scan jwynia Scans a codebase for OWASP Top 10 flaws, hardcoded credentials and unsafe configs. Useful before a deploy or when reading unfamiliar code. CX Codex +1 471installs typescript-security-review giuseppe-trisciuoglio Reviews TypeScript and Node.js codebases for OWASP Top 10 vulnerabilities, leaked secrets, and authentication bugs. Useful before production releases in Express, NestJS, or Next.js apps. CX Codex +1 3,900installs security-testing kindlmann Runs negative-path security checks in CI: ZAP, Semgrep, dependency scanning and tests for XSS, SSRF, auth bypass. CX Codex +1 1,100installs secrets-scan OWASP Scans source and config files for hardcoded credentials, API keys and tokens, whether you are reviewing before a merge or auditing a repo. CX Codex +1 337installs

From the same set32

aws-dynamodb Guides coding assistants through DynamoDB single-table design, GSI indexing patterns, and AWS SDK v3 operations in TypeScript and Python. CX Codex +1 522installs klaviyo A skill for wiring Klaviyo into your code: customer profiles, event tracking, campaigns, flows and segmentation. Useful when a store needs marketing hooks. CX Codex +1 517installs aeo-optimization Formats content and page structures for citations across ChatGPT, Perplexity, and Google AI Overviews. Useful when writing documentation or product landing pages. CX Codex +1 437installs nodejs-backend A skill that lays out Node.js project structure and ready-made patterns for Express, Fastify and repositories. It kicks in when the agent edits code under src/api, src/routes or src/server. CX Codex +1 434installs agentic-development A skill for building autonomous AI agents with Pydantic AI in Python or the Claude Agent SDK in Node.js. Useful when an agent has to run multi-step tasks with tools. CX Codex +1 406installs ui-web Design skill enforcing WCAG 2.1 AA accessibility, Tailwind CSS tokens, dark mode contrast, and glassmorphic UI patterns. CX Codex +1 393installs woocommerce A skill for talking to WooCommerce over the REST API: products, orders, customers, webhooks. Useful when your agent writes store integration code. CX Codex +1 390installs ui-testing An accessibility and visual QA checklist for components: detects invisible buttons, failing contrast ratios, and small touch targets. CX Codex +1 364installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor