This document outlines the security measures implemented in Resumier to protect against common web vulnerabilities and ensure production readiness.
Protects against XSS attacks by controlling which resources can be loaded.
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com data:;
img-src 'self' data: blob: https:;
connect-src 'self';
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
Notes:
'unsafe-inline'and'unsafe-eval'are required for React and some dependencies- In production, consider using nonces for inline scripts
- Google Fonts is whitelisted for font loading
Prevents clickjacking attacks.
X-Frame-Options: DENY
Prevents MIME type sniffing.
X-Content-Type-Options: nosniff
Controls referrer information sent with requests.
Referrer-Policy: strict-origin-when-cross-origin
Restricts browser features.
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Forces HTTPS connections (only enable after confirming HTTPS works).
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Headers are configured in public/_headers file. Netlify automatically applies these headers.
Deploy Command:
npm run buildBuild Directory: dist
Headers are configured in vercel.json file.
Deploy Command:
vercel --prodHeaders are configured in .htaccess file. Ensure mod_headers and mod_rewrite are enabled.
Upload: Copy contents of dist/ folder to web root.
Security headers cannot be configured directly on GitHub Pages. Consider using:
- Cloudflare Pages (proxy with header injection)
- Netlify
- Vercel
import { sanitizeText, sanitizeHtml } from "@/lib/security"
// For plain text display
const safeText = sanitizeText(userInput)
// For HTML content (removes dangerous tags)
const safeHtml = sanitizeHtml(userInput)import { sanitizeUrl } from "@/lib/security"
// Validates protocol and format
const safeUrl = sanitizeUrl(userInput)
if (safeUrl) {
// Safe to use
}import { isValidEmail, isValidPhone } from "@/lib/security"
if (isValidEmail(email)) {
// Valid email
}
if (isValidPhone(phone)) {
// Valid phone
}import { sanitizeFilename } from "@/lib/security"
// Prevents directory traversal
const safeFilename = sanitizeFilename(userInput)import { globalRateLimiter } from "@/lib/security"
// Check if action is allowed (max 5 calls per 60 seconds)
if (!globalRateLimiter.isAllowed("action-key", 5, 60000)) {
console.error("Rate limit exceeded")
return
}
// Perform action
await performAction()import { RateLimiter } from "@/lib/security"
const userLimiter = new RateLimiter()
function handleUserAction(userId: string) {
// Max 10 actions per minute per user
if (!userLimiter.isAllowed(userId, 10, 60000)) {
throw new Error("Too many requests")
}
// Process action
}import { generateSecureToken } from "@/lib/security"
// Generate cryptographically secure random token
const token = generateSecureToken(32) // 32 bytes = 64 hex charsimport { validateContentLength } from "@/lib/security"
// Prevent DoS with large payloads
if (!validateContentLength(content, 10000)) {
throw new Error("Content too large")
}- Never store tokens in localStorage (vulnerable to XSS)
- Use in-memory storage or secure httpOnly cookies
- Current implementation: Zustand store (in-memory)
// In auth store
const useAuthStore = create<AuthStore>((set) => ({
token: null, // In-memory only
login: async (credentials) => {
const { token } = await authApi.login(credentials)
set({ token }) // Stored in memory
},
logout: () => {
set({ token: null })
},
}))For production with real backend:
- Implement CSRF tokens for state-changing operations
- Use SameSite=Strict cookies
- Validate Origin/Referer headers
The app currently uses a mock API for development. When connecting to a real backend:
- Enable CORS properly:
// Backend configuration
app.use(cors({
origin: 'https://yourdomain.com',
credentials: true,
}))- Validate all inputs with Zod:
import { z } from "zod"
const resumeSchema = z.object({
title: z.string().min(1).max(100),
content: z.string().max(10000),
// ...
})
// Validate before processing
const validated = resumeSchema.parse(input)- Implement rate limiting:
import rateLimit from "express-rate-limit"
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
})
app.use("/api/", limiter)# Check for vulnerabilities
bun audit
# Update dependencies
bun update
# Check outdated packages
bun outdated- All dependencies up to date as of Phase 16
- No known vulnerabilities
- Regular updates scheduled in CI/CD pipeline
-
Test CSP:
- Open DevTools Console
- Should see no CSP violations
- Try injecting script:
document.write('<script>alert("xss")</script>')
-
Test Headers:
- Visit: https://securityheaders.com
- Enter your deployed URL
- Target: A+ rating
-
Test XSS Prevention:
- Try entering
<script>alert('xss')</script>in form fields - Should be sanitized or escaped
- Try entering
# Run security tests
bun test src/lib/security
# Run all tests
bun testIn production, integrate error tracking:
// Example with Sentry
import * as Sentry from "@sentry/react"
Sentry.init({
dsn: "YOUR_SENTRY_DSN",
environment: import.meta.env.MODE,
})Log security-relevant events:
- Failed login attempts
- Rate limit violations
- Invalid tokens
- Suspicious input patterns
- Security headers configured (CSP, X-Frame-Options, etc.)
- Input sanitization implemented
- URL validation implemented
- Rate limiting implemented
- Secure token generation implemented
- Content length validation implemented
- Run security audit:
bun audit - Test at securityheaders.com (A+ rating)
- Configure HTTPS (required for HSTS)
- Set up error monitoring (Sentry, LogRocket, etc.)
- Enable CORS for production API
- Implement CSRF protection (if using cookies)
- Set up regular dependency updates
- Review and tighten CSP (remove 'unsafe-inline' if possible)
For security concerns or to report vulnerabilities:
- Open an issue on GitHub (for non-sensitive issues)
- Email: security@yourproject.com (for sensitive disclosures)