SwapLink implements comprehensive security measures to protect against common web vulnerabilities and attacks. This document outlines all security configurations and best practices implemented in the application.
- CORS Configuration
- Security Headers (Helmet)
- Rate Limiting
- Request Body Size Limits
- Request ID Tracking
- Environment Variables
- Best Practices
Cross-Origin Resource Sharing (CORS) is a security feature that restricts web pages from making requests to a different domain than the one serving the web page.
// config/security.config.ts
export const corsConfig: CorsOptions = {
origin: (origin, callback) => {
// Whitelist origins from environment variable
const allowedOrigins = envConfig.CORS_URLS.split(',');
if (!origin || allowedOrigins.includes(origin) || allowedOrigins.includes('*')) {
callback(null, true);
} else {
callback(new Error('Origin not allowed by CORS policy'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'X-API-Key'],
exposedHeaders: ['X-Request-ID', 'X-RateLimit-*'],
maxAge: 600, // 10 minutes
};Add allowed origins to your .env file:
# Development
CORS_URLS=http://localhost:3000,http://localhost:5173
# Production
CORS_URLS=https://swaplink.app,https://www.swaplink.appHelmet helps secure Express apps by setting various HTTP headers to protect against common vulnerabilities.
| Header | Purpose | Our Setting |
|---|---|---|
| Content-Security-Policy | Prevents XSS attacks | Strict policy, only self-hosted resources |
| Strict-Transport-Security | Forces HTTPS | 1 year max-age, includeSubDomains |
| X-Frame-Options | Prevents clickjacking | DENY |
| X-Content-Type-Options | Prevents MIME sniffing | nosniff |
| X-XSS-Protection | XSS filter for older browsers | Enabled |
| Referrer-Policy | Controls referrer info | strict-origin-when-cross-origin |
| X-DNS-Prefetch-Control | Controls DNS prefetching | Disabled |
export const helmetConfig: HelmetOptions = {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
// ... more directives
},
},
hsts: {
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true,
},
frameguard: { action: 'deny' },
noSniff: true,
hidePoweredBy: true,
};Rate limiting protects your API from:
- Brute-force attacks
- DDoS attacks
- API abuse
- Excessive resource consumption
| Endpoint Type | Window | Max Requests | Use Case |
|---|---|---|---|
| Global | 15 min | 100 | All endpoints |
| Authentication | 15 min | 5 | Login/Register |
| OTP | 1 hour | 3 | OTP generation |
| Password Reset | 1 hour | 3 | Password reset |
| Transactions | 1 min | 10 | Transaction endpoints |
| Wallet | 1 min | 30 | Wallet queries |
// Auth routes with rate limiting
router.post('/login', rateLimiters.auth, authController.login);
router.post('/otp/phone', rateLimiters.otp, authController.sendPhoneOtp);
router.post(
'/password/reset-request',
rateLimiters.passwordReset,
authController.requestPasswordReset
);When rate limited, clients receive:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1702345678
Content-Type: application/json
{
"error": "Too many authentication attempts, please try again later."
}Prevents:
- Memory exhaustion attacks
- Large payload attacks
- Server overload
export const bodySizeLimits = {
json: '10kb', // JSON payloads
urlencoded: '10kb', // Form data
fileUpload: '5mb', // File uploads (KYC documents)
};app.use(express.json({ limit: securityConfig.bodySize.json }));
app.use(
express.urlencoded({
extended: true,
limit: securityConfig.bodySize.urlencoded,
})
);- Track requests across distributed systems
- Correlate logs for debugging
- Audit trail for security incidents
app.use((req, res, next) => {
const requestId = req.headers['x-request-id'] || randomUUID();
req.headers['x-request-id'] = requestId;
res.setHeader('X-Request-ID', requestId);
next();
});Clients can:
- Send their own request ID:
X-Request-ID: custom-id-123 - Receive the request ID in response headers
- Use it for support/debugging
# JWT Secrets (use strong, random strings)
JWT_SECRET=your-super-secret-jwt-key-min-32-chars
JWT_ACCESS_EXPIRATION=24h
JWT_REFRESH_SECRET=your-super-secret-refresh-key-min-32-chars
JWT_REFRESH_EXPIRATION=7d
# CORS
CORS_URLS=http://localhost:3000,https://yourdomain.com
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/swaplink
# External Services
GLOBUS_SECRET_KEY=your-globus-secret
GLOBUS_WEBHOOK_SECRET=your-webhook-secret
# Email
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-app-password
FROM_EMAIL=noreply@swaplink.app# Generate a secure random string (32 bytes)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Or use OpenSSL
openssl rand -hex 32// Force HTTPS redirect in production
if (process.env.NODE_ENV === 'production') {
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(`https://${req.header('host')}${req.url}`);
} else {
next();
}
});
}# Check for vulnerabilities
pnpm audit
# Update dependencies
pnpm update// Development: Relaxed CORS
CORS_URLS=*
// Production: Strict whitelist
CORS_URLS=https://swaplink.app,https://www.swaplink.app// Log rate limit violations
app.use((req, res, next) => {
if (res.statusCode === 429) {
logger.warn(`Rate limit exceeded: ${req.ip} - ${req.path}`);
}
next();
});- Review security configurations quarterly
- Update Helmet and other security packages
- Monitor security advisories
- Conduct penetration testing
Always validate and sanitize user input:
import { z } from 'zod';
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
// Use in routes
router.post('/login', validateBody(loginSchema), authController.login);// Always hash passwords with bcrypt
const hashedPassword = await bcrypt.hash(password, 12);- Use short expiration times (15-30 minutes for access tokens)
- Implement refresh token rotation
- Store refresh tokens securely
- Blacklist compromised tokens
- HTTPS enabled in production
- CORS properly configured
- Rate limiting on all sensitive endpoints
- Helmet security headers configured
- Request body size limits set
- JWT secrets are strong and secure
- Environment variables properly set
- Input validation on all endpoints
- Passwords hashed with bcrypt
- SQL injection prevention (using Prisma ORM)
- XSS prevention (input sanitization)
- CSRF protection (SameSite cookies)
- Regular dependency updates
- Security monitoring and logging
-
Immediate Actions
- Rotate all secrets (JWT, API keys, database passwords)
- Invalidate all active sessions
- Review access logs
-
Investigation
- Identify the attack vector
- Assess the scope of the breach
- Document findings
-
Remediation
- Patch the vulnerability
- Notify affected users
- Update security measures
-
Post-Incident
- Conduct a post-mortem
- Update security policies
- Implement additional safeguards
- OWASP Top 10
- Express Security Best Practices
- Helmet.js Documentation
- Express Rate Limit
- CORS Documentation
For security concerns or to report vulnerabilities, contact:
- Email: security@swaplink.app
- Create a private security advisory on GitHub
Do not publicly disclose security vulnerabilities.