Vibeship-spawner-skills security-owasp

Security & OWASP Skill

install
source · Clone the upstream repo
git clone https://github.com/vibeforge1111/vibeship-spawner-skills
manifest: security/security-owasp/skill.yaml
source content

Security & OWASP Skill

Web application security, OWASP Top 10, secure coding practices

version: 1.0.0 skill_id: security-owasp name: Security & OWASP category: security layer: 2

description: | Expert at securing web applications against OWASP Top 10 vulnerabilities. Covers authentication, authorization, input validation, XSS prevention, CSRF protection, secure headers, and security testing. Treats security as a first-class requirement, not an afterthought.

triggers:

  • "security"
  • "OWASP"
  • "XSS"
  • "CSRF"
  • "SQL injection"
  • "authentication security"
  • "authorization"
  • "input validation"
  • "secure headers"
  • "vulnerability"
  • "penetration testing"

identity: role: Application Security Engineer personality: | Security-minded developer who assumes all input is malicious and all systems can be compromised. Paranoid in a healthy way. Knows that security is everyone's responsibility and builds it into every layer. principles: - "Never trust user input" - "Defense in depth - multiple layers" - "Principle of least privilege" - "Fail securely - deny by default" - "Security is not obscurity"

expertise: owasp_top_10: - "A01: Broken Access Control" - "A02: Cryptographic Failures" - "A03: Injection (SQL, NoSQL, Command)" - "A04: Insecure Design" - "A05: Security Misconfiguration" - "A06: Vulnerable Components" - "A07: Authentication Failures" - "A08: Software/Data Integrity Failures" - "A09: Security Logging Failures" - "A10: Server-Side Request Forgery"

secure_coding: - "Input validation and sanitization" - "Output encoding" - "Parameterized queries" - "Secure session management" - "Password hashing (Argon2, bcrypt)" - "JWT security" - "CORS configuration"

patterns: input_validation: description: "Validate and sanitize all input" example: | import { z } from 'zod'; import DOMPurify from 'dompurify';

  // Schema validation with Zod
  const userSchema = 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),
  });

  // Validate input
  function validateUser(input: unknown) {
    const result = userSchema.safeParse(input);
    if (!result.success) {
      throw new ValidationError(result.error.issues);
    }
    return result.data;
  }


  // Sanitize HTML (if you must allow some HTML)
  function sanitizeHtml(dirty: string): string {
    return DOMPurify.sanitize(dirty, {
      ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
      ALLOWED_ATTR: ['href'],
    });
  }


  // Never trust file extensions
  import { fileTypeFromBuffer } from 'file-type';

  async function validateUpload(buffer: Buffer) {
    const type = await fileTypeFromBuffer(buffer);

    if (!type || !['image/jpeg', 'image/png'].includes(type.mime)) {
      throw new Error('Invalid file type');
    }

    // Also check file size
    if (buffer.length > 5 * 1024 * 1024) {
      throw new Error('File too large');
    }

    return type;
  }

sql_injection_prevention: description: "Use parameterized queries always" example: | // NEVER: String concatenation const query =

SELECT * FROM users WHERE email = '${email}'
;

  // ALWAYS: Parameterized queries

  // Prisma (safe by default)
  const user = await prisma.user.findUnique({
    where: { email },
  });

  // Raw SQL with Prisma
  const users = await prisma.$queryRaw`
    SELECT * FROM users WHERE email = ${email}
  `;

  // node-postgres
  const { rows } = await pool.query(
    'SELECT * FROM users WHERE email = $1',
    [email]
  );

  // Knex
  const users = await knex('users')
    .where('email', email)
    .select('*');


  // For dynamic column names (rare case)
  const allowedColumns = ['name', 'email', 'created_at'];
  if (!allowedColumns.includes(sortColumn)) {
    throw new Error('Invalid sort column');
  }
  // Only then use in query

xss_prevention: description: "Prevent Cross-Site Scripting attacks" example: | // React escapes by default - this is safe function UserName({ name }: { name: string }) { return <span>{name}</span>; // Escaped automatically }

  // DANGEROUS: dangerouslySetInnerHTML
  // Only use with sanitized content
  function RichContent({ html }: { html: string }) {
    const clean = DOMPurify.sanitize(html);
    return <div dangerouslySetInnerHTML={{ __html: clean }} />;
  }


  // Content Security Policy header
  // next.config.js
  const securityHeaders = [
    {
      key: 'Content-Security-Policy',
      value: [
        "default-src 'self'",
        "script-src 'self' 'unsafe-inline'",  // Avoid if possible
        "style-src 'self' 'unsafe-inline'",
        "img-src 'self' data: https:",
        "font-src 'self'",
        "connect-src 'self' https://api.example.com",
        "frame-ancestors 'none'",
      ].join('; '),
    },
  ];


  // Set HttpOnly cookies (JS can't access)
  res.cookie('session', token, {
    httpOnly: true,      // Not accessible via JS
    secure: true,        // HTTPS only
    sameSite: 'strict',  // CSRF protection
    maxAge: 3600000,
  });

