Claude Code Plugins

Community-maintained marketplace

Feedback

OWASP Top 10, authentication, and secure coding practices

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name security-practices
description OWASP Top 10, authentication, and secure coding practices
domain software-engineering
version 1.0.0
tags security, owasp, authentication, authorization, encryption, xss, csrf
triggers [object Object]

Security Practices

Overview

Essential security practices for application development. Covers OWASP Top 10 and secure coding guidelines.


OWASP Top 10

1. Injection (SQL, NoSQL, Command)

// ❌ SQL Injection vulnerable
const query = `SELECT * FROM users WHERE email = '${email}'`;
// Attack: email = "'; DROP TABLE users; --"

// ✅ Parameterized query
const result = await db.query(
  'SELECT * FROM users WHERE email = $1',
  [email]
);

// ✅ ORM with parameterization
const user = await prisma.user.findUnique({
  where: { email }
});

// ❌ Command injection vulnerable
exec(`ping ${userInput}`);
// Attack: userInput = "google.com; rm -rf /"

// ✅ Use arrays, not string concatenation
execFile('ping', ['-c', '4', hostname]);

2. Broken Authentication

// Strong password requirements
const passwordSchema = z.string()
  .min(12)
  .regex(/[A-Z]/, 'Must contain uppercase')
  .regex(/[a-z]/, 'Must contain lowercase')
  .regex(/[0-9]/, 'Must contain number')
  .regex(/[^A-Za-z0-9]/, 'Must contain special character');

// Secure password hashing
import argon2 from 'argon2';

async function hashPassword(password: string): Promise<string> {
  return argon2.hash(password, {
    type: argon2.argon2id,
    memoryCost: 65536,  // 64 MB
    timeCost: 3,
    parallelism: 4
  });
}

async function verifyPassword(hash: string, password: string): Promise<boolean> {
  return argon2.verify(hash, password);
}

// Rate limiting login attempts
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 attempts
  message: 'Too many login attempts'
});

app.post('/login', loginLimiter, handleLogin);

3. Cross-Site Scripting (XSS)

// ❌ Direct HTML insertion
element.innerHTML = userInput;
// Attack: userInput = "<script>stealCookies()</script>"

// ✅ Use textContent for text
element.textContent = userInput;

// ✅ React auto-escapes by default
function UserName({ name }: { name: string }) {
  return <span>{name}</span>; // Safe
}

// ⚠️ dangerouslySetInnerHTML requires sanitization
import DOMPurify from 'dompurify';

function RichContent({ html }: { html: string }) {
  const sanitized = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
    ALLOWED_ATTR: ['href']
  });

  return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}

// Content Security Policy header
app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy',
    "default-src 'self'; " +
    "script-src 'self' 'unsafe-inline'; " +
    "style-src 'self' 'unsafe-inline'; " +
    "img-src 'self' data: https:;"
  );
  next();
});

4. Insecure Direct Object References

// ❌ No authorization check
app.get('/api/documents/:id', async (req, res) => {
  const doc = await db.documents.findById(req.params.id);
  res.json(doc);
});
// Attack: User can access any document by guessing ID

