MCPInstall

security-patterns

Implements authentication, authorization, encryption, secrets management, and security hardening patterns. Use when designing auth flows, managing secrets, configuring CORS, implementing rate limiting, or when asked about JWT, OAuth, password hashing, API keys, RBAC, or security best practices.

Installs
126
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/cloudai-x/claude-workflow-v2 --skill security-patterns
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads security-patterns automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Security Patterns

When to Load

  • Trigger: Auth flows, encryption, secrets management, CORS configuration, input validation, rate limiting
  • Skip: No security surface involved in the current task

Security Implementation Workflow

Copy this checklist and track progress:

Security Implementation Progress:
- [ ] Step 1: Choose authentication strategy
- [ ] Step 2: Implement authorization model
- [ ] Step 3: Set up password hashing
- [ ] Step 4: Configure secrets management
- [ ] Step 5: Enable encryption (transit + rest)
- [ ] Step 6: Configure CORS
- [ ] Step 7: Add rate limiting
- [ ] Step 8: Validate against anti-patterns checklist

Authentication Patterns

JWT (JSON Web Tokens)

import jwt from "jsonwebtoken";

function generateTokens(user: User) {
  const accessToken = jwt.sign(
    { sub: user.id, role: user.role },
    process.env.JWT_SECRET!,
    { expiresIn: "15m", algorithm: "HS256" },
  );
  const refreshToken = jwt.sign(
    { sub: user.id, tokenVersion: user.tokenVersion },
    process.env.JWT_REFRESH_SECRET!,
    { expiresIn: "7d" },
  );
  return { accessToken, refreshToken };
}

// WRONG: localStorage (XSS vulnerable) | CORRECT: httpOnly cookie for refresh, memory for access
res.cookie("refreshToken", refreshToken, {
  httpOnly: true,
  secure: true,
  sameSite: "strict",
  maxAge: 7 * 24 * 60 * 60 * 1000,
  path: "/api/auth/refresh",
});

JWT Verification Middleware

function authenticate(req: Request, res: Response, next: NextFunction) {
  const header = req.headers.authorization;
  if (!header?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing token" });
  }

  try {
    const token = header.slice(7);
    const payload = jwt.verify(token, process.env.JWT_SECRET!, {
      algorithms: ["HS256"], // pin: never let the token choose its algorithm
    }) as JwtPayload;
    req.user = { id: payload.sub, role: payload.role };
    next();
  } catch (err) {
    if (err instanceof jwt.TokenExpiredError) {
      return res.status(401).json({ error: "Token expired" });
    }
    return res.status(401).json({ error: "Invalid token" });
  }
}

Session-Based Auth

import session from "express-session";
import { RedisStore } from "connect-redis";

app.use(
  session({
    store: new RedisStore({ client: redisClient }),
    secret: process.env.SESSION_SECRET!,
    resave: false,
    saveUninitialized: false,
    cookie: {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "strict",
      maxAge: 24 * 60 * 60 * 1000, // 24 hours
    },
  }),
);

OAuth 2.0 / OIDC Flow Summary

Authorization Code Flow with PKCE (all clients):
1. Redirect to provider: /authorize?response_type=code&client_id=...&redirect_uri=...&scope=openid email
   &state=RANDOM&nonce=RANDOM&code_challenge=...&code_challenge_method=S256
2. User authenticates, provider redirects back with ?code=AUTHORIZATION_CODE&state=... (reject if state differs)
3. Backend exchanges code for tokens (POST /token with code_verifier, plus client_secret for confidential clients)
4. Backend validates the id_token (signature, iss, aud, exp, nonce), then creates session/JWT

Public clients (SPAs, mobile): same flow without client_secret
NEVER use Implicit Flow (deprecated, tokens exposed in URL)

API Key Authentication

async function authenticateApiKey(
  req: Request,
  res: Response,
  next: NextFunction,
) {
  const apiKey = req.headers["x-api-key"] as string;
  if (!apiKey) return res.status(401).json({ error: "API key required" });

  // WRONG: Direct comparison (timing attack) | CORRECT: Hash-based lookup
  const hashedKey = crypto.createHash("sha256").update(apiKey).digest("hex");
  const keyRecord = await db.apiKey.findUnique({ where: { hash: hashedKey } });
  if (!keyRecord || keyRecord.revokedAt)
    return res.status(401).json({ error: "Invalid API key" });

  req.apiClient = { id: keyRecord.clientId, scopes: keyRecord.scopes };
  next();
}

Authorization Models

RBAC (Role-Based Access Control)

