Security Testing & Verification
Built-In Security Tests
This project includes automated tests and verification scripts for all security features.
Testing Rate Limiting
Automated Test Script
# Run the provided test script node scripts/test-rate-limit.js
What it tests:
- •Makes 10 consecutive requests to rate-limited endpoint
- •Verifies first 5 succeed (HTTP 200)
- •Verifies requests 6-10 are blocked (HTTP 429)
- •Tests rate limit reset after 60 seconds
Expected output:
Testing Rate Limiting (5 requests/minute per IP) Request 1: ✓ 200 - Success Request 2: ✓ 200 - Success Request 3: ✓ 200 - Success Request 4: ✓ 200 - Success Request 5: ✓ 200 - Success Request 6: ✗ 429 - Too many requests Request 7: ✗ 429 - Too many requests Request 8: ✗ 429 - Too many requests Request 9: ✗ 429 - Too many requests Request 10: ✗ 429 - Too many requests ✓ Rate limiting is working correctly!
Manual Testing
# Test rate limiting manually
for i in {1..10}; do
echo "Request $i:"
curl -s -o /dev/null -w "%{http_code}\n" \
http://localhost:3000/api/test-rate-limit
sleep 0.1
done
# Expected:
# Requests 1-5: 200
# Requests 6-10: 429
Test Reset After Window
# Make 5 requests
for i in {1..5}; do
curl http://localhost:3000/api/test-rate-limit
done
# Wait 61 seconds (rate limit window = 60 seconds)
sleep 61
# Try again - should succeed
curl http://localhost:3000/api/test-rate-limit
# Expected: 200 OK (limit reset)
Testing CSRF Protection
Test 1: Request Without Token (Should Fail)
curl -X POST http://localhost:3000/api/example-protected \
-H "Content-Type: application/json" \
-d '{"title": "test"}'
# Expected: 403 Forbidden
# {
# "error": "CSRF token missing"
# }
Test 2: Request With Valid Token (Should Succeed)
# Step 1: Get CSRF token
TOKEN=$(curl -s http://localhost:3000/api/csrf \
-c cookies.txt | jq -r '.csrfToken')
# Step 2: Use token in request
curl -X POST http://localhost:3000/api/example-protected \
-b cookies.txt \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: $TOKEN" \
-d '{"title": "test"}'
# Expected: 200 OK
Test 3: Token Reuse (Should Fail)
# Get token
TOKEN=$(curl -s http://localhost:3000/api/csrf \
-c cookies.txt | jq -r '.csrfToken')
# Use once (succeeds)
curl -X POST http://localhost:3000/api/example-protected \
-b cookies.txt \
-H "X-CSRF-Token: $TOKEN" \
-d '{"title": "test"}'
# Try to reuse same token (should fail)
curl -X POST http://localhost:3000/api/example-protected \
-b cookies.txt \
-H "X-CSRF-Token: $TOKEN" \
-d '{"title": "test2"}'
# Expected: 403 Forbidden - Token already used
Test 4: Invalid Token (Should Fail)
curl -X POST http://localhost:3000/api/example-protected \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: fake-token-12345" \
-d '{"title": "test"}'
# Expected: 403 Forbidden
# {
# "error": "CSRF token invalid"
# }
Testing Input Validation
Test XSS Sanitization
# Test script tags removal
curl -X POST http://localhost:3000/api/example-protected \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <get-token-first>" \
-d '{"title": "<script>alert(1)</script>"}'
# Expected: 200 OK
# Title sanitized to: "alert(1)"
# < and > removed
Test Length Validation
# Test too-long input
curl -X POST http://localhost:3000/api/example-protected \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-d "{\"title\": \"$(printf 'A%.0s' {1..200})\"}"
# Expected: 400 Bad Request
# {
# "error": "Validation failed",
# "details": {
# "title": "String must contain at most 100 character(s)"
# }
# }
Test Email Validation
curl -X POST http://localhost:3000/api/contact \
-H "Content-Type: application/json" \
-d '{
"name": "Test User",
"email": "not-an-email",
"subject": "Test",
"message": "Test message"
}'
# Expected: 400 Bad Request
# {
# "error": "Validation failed",
# "details": {
# "email": "Invalid email"
# }
# }
Test Required Fields
curl -X POST http://localhost:3000/api/contact \
-H "Content-Type: application/json" \
-d '{
"name": "Test User"
}'
# Expected: 400 Bad Request with missing field errors
Testing Security Headers
Test All Headers
curl -I http://localhost:3000 # Expected headers: # Content-Security-Policy: default-src 'self'; ... # X-Frame-Options: DENY # X-Content-Type-Options: nosniff # (HSTS only in production)
Test CSP
# Check CSP includes required domains curl -I http://localhost:3000 | grep "Content-Security-Policy" # Should include: # - script-src with Clerk domain # - connect-src with Convex domain # - frame-src with Stripe domain
Test HSTS (Production Only)
# In production environment curl -I https://yourapp.com | grep "Strict-Transport-Security" # Should return: # Strict-Transport-Security: max-age=31536000; includeSubDomains
Test Protected Route Headers
curl -I http://localhost:3000/dashboard # Should include: # X-Robots-Tag: noindex, nofollow
Testing Authentication
Test Unauthenticated Access
# Try to access protected API without auth
curl http://localhost:3000/api/protected-endpoint
# Expected: 401 Unauthorized
# {
# "error": "Unauthorized",
# "message": "Authentication required"
# }
Test Authenticated Access
# With valid Clerk session cookie curl http://localhost:3000/api/protected-endpoint \ -H "Cookie: __session=<clerk-session-token>" # Expected: 200 OK (with authorized response)
Test Authorization (Resource Ownership)
# Try to access another user's resource
curl http://localhost:3000/api/posts/user-abc-post-123 \
-H "Cookie: __session=<different-user-token>"
# Expected: 403 Forbidden
# {
# "error": "Forbidden",
# "message": "You do not have access to this resource"
# }
Test Subscription Gating
# Try premium feature with free account
curl http://localhost:3000/api/premium/generate \
-H "Cookie: __session=<free-user-token>"
# Expected: 403 Forbidden
# {
# "error": "Forbidden",
# "message": "Premium subscription required"
# }
Testing Error Handling
Test Production Error Messages
# Set NODE_ENV=production temporarily
export NODE_ENV=production
# Trigger error in API
curl http://localhost:3000/api/error-test
# Expected: Generic message (no stack trace)
# {
# "error": "Internal server error",
# "message": "An unexpected error occurred"
# }
Test Development Error Messages
# In development (NODE_ENV=development)
curl http://localhost:3000/api/error-test
# Expected: Detailed error with stack trace
# {
# "error": "Internal server error",
# "message": "Specific error message",
# "stack": "Error: ...\n at ...",
# "context": "error-test"
# }
Testing Dependency Security
Run npm Audit
# Check for vulnerabilities npm audit # Expected: 0 vulnerabilities # found 0 vulnerabilities
Run Production Audit
# Only check production dependencies npm audit --production # Expected: 0 vulnerabilities
Check Outdated Packages
npm outdated # Expected: All packages up-to-date # (or list of safe minor/patch updates available)
Run Security Check Script
bash scripts/security-check.sh # Expected: # - 0 vulnerabilities # - Minimal outdated packages # - Fix commands if needed
Online Security Testing Tools
Security Headers Scanner
Tool: https://securityheaders.com/
How to use:
- •Deploy your app
- •Enter URL in Security Headers scanner
- •Check for A+ rating
What it checks:
- •Content-Security-Policy
- •X-Frame-Options
- •X-Content-Type-Options
- •Strict-Transport-Security
- •Referrer-Policy
- •Permissions-Policy
Mozilla Observatory
Tool: https://observatory.mozilla.org/
How to use:
- •Enter your deployed URL
- •Run scan
- •Check score (aim for A+)
What it checks:
- •Security headers
- •Cookie security
- •HTTPS configuration
- •Subresource integrity
- •Content Security Policy
SSL Labs
Tool: https://www.ssllabs.com/ssltest/
How to use:
- •Enter your domain
- •Wait for scan (takes ~2 minutes)
- •Check for A+ rating
What it checks:
- •SSL/TLS configuration
- •Certificate validity
- •Protocol support
- •Cipher suite strength
- •HSTS configuration
Pre-Deployment Security Checklist
Run through this checklist before every production deployment:
Environment & Configuration
- • All environment variables set in production
- •
CSRF_SECRETgenerated and configured (32+ bytes) - •
SESSION_SECRETgenerated and configured (32+ bytes) - • Clerk production keys configured
- • Stripe live mode keys configured (if using payments)
- •
.env.localNOT committed to git
Dependencies
- • Run
npm audit --production- 0 vulnerabilities - • Run
npm outdated- Check for critical updates - •
package-lock.jsoncommitted - • Next.js on latest stable version
Security Features
- • CSRF protection tested (see tests above)
- • Rate limiting tested (see tests above)
- • Input validation tested (see tests above)
- • Security headers verified (securityheaders.com)
- • HSTS enabled in production
- • Error messages are generic in production
Authentication & Authorization
- • Protected routes require authentication
- • Resource ownership checked before access
- • Subscription status verified for premium features
- • Webhook signatures verified (Clerk, Stripe)
- • Session expiration handled gracefully
API Security
- • All POST/PUT/DELETE routes have CSRF protection
- • All public endpoints have rate limiting
- • All user input validated with Zod
- • All errors handled with error handler utilities
- • No sensitive data in logs
- • No hardcoded secrets in code
Payment Security (if applicable)
- • Using Clerk Billing + Stripe (not handling cards directly)
- • Webhooks verified (Svix signatures)
- • Subscription status checked on server
- • Test mode disabled in production
Testing
- • Run rate limit test:
node scripts/test-rate-limit.js - • Test CSRF protection manually
- • Test input validation with malicious input
- • Check security headers:
curl -I https://yourapp.com - • Test authentication flows
- • Test error handling in production mode
Monitoring
- • Error logging configured (Vercel logs)
- • Failed auth attempts tracked (Clerk dashboard)
- • GitHub Dependabot alerts enabled
- • Security headers monitored (automated checks)
Automated Testing Script
security-test.sh
Create a comprehensive test script:
#!/bin/bash
echo "================================="
echo "Security Testing Suite"
echo "================================="
echo ""
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Test counter
PASSED=0
FAILED=0
# Function to run test
run_test() {
local test_name=$1
local command=$2
local expected=$3
echo -n "Testing $test_name... "
result=$(eval $command 2>&1)
if echo "$result" | grep -q "$expected"; then
echo -e "${GREEN}✓ PASS${NC}"
((PASSED++))
else
echo -e "${RED}✗ FAIL${NC}"
echo " Expected: $expected"
echo " Got: $result"
((FAILED++))
fi
}
echo "=== Dependency Security ==="
run_test "npm audit" "npm audit --production" "found 0 vulnerabilities"
echo ""
echo "=== Rate Limiting ==="
echo "Running rate limit test script..."
node scripts/test-rate-limit.js
echo ""
echo "=== Security Headers ==="
run_test "X-Frame-Options" "curl -I http://localhost:3000" "X-Frame-Options: DENY"
run_test "X-Content-Type-Options" "curl -I http://localhost:3000" "X-Content-Type-Options: nosniff"
run_test "Content-Security-Policy" "curl -I http://localhost:3000" "Content-Security-Policy"
echo ""
echo "================================="
echo "Tests Passed: $PASSED"
echo "Tests Failed: $FAILED"
echo "================================="
if [ $FAILED -eq 0 ]; then
echo -e "${GREEN}All tests passed!${NC}"
exit 0
else
echo -e "${RED}Some tests failed!${NC}"
exit 1
fi
Run it:
bash scripts/security-test.sh
Continuous Security Testing
Add to CI/CD Pipeline
# .github/workflows/security.yml
name: Security Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run npm audit
run: npm audit --production
- name: Check for outdated packages
run: npm outdated || true
- name: Build application
run: npm run build
- name: Start server (background)
run: npm run dev &
env:
NODE_ENV: test
- name: Wait for server
run: npx wait-on http://localhost:3000
- name: Run security tests
run: bash scripts/security-test.sh
- name: Stop server
run: pkill -f "npm run dev"
Manual Penetration Testing
Test XSS in All Input Fields
- •
Try these payloads in every input:
code<script>alert('XSS')</script> <img src=x onerror=alert('XSS')> <svg onload=alert('XSS')> javascript:alert('XSS') "><script>alert('XSS')</script> - •
Verify all are sanitized
Test SQL Injection
- •
Try these in search/query fields:
code' OR '1'='1 '; DROP TABLE users; -- ' UNION SELECT * FROM users --
- •
Verify input validation blocks or sanitizes
Test CSRF
- •
Create malicious HTML file:
html<form action="http://localhost:3000/api/delete-account" method="POST"> <input type="hidden" name="confirm" value="yes" /> </form> <script>document.forms[0].submit();</script>
- •
Open while logged in
- •
Verify request blocked (403 Forbidden)
Test Authorization
- •Create resource as User A
- •Try to access/modify as User B
- •Verify 403 Forbidden
What To Monitor Post-Deployment
Daily
- •Error rates (Vercel dashboard)
- •Failed authentication attempts (Clerk dashboard)
- •Rate limit violations (check logs for 429 responses)
Weekly
- •Run
npm audit --production - •Check GitHub Dependabot alerts
- •Review error logs for patterns
Monthly
- •Full security audit
- •Update dependencies
- •Re-run security testing suite
- •Check security headers (securityheaders.com)
References
- •OWASP Testing Guide: https://owasp.org/www-project-web-security-testing-guide/
- •Security Headers Scanner: https://securityheaders.com/
- •Mozilla Observatory: https://observatory.mozilla.org/
- •SSL Labs: https://www.ssllabs.com/ssltest/
- •npm Audit: https://docs.npmjs.com/cli/v8/commands/npm-audit
Next Steps
- •For fixing issues: Use appropriate security skill (csrf-protection, rate-limiting, etc.)
- •For deployment: Complete pre-deployment checklist above
- •For monitoring: Set up automated security scans in CI/CD
- •For ongoing maintenance: Run monthly security audit