Agent Skill
2/7/2026

cyber-security-core-skills

Core web application security patterns for Next.js 16. Use when implementing input validation, SQL injection prevention, XSS protection, CSRF defense, and secure coding practices. Essential for building cyber-hardened applications.

L
lewisperez999
0GitHub Stars
2Views
npx skills add lewisperez999/digital-twin-iii

SKILL.md

Namecyber-security-core-skills
DescriptionCore web application security patterns for Next.js 16. Use when implementing input validation, SQL injection prevention, XSS protection, CSRF defense, and secure coding practices. Essential for building cyber-hardened applications.

name: cyber-security-core-skills description: Core web application security patterns for Next.js 16. Use when implementing input validation, SQL injection prevention, XSS protection, CSRF defense, and secure coding practices. Essential for building cyber-hardened applications. license: MIT

Cyber Security Core Skills

Comprehensive security patterns for building cyber-hardened Next.js 16 applications with defense-in-depth strategies.

Table of Contents

  1. Input Validation & Sanitization
  2. SQL Injection Prevention
  3. XSS Protection
  4. CSRF Protection
  5. Security Headers
  6. Secure API Design
  7. Error Handling
  8. Best Practices

Input Validation & Sanitization

Zod Schema Validation

Always validate all inputs at the API boundary:

// lib/validations/user.ts
import { z } from 'zod';

// Strict input schemas with security constraints
export const userInputSchema = z.object({
  email: z
    .string()
    .email('Invalid email format')
    .max(255, 'Email too long')
    .toLowerCase()
    .trim(),
  username: z
    .string()
    .min(3, 'Username too short')
    .max(30, 'Username too long')
    .regex(/^[a-zA-Z0-9_-]+$/, 'Only alphanumeric, underscore, and hyphen allowed'),
  password: z
    .string()
    .min(12, 'Password must be at least 12 characters')
    .max(128, 'Password too long')
    .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'),
  bio: z
    .string()
    .max(500, 'Bio too long')
    .optional()
    .transform(val => val ? sanitizeHtml(val) : val),
});