const PERMISSIONS = {
  admin: [
    "users:read",
    "users:write",
    "users:delete",
    "posts:read",
    "posts:write",
    "posts:delete",
  ],
  editor: ["posts:read", "posts:write", "posts:delete", "users:read"],
  viewer: ["posts:read", "users:read"],
} as const;

type Role = keyof typeof PERMISSIONS;

function authorize(...requiredPermissions: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    const userPermissions = PERMISSIONS[req.user.role as Role] || [];
    const hasPermission = requiredPermissions.every((p) =>
      (userPermissions as readonly string[]).includes(p),
    );
    if (!hasPermission)
      return res.status(403).json({ error: "Insufficient permissions" });
    next();
  };
}

// Usage: app.delete("/api/users/:id", authenticate, authorize("users:delete"), deleteUser);

Resource-Level Authorization

// WRONG: Only checking role, not ownership -- any editor can edit ANY post
// CORRECT: Check ownership or admin role
app.put(
  "/api/posts/:id",
  authenticate,
  authorize("posts:write"),
  async (req, res) => {
    const post = await db.post.findUnique({ where: { id: req.params.id } });
    if (!post) return res.status(404).json({ error: "Not found" });
    if (post.authorId !== req.user.id && req.user.role !== "admin") {
      return res
        .status(403)
        .json({ error: "Not authorized to edit this post" });
    }
    // WRONG: data: req.body -- mass assignment lets the client overwrite authorId
    const { title, body } = UpdatePostSchema.parse(req.body); // allowlisted fields only
    const updated = await db.post.update({
      where: { id: post.id },
      data: { title, body },
    });
    res.json(updated);
  },
);

Password Handling

import bcrypt from "bcrypt";
// WRONG: plaintext or MD5/SHA256 (too fast, brute-forceable)
// CORRECT: bcrypt with appropriate cost factor
const SALT_ROUNDS = 12; // ~250ms on modern hardware

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); // constant-time comparison built-in
}

// Registration
await db.user.create({
  data: { email, password: await hashPassword(req.body.password) },
});

// Login -- WRONG: "Invalid password" (reveals email exists) | CORRECT: generic message
// Always run bcrypt, even for unknown emails, or response time reveals which emails exist
const user = await db.user.findUnique({ where: { email } });
const hash = user?.password ?? DUMMY_HASH; // bcrypt hash of a random string, created at startup
if (!(await verifyPassword(req.body.password, hash)) || !user) {
  return res.status(401).json({ error: "Invalid email or password" });
}

Password Policies

function validatePassword(password: string): string[] {
  const errors: string[] = [];
  if (password.length < 12) errors.push("Minimum 12 characters");
  if (Buffer.byteLength(password) > 72) errors.push("Maximum 72 bytes"); // bcrypt ignores the rest

  // Check against breached password lists (haveibeenpwned API or local)
  // Do NOT enforce arbitrary complexity rules (uppercase + number + symbol)
  // NIST 800-63B recommends length over complexity
  return errors;
}

Secrets Management

# WRONG: Hardcoded values in source code
# API_KEY = "some-value-here"

# CORRECT: Environment variables loaded from .env
from dotenv import load_dotenv
import os

load_dotenv()
api_key = os.getenv("API_KEY")
db_url = os.getenv("DATABASE_URL")

# CORRECT: Secrets manager for production
# AWS: Secrets Manager, Parameter Store
# GCP: Secret Manager
# HashiCorp Vault for self-hosted

Secret Rotation

1. Generate new secret value
2. Deploy code that accepts BOTH old and new values
3. Update all consumers to use the new value
4. Verify old value is no longer in use
5. Revoke old value

Never: Rotate in-place without a transition period

Encryption Patterns

In Transit

// Redirect HTTP to HTTPS in production
app.use((req, res, next) => {
  if (
    req.headers["x-forwarded-proto"] !== "https" &&
    process.env.NODE_ENV === "production"
  ) {
    return res.redirect(301, `https://${req.hostname}${req.url}`);
  }
  next();
});
// HSTS header
app.use((req, res, next) => {
  res.setHeader(
    "Strict-Transport-Security",
    "max-age=31536000; includeSubDomains",
  );
  next();
});

At Rest

import crypto from "crypto";
const ALGORITHM = "aes-256-gcm";

function encrypt(
  plaintext: string,
  key: Buffer,
): { ciphertext: string; iv: string; tag: string } {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
  let ciphertext =
    cipher.update(plaintext, "utf8", "hex") + cipher.final("hex");
  return {
    ciphertext,
    iv: iv.toString("hex"),
    tag: cipher.getAuthTag().toString("hex"),
  };
}

