Security Guidelines

Security best practices and vulnerability reporting

Security Guidelines

Security best practices for development and guidelines for reporting security vulnerabilities.

Security Best Practices

Input Validation

Always validate and sanitize user input:

// Validate using Zod schemas
const productSchema = z.object({
  name: z.string().min(1).max(255).regex(/^[a-zA-Z0-9\s\-_]+$/),
  price: z.number().positive().max(999999),
  sku: z.string().min(1).max(100).regex(/^[A-Z0-9\-]+$/),
})

export async function createProduct(data: unknown) {
  // Validate input
  const validatedData = productSchema.parse(data)
  
  // Sanitize for database
  const sanitizedData = {
    name: validatedData.name.trim(),
    price: Math.round(validatedData.price * 100) / 100,
    sku: validatedData.sku.toUpperCase(),
  }
  
  return await productService.create(sanitizedData)
}

Authentication and Authorization

JWT Token Handling

// lib/auth/jwt.ts
import jwt from 'jsonwebtoken'
import { cookies } from 'next/headers'

export function verifyToken(token: string) {
  try {
    return jwt.verify(token, process.env.JWT_SECRET!)
  } catch (error) {
    throw new Error('Invalid token')
  }
}

export function getTokenFromCookies() {
  const cookieStore = cookies()
  const token = cookieStore.get('auth-token')?.value
  
  if (!token) {
    throw new Error('No authentication token found')
  }
  
  return verifyToken(token)
}

Route Protection

// lib/middleware/auth.ts
import { NextRequest, NextResponse } from 'next/server'
import { getTokenFromCookies } from '@/lib/auth/jwt'

export function withAuth(handler: Function) {
  return async (request: NextRequest) => {
    try {
      const user = getTokenFromCookies()
      
      // Add user to request context
      request.user = user
      
      return await handler(request)
    } catch (error) {
      return NextResponse.json(
        { error: 'Unauthorized' },
        { status: 401 }
      )
    }
  }
}

export function withRole(roles: string[]) {
  return function(handler: Function) {
    return withAuth(async (request: NextRequest) => {
      const user = request.user
      
      if (!roles.includes(user.role)) {
        return NextResponse.json(
          { error: 'Forbidden' },
          { status: 403 }
        )
      }
      
      return await handler(request)
    })
  }
}

Database Security

Parameterized Queries

// Always use parameterized queries
export async function getProductsByCategory(categoryId: string) {
  const query = `
    SELECT * FROM products 
    WHERE category_id = $1 AND is_active = true
  `
  
  return await db.query(query, [categoryId])
}

// NEVER do this (SQL injection vulnerability)
export async function getProductsByCategoryBad(categoryId: string) {
  const query = `
    SELECT * FROM products 
    WHERE category_id = '${categoryId}' AND is_active = true
  `
  
  return await db.query(query) // Vulnerable to SQL injection
}

Row Level Security

-- Enable RLS on tables
ALTER TABLE products ENABLE ROW LEVEL SECURITY;

-- Create policies
CREATE POLICY "Users can view active products" ON products
  FOR SELECT USING (is_active = true);

CREATE POLICY "Admins can manage all products" ON products
  FOR ALL USING (auth.role() = 'admin');

API Security

Rate Limiting

// lib/middleware/rate-limit.ts
import { NextRequest, NextResponse } from 'next/server'
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '1 m'), // 10 requests per minute
})

export function withRateLimit(handler: Function) {
  return async (request: NextRequest) => {
    const ip = request.headers.get('x-forwarded-for') || 'unknown'
    
    const { success, limit, reset, remaining } = await ratelimit.limit(ip)
    
    if (!success) {
      return NextResponse.json(
        { error: 'Rate limit exceeded' },
        { 
          status: 429,
          headers: {
            'X-RateLimit-Limit': limit.toString(),
            'X-RateLimit-Remaining': remaining.toString(),
            'X-RateLimit-Reset': new Date(reset).toISOString(),
          }
        }
      )
    }
    
    return await handler(request)
  }
}

CORS Configuration

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [
          { key: 'Access-Control-Allow-Origin', value: 'https://yourdomain.com' },
          { key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE,OPTIONS' },
          { key: 'Access-Control-Allow-Headers', value: 'Content-Type,Authorization' },
        ],
      },
    ]
  },
}

Environment Variables

Secure Configuration

// lib/config/env.ts
import { z } from 'zod'

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  NEXTAUTH_SECRET: z.string().min(32),
  NEXTAUTH_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  REDIS_URL: z.string().url().optional(),
})

export const env = envSchema.parse(process.env)
# .env.example
DATABASE_URL=postgresql://user:password@localhost:5432/smartshelf
NEXTAUTH_SECRET=your-very-long-secret-key-here-at-least-32-characters
NEXTAUTH_URL=http://localhost:3000
JWT_SECRET=another-very-long-secret-key-for-jwt-tokens
REDIS_URL=redis://localhost:6379

