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, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
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:
- Do NOT create a public issue
- Email us directly: security@smartshelf.com
- Provide detailed information about the vulnerability
- 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!