function decrypt(
  ciphertext: string,
  key: Buffer,
  iv: string,
  tag: string,
): string {
  const decipher = crypto.createDecipheriv(
    ALGORITHM,
    key,
    Buffer.from(iv, "hex"),
  );
  decipher.setAuthTag(Buffer.from(tag, "hex"));
  return decipher.update(ciphertext, "hex", "utf8") + decipher.final("utf8");
}
// Use for PII, sensitive data. Encryption key in secrets manager, NOT in code.

CORS Configuration

import cors from "cors";

// WRONG: Allow everything
app.use(cors()); // origin: *, credentials: false

// WRONG: Wildcard with credentials
app.use(cors({ origin: "*", credentials: true })); // browsers reject this

// CORRECT: Explicit allowed origins
const ALLOWED_ORIGINS = [
  "https://myapp.com",
  "https://admin.myapp.com",
  ...(process.env.NODE_ENV !== "production" ? ["http://localhost:3000"] : []),
];

app.use(
  cors({
    origin: (origin, callback) => {
      if (!origin || ALLOWED_ORIGINS.includes(origin)) {
        callback(null, true);
      } else {
        callback(new Error("Not allowed by CORS"));
      }
    },
    credentials: true,
    methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
    allowedHeaders: ["Content-Type", "Authorization"],
    maxAge: 86400, // cache preflight for 24 hours
  }),
);

Rate Limiting

import rateLimit, { ipKeyGenerator } from "express-rate-limit";
import RedisStore from "rate-limit-redis";

// Global rate limit
app.use(
  rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    limit: 100, // 100 requests per window
    standardHeaders: true, // RateLimit-* headers
    legacyHeaders: false,
    store: new RedisStore({
      sendCommand: (...args) => redisClient.sendCommand(args),
    }),
  }),
);

// Strict limit on auth endpoints
app.use(
  "/api/auth/login",
  rateLimit({
    windowMs: 15 * 60 * 1000,
    limit: 5, // 5 login attempts per 15 min
    message: { error: "Too many login attempts. Try again later." },
  }),
);

// Per-API-key rate limiting for developer APIs
app.use(
  "/api/v1/",
  rateLimit({
    windowMs: 60 * 1000, // 1 minute
    limit: 60, // 60 requests per minute
    keyGenerator: (req) => req.apiClient?.id ?? ipKeyGenerator(req.ip),
  }),
);

Security Headers

import helmet from "helmet";

app.use(helmet()); // Sets many secure headers at once

// Key headers helmet sets:
// X-Content-Type-Options: nosniff
// X-Frame-Options: SAMEORIGIN
// Strict-Transport-Security: max-age=31536000; includeSubDomains
// Content-Security-Policy: default-src 'self'

// Customize CSP for your app
app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https://cdn.example.com"],
      connectSrc: ["'self'", "https://api.example.com"],
    },
  }),
);

Input Validation

import { z } from "zod";

// WRONG: Trusting user input directly (SQL injection risk)
app.post("/api/users", (req, res) => {
  db.query(`SELECT * FROM users WHERE email = '${req.body.email}'`);
});

// CORRECT: Validate with schema, use parameterized queries
const CreateUserSchema = z.object({
  email: z.string().email().max(255),
  name: z.string().trim().min(1).max(100),
  age: z.number().int().min(13).max(150).optional(),
});

app.post("/api/users", async (req, res) => {
  const result = CreateUserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ errors: result.error.flatten() });
  }
  // Use parameterized query (ORM or prepared statement)
  await db.user.create({ data: result.data });
});

Common Anti-Patterns Summary

AVOID                              DO INSTEAD
-------------------------------------------------------------------
JWT in localStorage                httpOnly secure cookie (refresh), memory (access)
MD5/SHA for passwords              bcrypt or argon2 with proper cost factor
Hardcoded secrets in code          Environment variables + secrets manager
cors({ origin: '*' })             Explicit allowed origins list
"Invalid password" message         "Invalid email or password" (no enumeration)
No rate limiting on auth           Strict rate limits on login/register
Rolling your own crypto            Use established libraries (jose, bcrypt)
Trusting user input                Validate with zod/joi, parameterized queries
Same API key forever               Rotate keys regularly, support multiple active
No HTTPS redirect                  Force HTTPS + HSTS header
Symmetric JWT for multi-service    Use RS256/ES256 (asymmetric) for distributed
No input length limits             Max length on all string inputs

FAQ

What does the security-patterns skill do?

Implements authentication, authorization, encryption, secrets management, and security hardening patterns. Use when designing auth flows, managing secrets, configuring CORS, implementing rate limiting, or when asked about JWT, OAuth, password hashing, API keys, RBAC, or security best practices.