XSS Prevention

Content Security Policy

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: [
              "default-src 'self'",
              "script-src 'self' 'unsafe-eval' 'unsafe-inline'",
              "style-src 'self' 'unsafe-inline'",
              "img-src 'self' data: https:",
              "font-src 'self'",
              "connect-src 'self'",
            ].join('; '),
          },
        ],
      },
    ]
  },
}

Input Sanitization

// lib/utils/sanitize.ts
import DOMPurify from 'dompurify'
import { JSDOM } from 'jsdom'

const window = new JSDOM('').window
const purify = DOMPurify(window)

export function sanitizeHtml(html: string): string {
  return purify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br'],
    ALLOWED_ATTR: []
  })
}

export function escapeHtml(text: string): string {
  return text
    .replace(/&/g, '&')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;')
}

Error Handling

Secure Error Responses

// lib/middleware/error-handler.ts
import { NextRequest, NextResponse } from 'next/server'

export function withErrorHandler(handler: Function) {
  return async (request: NextRequest) => {
    try {
      return await handler(request)
    } catch (error) {
      console.error('API Error:', error)
      
      // Don't expose internal errors in production
      const isDev = process.env.NODE_ENV === 'development'
      
      if (error instanceof ValidationError) {
        return NextResponse.json(
          {
            error: 'Validation failed',
            details: error.details
          },
          { status: 400 }
        )
      }
      
      if (error instanceof AuthError) {
        return NextResponse.json(
          { error: 'Authentication required' },
          { status: 401 }
        )
      }
      
      // Generic error response
      return NextResponse.json(
        {
          error: isDev ? error.message : 'Internal server error'
        },
        { status: 500 }
      )
    }
  }
}

Reporting Security Issues

Responsible Disclosure

If you discover a security vulnerability, please follow these steps:

  1. Do NOT create a public issue
  2. Email us directly: security@smartshelf.com
  3. Provide detailed information about the vulnerability
  4. Allow time for response before public disclosure

Security Report Template

Subject: Security Vulnerability Report - [Brief Description]

**Vulnerability Type**
[e.g., SQL Injection, XSS, Authentication Bypass, etc.]

**Affected Component**
[e.g., User authentication, Product API, Admin panel, etc.]

**Severity Level**
- [ ] Critical (Remote code execution, data breach)
- [ ] High (Privilege escalation, authentication bypass)
- [ ] Medium (Information disclosure, CSRF)
- [ ] Low (Minor information leak, configuration issue)

**Description**
[Detailed description of the vulnerability]

**Steps to Reproduce**
1. [Step 1]
2. [Step 2]
3. [Step 3]

**Proof of Concept**
[Code, screenshots, or other evidence]

**Impact**
[Potential impact if exploited]

**Suggested Fix**
[Your recommendations for fixing the issue]

**Contact Information**
- Name: [Your name]
- Email: [Your email]
- PGP Key: [If available]

Response Timeline

We commit to:

  • Acknowledge receipt within 24 hours
  • Initial assessment within 72 hours
  • Regular updates on progress
  • Resolution based on severity:
    • Critical: 1-3 days
    • High: 1-2 weeks
    • Medium: 2-4 weeks
    • Low: Next scheduled release

Security Bounty

We offer recognition for responsible disclosure:

  • Hall of Fame: Recognition in our security acknowledgments
  • Swag: Company merchandise for valid reports
  • References: Professional references for security researchers

Security Review Checklist

Use this checklist when reviewing code for security issues:

Authentication & Authorization

  • Authentication required for protected routes
  • Authorization checks implemented
  • Session management secure
  • Password policies enforced
  • Account lockout mechanisms

Input Validation

  • All input validated and sanitized
  • SQL injection prevention
  • XSS prevention
  • CSRF protection
  • File upload security

Data Protection

  • Sensitive data encrypted
  • Secure transmission (HTTPS)
  • Proper key management
  • Data retention policies
  • Audit logging

API Security

  • Rate limiting implemented
  • CORS properly configured
  • Authentication tokens secure
  • Error handling secure
  • Input/output validation

Infrastructure

  • Dependencies up to date
  • Security headers configured
  • Environment variables secure
  • Monitoring and alerting
  • Backup and recovery

Security Tools

Automated Security Scanning

# Install security scanning tools
npm install -g @cyclonedx/cyclonedx-npm audit-ci

# Generate SBOM
cyclonedx-npm --output-file sbom.json

# Run security audit
npm audit --audit-level=high

# Check for outdated dependencies
npm outdated

Code Security Analysis

# Install security linting
npm install -D eslint-plugin-security

# Add to ESLint config
{
  "plugins": ["security"],
  "extends": ["plugin:security/recommended"]
}

Environment Security

# Check for secrets in code
git secrets --scan

# Validate environment configuration
npm run env:validate

Remember: Security is everyone's responsibility. When in doubt, ask for a security review!