// Search input with injection prevention
export const searchSchema = z.object({
  query: z
    .string()
    .max(100, 'Search query too long')
    .transform(val => val.replace(/[<>\"\'%;()&+]/g, '')), // Remove dangerous chars
  page: z.coerce.number().int().positive().max(1000).default(1),
  limit: z.coerce.number().int().positive().max(100).default(20),
});

Server Action Validation

// app/actions/user.ts
'use server';

import { userInputSchema } from '@/lib/validations/user';
import { revalidatePath } from 'next/cache';

export async function createUser(formData: FormData) {
  // Parse and validate all input
  const rawInput = {
    email: formData.get('email'),
    username: formData.get('username'),
    password: formData.get('password'),
    bio: formData.get('bio'),
  };

  const result = userInputSchema.safeParse(rawInput);
  
  if (!result.success) {
    // Log validation failure for security monitoring
    console.warn('[SECURITY] Validation failed:', {
      timestamp: new Date().toISOString(),
      errors: result.error.flatten(),
      // Never log actual input values
    });
    
    return { 
      success: false, 
      error: 'Invalid input provided',
      fieldErrors: result.error.flatten().fieldErrors,
    };
  }

  const validatedData = result.data;
  
  // Proceed with validated data only
  // ...
}

HTML Sanitization

// lib/security/sanitize.ts
import DOMPurify from 'isomorphic-dompurify';

export function sanitizeHtml(dirty: string): string {
  return DOMPurify.sanitize(dirty, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br'],
    ALLOWED_ATTR: [],
    KEEP_CONTENT: true,
  });
}

export function sanitizeForDisplay(input: string): string {
  return input
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;');
}

export function sanitizeFilename(filename: string): string {
  return filename
    .replace(/[^a-zA-Z0-9._-]/g, '_')
    .replace(/\.{2,}/g, '.')
    .substring(0, 255);
}

SQL Injection Prevention

Parameterized Queries with Prisma

Always use Prisma's parameterized queries:

// SECURE: Prisma automatically parameterizes
const user = await prisma.user.findUnique({
  where: { email: userInput }, // Safe - parameterized
});

// SECURE: Using Prisma's built-in methods
const users = await prisma.user.findMany({
  where: {
    OR: [
      { email: { contains: searchTerm } },
      { username: { contains: searchTerm } },
    ],
  },
});

// DANGEROUS: Never do this!
// const user = await prisma.$queryRaw`SELECT * FROM users WHERE email = '${userEmail}'`;

// SECURE: If raw queries needed, use parameterized version
const users = await prisma.$queryRaw`
  SELECT * FROM users 
  WHERE email = ${email} 
  AND status = ${status}
`;

Query Builder Safety

// lib/db/safe-queries.ts
import { Prisma } from '@prisma/client';

// Safe dynamic query building
export function buildUserSearchQuery(filters: {
  email?: string;
  username?: string;
  role?: string;
  createdAfter?: Date;
}) {
  const where: Prisma.UserWhereInput = {};

  if (filters.email) {
    where.email = { contains: filters.email, mode: 'insensitive' };
  }
  
  if (filters.username) {
    where.username = { contains: filters.username, mode: 'insensitive' };
  }
  
  if (filters.role) {
    // Validate against allowed values
    const allowedRoles = ['user', 'admin', 'moderator'];
    if (allowedRoles.includes(filters.role)) {
      where.role = filters.role;
    }
  }
  
  if (filters.createdAfter) {
    where.createdAt = { gte: filters.createdAfter };
  }

  return where;
}

XSS Protection

Content Security Policy

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  
  // Content Security Policy
  const csp = [
    "default-src 'self'",
    "script-src 'self' 'unsafe-inline' 'unsafe-eval'", // Adjust for your needs
    "style-src 'self' 'unsafe-inline'",
    "img-src 'self' data: https:",
    "font-src 'self'",
    "connect-src 'self' https://api.yourdomain.com",
    "frame-ancestors 'none'",
    "base-uri 'self'",
    "form-action 'self'",
  ].join('; ');

  response.headers.set('Content-Security-Policy', csp);
  response.headers.set('X-Content-Type-Options', 'nosniff');
  response.headers.set('X-Frame-Options', 'DENY');
  response.headers.set('X-XSS-Protection', '1; mode=block');
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
  response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');

  return response;
}

Safe Content Rendering

// components/SafeContent.tsx
'use client';

import DOMPurify from 'dompurify';
import { useMemo } from 'react';

interface SafeContentProps {
  html: string;
  allowedTags?: string[];
}

export function SafeContent({ html, allowedTags }: SafeContentProps) {
  const sanitizedHtml = useMemo(() => {
    return DOMPurify.sanitize(html, {
      ALLOWED_TAGS: allowedTags || ['p', 'b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li'],
      ALLOWED_ATTR: ['href', 'target', 'rel'],
      ADD_ATTR: ['target'], // Force target="_blank" on links
      FORBID_TAGS: ['script', 'style', 'iframe', 'form', 'input'],
    });
  }, [html, allowedTags]);

  return (
    <div 
      dangerouslySetInnerHTML={{ __html: sanitizedHtml }}
      className="prose"
    />
  );
}

// Usage - never render user content without sanitization
// <SafeContent html={userGeneratedContent} />

Escape User Input in Templates

// Always use React's built-in escaping
function UserProfile({ user }: { user: User }) {
  return (
    <div>
      {/* SAFE: React escapes by default */}
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
      
      {/* DANGEROUS: Only use with sanitized content */}
      {/* <div dangerouslySetInnerHTML={{ __html: user.bio }} /> */}
    </div>
  );
}

CSRF Protection

Server Actions with Validation

// app/actions/secure-action.ts
'use server';

import { headers } from 'next/headers';

async function validateRequest() {
  const headersList = await headers();
  const origin = headersList.get('origin');
  const host = headersList.get('host');
  
  // Validate origin matches host
  if (origin) {
    const originUrl = new URL(origin);
    if (originUrl.host !== host) {
      throw new Error('Invalid request origin');
    }
  }
  
  return true;
}

export async function secureAction(formData: FormData) {
  await validateRequest();
  
  // Proceed with action
}

CSRF Token Implementation

// lib/security/csrf.ts
import { cookies } from 'next/headers';
import { randomBytes, createHmac } from 'crypto';

const CSRF_SECRET = process.env.CSRF_SECRET!;

export async function generateCsrfToken(): Promise<string> {
  const token = randomBytes(32).toString('hex');
  const signature = createHmac('sha256', CSRF_SECRET)
    .update(token)
    .digest('hex');
  
  const cookieStore = await cookies();
  cookieStore.set('csrf_token', token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    maxAge: 3600, // 1 hour
  });
  
  return `${token}.${signature}`;
}

export async function validateCsrfToken(clientToken: string): Promise<boolean> {
  const cookieStore = await cookies();
  const storedToken = cookieStore.get('csrf_token')?.value;
  
  if (!storedToken || !clientToken) return false;
  
  const [token, signature] = clientToken.split('.');
  
  if (token !== storedToken) return false;
  
  const expectedSignature = createHmac('sha256', CSRF_SECRET)
    .update(token)
    .digest('hex');
  
  return signature === expectedSignature;
}

Security Headers

Next.js Config Headers

// next.config.ts
import type { NextConfig } from 'next';

const securityHeaders = [
  {
    key: 'X-DNS-Prefetch-Control',
    value: 'on',
  },
  {
    key: 'Strict-Transport-Security',
    value: 'max-age=63072000; includeSubDomains; preload',
  },
  {
    key: 'X-Content-Type-Options',
    value: 'nosniff',
  },
  {
    key: 'X-Frame-Options',
    value: 'DENY',
  },
  {
    key: 'X-XSS-Protection',
    value: '1; mode=block',
  },
  {
    key: 'Referrer-Policy',
    value: 'strict-origin-when-cross-origin',
  },
  {
    key: 'Permissions-Policy',
    value: 'camera=(), microphone=(), geolocation=()',
  },
];