How do I install security-patterns?

Run npx skills add https://github.com/cloudai-x/claude-workflow-v2 --skill security-patterns in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/security-patterns/ (personal) or .claude/skills/security-patterns/ (project).

Which AI agents can use security-patterns?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is security-patterns safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains security-patterns?

It is published by cloudai-x in cloudai-x/claude-workflow-v2. It has 126 recorded installs.

More from cloudai-x/claude-workflow-v2

View all →

analyzing-projects

cloudai-x/claude-workflow-v2

Open the skill to read its SKILL.md instructions.

designing-architecture

cloudai-x/claude-workflow-v2

Open the skill to read its SKILL.md instructions.

convex-backend

cloudai-x/claude-workflow-v2

Open the skill to read its SKILL.md instructions.

Backend & APIs216 installs

parallel-execution

cloudai-x/claude-workflow-v2

Patterns for parallel subagent execution using the Agent tool (formerly Task). Use when coordinating multiple independent tasks, spawning dynamic subagents, or implementing features that can be parallelized.

AI & Agents210 installs

optimizing-performance

cloudai-x/claude-workflow-v2

Open the skill to read its SKILL.md instructions.

managing-git

cloudai-x/claude-workflow-v2

Open the skill to read its SKILL.md instructions.

Popular security skills

View category →

azure-compliance

microsoft/azure-skills

Run Azure compliance and security audits with azqr plus Key Vault expiration checks. Covers best-practice assessment, resource review, policy/compliance validation, and security posture checks. WHEN: compliance scan, security audit, BEFORE running azqr (compliance cli tool), Azure best practices, Key Vault expiration check, expired certificates, expiring secrets, orphaned resources, compliance assessment.

OfficialSecurity567K installs

minimal-run-and-audit

lllllllama/rigorpilot-skills

Rigor Run skill for README-first deep learning repo reproduction. Use when the task is specifically to capture or normalize evidence from the selected smoke test or documented inference or evaluation command and write standardized reprooutputs/ files, including patch notes when repository files changed. Do not use for training execution, initial repo intake, generic environment setup, paper lookup, target selection, hidden scientific-meaning changes, or end-to-end orchestration by itself.

Security477.4K installs

seo-audit

coreyhaines31/marketingskills

When the user wants to audit, review, or diagnose SEO issues on their site. Also use when the user mentions "SEO audit," "technical SEO," "why am I not ranking," "SEO issues," "on-page SEO," "meta tags review," "SEO health check," "my traffic dropped," "lost rankings," "not showing up in Google," "site isn't ranking," "Google update hit me," "page speed," "core web vitals," "crawl errors," "indexing issues," "stuck on page 2," "striking distance keywords," "title tag rewrite," "low CTR," "local SEO," "Google Business Profile," or "map pack." Use this even if the user just says something vague like "my SEO is bad" or "help with SEO" — start with an audit. For building pages at scale to target keywords, see programmatic-seo. For adding structured data, see schema. For AI search optimization, see ai-seo.

Security238.7K installs

firebase-security-rules-auditor

firebase/agent-skills

Audits Firebase (Firestore, Cloud Storage) security rules for vulnerabilities, privilege escalation, role bypasses, create vs update inconsistencies, resource exhaustion, type safety, size limits, and hasOnly ownership checks. Use when auditing/reviewing rules, running red-team rule assessments, or scoring against auditor checklists. Don't use for Firebase CLI (login, deploy), Auth, Crashlytics, Remote Config, or database queries.

OfficialSecurity139.1K installs

audit

pbakaus/impeccable

Run technical quality checks across accessibility, performance, theming, responsive design, and anti-patterns. Generates a scored report with P0-P3 severity ratings and actionable plan. Use when the user wants an accessibility check, performance audit, or technical quality review.

Security81.9K installs

competitors

coreyhaines31/marketingskills

When the user wants to create competitor comparison or alternative pages for SEO and buyer-facing use. Also use when the user mentions 'alternative page,' 'vs page,' 'competitor comparison,' 'comparison page,' '[Product] vs [Product],' '[Product] alternative,' 'competitive landing pages,' 'how do we compare to X,' 'competitor teardown,' 'audit our competitor pages,' 'are our comparison pages out of date,' or 'competitive asset audit.' Use this for any content that positions your product against competitors. Covers four formats: singular alternative, plural alternatives, you vs competitor, and competitor vs competitor. For auditing existing claims (not researching competitors from scratch, which is competitor-profiling; not technical SEO on these pages, which is seo-audit), use the asset audit here. For internal battle cards and sales-specific competitor docs, see sales-enablement.

Security79.3K installs