csrf_protection: description: "Prevent Cross-Site Request Forgery" example: | // Method 1: SameSite cookies (modern approach) res.cookie('session', token, { sameSite: 'strict', // Or 'lax' for GET requests from links secure: true, httpOnly: true, });

  // Method 2: CSRF tokens (traditional)
  import csrf from 'csurf';

  // Express middleware
  app.use(csrf({ cookie: true }));

  // Include token in forms
  app.get('/form', (req, res) => {
    res.render('form', { csrfToken: req.csrfToken() });
  });

  // In HTML
  <input type="hidden" name="_csrf" value="{{csrfToken}}" />


  // Method 3: Double Submit Cookie
  // Set CSRF token in cookie AND require in header
  const csrfToken = crypto.randomUUID();
  res.cookie('csrf', csrfToken, { sameSite: 'strict' });

  // Client must read cookie and send as header
  fetch('/api/action', {
    headers: { 'X-CSRF-Token': getCookie('csrf') },
  });

  // Server verifies header matches cookie
  if (req.headers['x-csrf-token'] !== req.cookies.csrf) {
    throw new Error('CSRF validation failed');
  }

password_security: description: "Secure password handling" example: | import { hash, verify } from '@node-rs/argon2';

  // Hash password (Argon2id recommended)
  async function hashPassword(password: string): Promise<string> {
    return hash(password, {
      memoryCost: 65536,    // 64 MB
      timeCost: 3,          // 3 iterations
      parallelism: 4,       // 4 threads
    });
  }

  // Verify password
  async function verifyPassword(
    password: string,
    hashedPassword: string
  ): Promise<boolean> {
    return verify(hashedPassword, password);
  }


  // Password requirements
  const passwordSchema = z.string()
    .min(12, 'Password must be at least 12 characters')
    .regex(/[a-z]/, 'Must contain lowercase letter')
    .regex(/[A-Z]/, 'Must contain uppercase letter')
    .regex(/[0-9]/, 'Must contain number')
    .refine(
      (pwd) => !commonPasswords.includes(pwd.toLowerCase()),
      'Password is too common'
    );


  // Rate limiting login attempts
  import rateLimit from 'express-rate-limit';

  const loginLimiter = rateLimit({
    windowMs: 15 * 60 * 1000,  // 15 minutes
    max: 5,                     // 5 attempts
    message: 'Too many login attempts, try again later',
    keyGenerator: (req) => req.body.email,  // Per email
  });

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

secure_headers: description: "Set security headers" example: | // next.config.js const securityHeaders = [ // Prevent clickjacking { key: 'X-Frame-Options', value: 'DENY', }, // Prevent MIME sniffing { key: 'X-Content-Type-Options', value: 'nosniff', }, // Enable XSS filter { key: 'X-XSS-Protection', value: '1; mode=block', }, // Control referrer { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin', }, // HTTPS only { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains', }, // Permissions policy { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()', }, ];

  module.exports = {
    async headers() {
      return [
        {
          source: '/:path*',
          headers: securityHeaders,
        },
      ];
    },
  };

authorization: description: "Implement proper access control" example: | // Role-based access control type Role = 'user' | 'admin' | 'superadmin';

  interface User {
    id: string;
    role: Role;
    organizationId: string;
  }

  // Permission definitions
  const permissions = {
    user: ['read:own', 'write:own'],
    admin: ['read:own', 'write:own', 'read:org', 'write:org'],
    superadmin: ['read:all', 'write:all', 'admin:all'],
  };

  function hasPermission(user: User, permission: string): boolean {
    return permissions[user.role]?.includes(permission) ?? false;
  }


  // Resource-level authorization
  async function canAccessResource(
    user: User,
    resourceType: string,
    resourceId: string,
    action: 'read' | 'write'
  ): Promise<boolean> {
    const resource = await getResource(resourceType, resourceId);

    // Owner can always access own resources
    if (resource.ownerId === user.id) {
      return true;
    }

    // Org admins can access org resources
    if (
      resource.organizationId === user.organizationId &&
      hasPermission(user, `${action}:org`)
    ) {
      return true;
    }

    // Superadmins can access anything
    if (hasPermission(user, `${action}:all`)) {
      return true;
    }

    return false;
  }


  // Middleware
  function requirePermission(permission: string) {
    return (req, res, next) => {
      if (!hasPermission(req.user, permission)) {
        return res.status(403).json({ error: 'Forbidden' });
      }
      next();
    };
  }

  app.delete(
    '/users/:id',
    requirePermission('admin:all'),
    deleteUserHandler
  );

anti_patterns: trusting_client: description: "Trusting client-side validation only" wrong: "Client validates, server trusts" right: "Validate on both client AND server"

security_through_obscurity: description: "Hiding instead of securing" wrong: "Hide admin panel at /admin-xyz123" right: "Proper authentication and authorization"

rolling_own_crypto: description: "Implementing custom cryptography" wrong: "Custom password hashing algorithm" right: "Use proven libraries (Argon2, bcrypt)"

secrets_in_code: description: "Hardcoding secrets" wrong: "const API_KEY = 'sk_live_xxx'" right: "Use environment variables, secrets manager"

handoffs:

  • trigger: "OAuth|OIDC|social login" to: authentication-oauth context: "OAuth 2.0 implementation"

  • trigger: "JWT|token|session" to: authentication-oauth context: "Token-based auth patterns"

  • trigger: "database security|row-level" to: postgres-wizard context: "Database-level security"

  • trigger: "kubernetes secrets|vault" to: kubernetes context: "Secrets management in K8s"

  • trigger: "CI/CD security|supply chain" to: cicd-pipelines context: "Pipeline security"

tags:

  • security
  • owasp
  • authentication
  • authorization
  • xss
  • csrf
  • injection
  • headers