const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: securityHeaders,
      },
    ];
  },
};

export default nextConfig;

Secure API Design

API Route Protection

// app/api/secure/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { rateLimit } from '@/lib/security/rate-limit';
import { validateApiKey } from '@/lib/security/api-key';

const requestSchema = z.object({
  action: z.enum(['read', 'write', 'delete']),
  resourceId: z.string().uuid(),
});

export async function POST(request: NextRequest) {
  try {
    // 1. Rate limiting
    const ip = request.headers.get('x-forwarded-for') || 'unknown';
    const rateLimitResult = await rateLimit(ip, 'api', 100, 60000);
    
    if (!rateLimitResult.success) {
      return NextResponse.json(
        { error: 'Too many requests' },
        { 
          status: 429,
          headers: {
            'Retry-After': String(rateLimitResult.retryAfter),
            'X-RateLimit-Limit': '100',
            'X-RateLimit-Remaining': '0',
          },
        }
      );
    }

    // 2. API Key validation
    const apiKey = request.headers.get('x-api-key');
    if (!apiKey || !await validateApiKey(apiKey)) {
      return NextResponse.json(
        { error: 'Unauthorized' },
        { status: 401 }
      );
    }

    // 3. Input validation
    const body = await request.json();
    const result = requestSchema.safeParse(body);
    
    if (!result.success) {
      return NextResponse.json(
        { error: 'Invalid request', details: result.error.flatten() },
        { status: 400 }
      );
    }

    // 4. Process request with validated data
    const { action, resourceId } = result.data;
    
    // ... business logic

    return NextResponse.json({ success: true });
    
  } catch (error) {
    // Never expose internal errors
    console.error('[API_ERROR]', error);
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}

Error Handling

Secure Error Responses

// lib/security/error-handler.ts
export class AppError extends Error {
  constructor(
    message: string,
    public statusCode: number = 500,
    public isOperational: boolean = true,
    public code?: string
  ) {
    super(message);
    Error.captureStackTrace(this, this.constructor);
  }
}

// Safe error messages for clients
const SAFE_ERROR_MESSAGES: Record<string, string> = {
  VALIDATION_ERROR: 'Invalid input provided',
  AUTH_ERROR: 'Authentication failed',
  NOT_FOUND: 'Resource not found',
  FORBIDDEN: 'Access denied',
  RATE_LIMIT: 'Too many requests',
  DEFAULT: 'An error occurred',
};

export function getSafeErrorMessage(error: unknown): string {
  if (error instanceof AppError && error.isOperational) {
    return error.message;
  }
  
  // Never expose internal error details
  return SAFE_ERROR_MESSAGES.DEFAULT;
}

export function handleApiError(error: unknown) {
  // Log full error internally
  console.error('[ERROR]', {
    timestamp: new Date().toISOString(),
    error: error instanceof Error ? {
      name: error.name,
      message: error.message,
      stack: error.stack,
    } : error,
  });

  // Return safe response
  const statusCode = error instanceof AppError ? error.statusCode : 500;
  const message = getSafeErrorMessage(error);
  
  return { statusCode, message };
}

Best Practices

Security Checklist

  1. Input Validation

    • Validate ALL inputs at API boundaries
    • Use strict schemas with type coercion
    • Sanitize HTML content before storage and display
    • Limit input lengths to prevent DoS
  2. Output Encoding

    • Use React's built-in escaping
    • Sanitize any dangerouslySetInnerHTML usage
    • Set appropriate Content-Type headers
  3. Authentication

    • Use secure session management
    • Implement proper password hashing (bcrypt/argon2)
    • Enable MFA for sensitive operations
  4. Authorization

    • Check permissions on every request
    • Use principle of least privilege
    • Validate ownership before modifications
  5. Data Protection

    • Encrypt sensitive data at rest
    • Use HTTPS for all communications
    • Implement proper key management
  6. Logging & Monitoring

    • Log security events
    • Never log sensitive data (passwords, tokens)
    • Set up alerts for suspicious activity

Dependencies to Install

npm install zod isomorphic-dompurify
npm install -D @types/dompurify

Environment Variables

# .env.local
CSRF_SECRET=your-32-character-random-secret
API_KEY_SECRET=your-api-key-encryption-secret

Troubleshooting

Common Issues

  1. CSP Blocking Resources

    • Check browser console for CSP violations
    • Adjust CSP directives for legitimate resources
    • Use nonces for inline scripts if needed
  2. CORS Errors

    • Configure allowed origins explicitly
    • Don't use * in production
  3. Validation Bypasses

    • Ensure validation runs server-side
    • Never trust client-side validation alone
Skills Info
Original Name:cyber-security-core-skillsAuthor:lewisperez999