// ✅ Verify ownership
app.get('/api/documents/:id', auth, async (req, res) => {
  const doc = await db.documents.findById(req.params.id);

  if (!doc) {
    return res.status(404).json({ error: 'Not found' });
  }

  if (doc.ownerId !== req.user.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  res.json(doc);
});

// ✅ Use UUIDs instead of sequential IDs
// Harder to guess, but still check authorization!
const docId = crypto.randomUUID();

5. Cross-Site Request Forgery (CSRF)

// CSRF token middleware
import csrf from 'csurf';

const csrfProtection = csrf({ cookie: true });

app.get('/form', csrfProtection, (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

app.post('/submit', csrfProtection, (req, res) => {
  // Token automatically validated
  // ...
});

// In form
<form action="/submit" method="POST">
  <input type="hidden" name="_csrf" value="{{csrfToken}}" />
  <!-- form fields -->
</form>

// SameSite cookies
res.cookie('sessionId', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict' // or 'lax'
});

Authentication

JWT Best Practices

import jwt from 'jsonwebtoken';

// Access token (short-lived)
function generateAccessToken(user: User): string {
  return jwt.sign(
    { sub: user.id, role: user.role },
    process.env.JWT_SECRET!,
    { expiresIn: '15m' }
  );
}

// Refresh token (long-lived, stored securely)
function generateRefreshToken(user: User): string {
  const token = jwt.sign(
    { sub: user.id, type: 'refresh' },
    process.env.JWT_REFRESH_SECRET!,
    { expiresIn: '7d' }
  );

  // Store in database to allow revocation
  db.refreshTokens.create({
    userId: user.id,
    token: hashToken(token),
    expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
  });

  return token;
}

// Verify and refresh
async function refreshAccessToken(refreshToken: string) {
  const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!);

  // Check if token is revoked
  const storedToken = await db.refreshTokens.findOne({
    userId: payload.sub,
    token: hashToken(refreshToken)
  });

  if (!storedToken) {
    throw new Error('Token revoked');
  }

  const user = await db.users.findById(payload.sub);
  return generateAccessToken(user);
}

OAuth 2.0 / OIDC

import { OAuth2Client } from 'google-auth-library';

const client = new OAuth2Client(
  process.env.GOOGLE_CLIENT_ID,
  process.env.GOOGLE_CLIENT_SECRET,
  'https://myapp.com/auth/google/callback'
);

// Generate auth URL
app.get('/auth/google', (req, res) => {
  const url = client.generateAuthUrl({
    scope: ['openid', 'email', 'profile'],
    state: generateState(req.session.id) // CSRF protection
  });
  res.redirect(url);
});

// Handle callback
app.get('/auth/google/callback', async (req, res) => {
  const { code, state } = req.query;

  // Verify state
  if (!verifyState(state, req.session.id)) {
    return res.status(400).send('Invalid state');
  }

  // Exchange code for tokens
  const { tokens } = await client.getToken(code);

  // Verify ID token
  const ticket = await client.verifyIdToken({
    idToken: tokens.id_token,
    audience: process.env.GOOGLE_CLIENT_ID
  });

  const payload = ticket.getPayload();

  // Create or update user
  const user = await upsertUser({
    email: payload.email,
    name: payload.name,
    picture: payload.picture
  });

  // Create session
  req.session.userId = user.id;
  res.redirect('/dashboard');
});

Authorization

Role-Based Access Control (RBAC)

// Define permissions
const PERMISSIONS = {
  admin: ['read', 'write', 'delete', 'admin'],
  editor: ['read', 'write'],
  viewer: ['read']
} as const;

// Middleware
function requirePermission(permission: string) {
  return (req: Request, res: Response, next: NextFunction) => {
    const userPermissions = PERMISSIONS[req.user.role] || [];

    if (!userPermissions.includes(permission)) {
      return res.status(403).json({ error: 'Forbidden' });
    }

    next();
  };
}

// Usage
app.delete('/api/posts/:id', auth, requirePermission('delete'), deletePost);

Attribute-Based Access Control (ABAC)

interface Policy {
  effect: 'allow' | 'deny';
  resource: string;
  action: string;
  condition?: (context: Context) => boolean;
}

const policies: Policy[] = [
  {
    effect: 'allow',
    resource: 'document',
    action: 'read',
    condition: (ctx) => ctx.resource.isPublic || ctx.user.id === ctx.resource.ownerId
  },
  {
    effect: 'allow',
    resource: 'document',
    action: 'write',
    condition: (ctx) => ctx.user.id === ctx.resource.ownerId
  },
  {
    effect: 'allow',
    resource: '*',
    action: '*',
    condition: (ctx) => ctx.user.role === 'admin'
  }
];

function isAllowed(user: User, action: string, resource: Resource): boolean {
  const context = { user, resource };

  for (const policy of policies) {
    if (
      (policy.resource === '*' || policy.resource === resource.type) &&
      (policy.action === '*' || policy.action === action)
    ) {
      if (!policy.condition || policy.condition(context)) {
        return policy.effect === 'allow';
      }
    }
  }

  return false; // Deny by default
}

Secrets Management

// ❌ Never hardcode secrets
const apiKey = 'sk_live_1234567890';

// ✅ Use environment variables
const apiKey = process.env.API_KEY;

// ✅ Use secret managers
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';

const client = new SecretManagerServiceClient();

async function getSecret(name: string): Promise<string> {
  const [version] = await client.accessSecretVersion({
    name: `projects/my-project/secrets/${name}/versions/latest`
  });

  return version.payload.data.toString();
}

// ✅ Rotate secrets regularly
// Store secret versions, not raw secrets
// Use short-lived tokens where possible

Input Validation

import { z } from 'zod';

// Define strict schemas
const createUserSchema = z.object({
  email: z.string().email().max(255),
  name: z.string().min(1).max(100).regex(/^[\w\s-]+$/),
  age: z.number().int().min(0).max(150).optional()
});

// Validate at boundaries
app.post('/api/users', async (req, res) => {
  const result = createUserSchema.safeParse(req.body);

  if (!result.success) {
    return res.status(400).json({
      error: 'Validation failed',
      details: result.error.flatten()
    });
  }

  // result.data is typed and validated
  const user = await createUser(result.data);
  res.json(user);
});

// File upload validation
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];

function validateFile(file: Express.Multer.File) {
  if (file.size > MAX_FILE_SIZE) {
    throw new Error('File too large');
  }

  if (!ALLOWED_TYPES.includes(file.mimetype)) {
    throw new Error('Invalid file type');
  }

  // Also check magic bytes, not just extension
  const fileType = await fileTypeFromBuffer(file.buffer);
  if (!fileType || !ALLOWED_TYPES.includes(fileType.mime)) {
    throw new Error('Invalid file content');
  }
}

Security Headers

import helmet from 'helmet';

app.use(helmet());

// Or configure individually
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'unsafe-inline'"],
    styleSrc: ["'self'", "'unsafe-inline'"],
    imgSrc: ["'self'", "data:", "https:"],
    connectSrc: ["'self'", "https://api.example.com"],
    fontSrc: ["'self'"],
    objectSrc: ["'none'"],
    frameAncestors: ["'none'"]
  }
}));

app.use(helmet.hsts({
  maxAge: 31536000,
  includeSubDomains: true,
  preload: true
}));

Related Skills

  • [[authentication]] - Auth patterns
  • [[api-design]] - API security
  • [[devops-cicd]] - Security in pipelines