mcpskills.net
SkillsMCPsAgentsPrompts
mcpskills.net — A curated directory of AI agent Skills and MCP servers
TermsPrivacy
← Back to Skills
Development

Security and Hardening

Hardens code against vulnerabilities. Use when handling user input, authentication, data storage, or external integrations.

by Addy OsmaniRepository →Source →

Security-first development practices for web applications. Treat every external input as hostile.

When to Use

  • Building anything that accepts user input
  • Implementing authentication or authorization
  • Storing or transmitting sensitive data
  • Integrating with external APIs or services
  • Adding file uploads, webhooks, or callbacks
  • Handling payment or PII data

Threat Model First

Before hardening, spend five minutes thinking like an attacker:

  1. Map the trust boundaries. Where does untrusted data cross into your system?
  2. Name the assets. What's worth stealing or breaking?
  3. Run STRIDE over each boundary:

| Threat | Ask | Typical mitigation | |---|---|---| | Spoofing | Can someone impersonate a user/service? | Authentication, signature verification | | Tampering | Can data be altered in transit or at rest? | Integrity checks, parameterized queries | | Repudiation | Can an action be denied later? | Audit logging of security events | | Information disclosure | Can data leak? | Encryption, field allowlists | | Denial of service | Can it be overwhelmed? | Rate limiting, input size caps | | Elevation of privilege | Can a user gain rights they shouldn't? | Authorization checks, least privilege |

The Three-Tier Boundary System

Always Do (No Exceptions)

  • Validate all external input at the system boundary
  • Parameterize all database queries — never concatenate user input into SQL
  • Encode output to prevent XSS
  • Use HTTPS for all external communication
  • Hash passwords with bcrypt/scrypt/argon2
  • Set security headers (CSP, HSTS, X-Frame-Options)
  • Use httpOnly, secure, sameSite cookies for sessions
  • Run npm audit before every release

Ask First (Requires Human Approval)

  • Adding new authentication flows
  • Storing new categories of sensitive data
  • Adding new external service integrations
  • Changing CORS configuration
  • Adding file upload handlers

Never Do

  • Never commit secrets to version control
  • Never log sensitive data (passwords, tokens, full credit card numbers)
  • Never trust client-side validation as a security boundary
  • Never disable security headers for convenience
  • Never use eval() or innerHTML with user-provided data

OWASP Top 10 Prevention Patterns

Injection (SQL, NoSQL, OS Command)

// BAD: SQL injection via string concatenation
const query = `SELECT * FROM users WHERE id = '${userId}'`;

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

// GOOD: ORM with parameterized input
const user = await prisma.user.findUnique({ where: { id: userId } });

Broken Authentication

// Password hashing
import { hash, compare } from 'bcrypt';

const SALT_ROUNDS = 12;
const hashedPassword = await hash(plaintext, SALT_ROUNDS);
const isValid = await compare(plaintext, hashedPassword);

// Session management
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    maxAge: 24 * 60 * 60 * 1000,
  },
}));

Cross-Site Scripting (XSS)

// BAD: Rendering user input as HTML
element.innerHTML = userInput;

// GOOD: Use framework auto-escaping (React does this by default)
return <div>{userInput}</div>;

// If you MUST render HTML, sanitize first
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userInput);

Broken Access Control

// Always check authorization, not just authentication
app.patch('/api/tasks/:id', authenticate, async (req, res) => {
  const task = await taskService.findById(req.params.id);

  // Check that the authenticated user owns this resource
  if (task.ownerId !== req.user.id) {
    return res.status(403).json({
      error: { code: 'FORBIDDEN', message: 'Not authorized' }
    });
  }

  const updated = await taskService.update(req.params.id, req.body);
  return res.json(updated);
});

Input Validation Patterns

Schema Validation at Boundaries

import { z } from 'zod';

const CreateTaskSchema = z.object({
  title: z.string().min(1).max(200).trim(),
  description: z.string().max(2000).optional(),
  priority: z.enum(['low', 'medium', 'high']).default('medium'),
  dueDate: z.string().datetime().optional(),
});

app.post('/api/tasks', async (req, res) => {
  const result = CreateTaskSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(422).json({
      error: {
        code: 'VALIDATION_ERROR',
        message: 'Invalid input',
        details: result.error.flatten(),
      },
    });
  }
  const task = await taskService.create(result.data);
  return res.status(201).json(task);
});

Rate Limiting

import rateLimit from 'express-rate-limit';

// General API rate limit
app.use('/api/', rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
}));

// Stricter limit for auth endpoints
app.use('/api/auth/', rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 10,
}));

Secrets Management

.env files:
  ├── .env.example  → Committed (template with placeholder values)
  ├── .env          → NOT committed (contains real secrets)
  └── .env.local    → NOT committed (local overrides)

.gitignore must include:
  .env
  .env.local
  .env.*.local
  *.pem
  *.key

Always check before committing:

git diff --cached | grep -i "password\|secret\|api_key\|token"

Security Review Checklist

  • [ ] Passwords hashed with bcrypt/scrypt/argon2 (salt rounds ≥ 12)
  • [ ] Session tokens are httpOnly, secure, sameSite
  • [ ] Login has rate limiting
  • [ ] Every endpoint checks user permissions
  • [ ] Users can only access their own resources
  • [ ] All user input validated at the boundary
  • [ ] SQL queries are parameterized
  • [ ] HTML output is encoded/escaped
  • [ ] No secrets in code or version control
  • [ ] Sensitive fields excluded from API responses
  • [ ] Security headers configured (CSP, HSTS, etc.)
  • [ ] CORS restricted to known origins
  • [ ] Dependencies audited for vulnerabilities
  • [ ] Error messages don't expose internals

Verification

After implementing security-relevant code:

  • [ ] npm audit shows no critical or high vulnerabilities
  • [ ] No secrets in source code or git history
  • [ ] All user input validated at system boundaries
  • [ ] Authentication and authorization checked on every protected endpoint
  • [ ] Security headers present in response
  • [ ] Error responses don't expose internal details
  • [ ] Rate limiting active on auth endpoints