This document outlines the security measures implemented in the AI Virtual Stylist API.
- Protected endpoints require API key authentication
- Supports two header formats:
x-api-keyandAuthorization: Bearer - See API_AUTHENTICATION.md for details
Protected Endpoints:
POST /catalog/embed-products- Expensive OpenAI operationPOST /routine- Generates personalized routines
- Configured to allow specific origins only
- Prevents unauthorized domains from accessing your API
- Supports credentials (cookies, authorization headers)
Configuration:
ALLOWED_ORIGINS="http://localhost:3000,http://localhost:3001,https://yourdomain.com"Default: http://localhost:3000 if not specified
- Protects against DoS attacks and API abuse
- Controls costs for expensive OpenAI operations
- Configurable per-endpoint limits
- See RATE_LIMITING.md for details
Rate Limits:
- Default: 10 requests per minute (all endpoints)
/routine: 5 requests per minute/catalog/embed-products: 1 request per hour
- Automatically adds security headers to all responses
- Protects against common web vulnerabilities
Headers Added:
X-Content-Type-Options: nosniff- Prevents MIME sniffingX-Frame-Options: DENY- Prevents clickjackingX-XSS-Protection: 1; mode=block- Enables XSS filterStrict-Transport-Security- Enforces HTTPSContent-Security-Policy- Prevents XSS attacks- And more...
- All DTOs use
class-validatordecorators - Automatic whitelist stripping of unknown properties
- Type safety enforced at runtime
Example:
export class HairProfileDto {
@IsString()
hairColor: string;
@IsArray()
@IsString({ each: true })
hairConcerns: string[];
}- Uses Prisma ORM with parameterized queries
- Raw queries use
$queryRawtagged templates (not$queryRawUnsafe)
Safe Implementation:
await this.prisma.$queryRaw`
SELECT * FROM "Product"
WHERE embedding <=> ${queryEmbedding}::vector
LIMIT ${limit}::BIGINT
`;- All secrets stored in
.envfile (gitignored) - Uses
@nestjs/configConfigService - No hardcoded credentials
Before deploying to production, ensure:
-
API_KEYis set to a strong, random value (32+ characters) -
ALLOWED_ORIGINScontains only your production domains -
.envfile is NOT committed to git - HTTPS is enabled (required for production)
- Database connection uses SSL (
?sslmode=require) - OpenAI API key is kept secure
- Shopify admin token is kept secure
# Generate a 32-byte random key
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"- API keys: Every 90 days
- Database passwords: Every 180 days
- Third-party tokens: Per provider recommendation
Development (.env):
API_KEY="dev-key-not-for-production"
ALLOWED_ORIGINS="http://localhost:3000"Production (.env.production):
API_KEY="<strong-random-key>"
ALLOWED_ORIGINS="https://yourdomain.com"
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"- Enable logging for failed authentication attempts
- Monitor for unusual API usage patterns
- Set up alerts for rate limit violations
- Rate Limiting - Prevent abuse and DoS attacks
npm install @nestjs/throttler
- Request Logging - Audit trail for all requests
- IP Whitelisting - Restrict access by IP address
- JWT Authentication - For user-specific operations
- Role-Based Access Control (RBAC) - Different permission levels
- Encryption at Rest - Encrypt sensitive data in database
- OAuth 2.0 Integration - For third-party authentication
- Two-Factor Authentication (2FA) - Extra security layer
- API Versioning - Prevent breaking changes
Without API Key (Should Fail):
curl -X POST http://localhost:3000/routine \
-H "Content-Type: application/json" \
-d '{"hairType": "curly"}'
# Expected: 401 UnauthorizedWith Valid API Key (Should Succeed):
curl -X POST http://localhost:3000/routine \
-H "Content-Type: application/json" \
-H "x-api-key: your-api-key-here" \
-d '{"hairType": "curly"}'
# Expected: 200 OKFrom Allowed Origin (Should Work):
curl -X GET http://localhost:3000/catalog/products/search?q=shampoo \
-H "Origin: http://localhost:3000"
# Expected: 200 OK with CORS headersFrom Disallowed Origin (Should Fail):
curl -X GET http://localhost:3000/catalog/products/search?q=shampoo \
-H "Origin: http://evil-site.com"
# Expected: CORS error (browser blocks response)curl -I http://localhost:3000/catalog/products/search?q=shampoo
# Look for security headers:
# X-Content-Type-Options: nosniff
# X-Frame-Options: DENY
# X-XSS-Protection: 1; mode=block- OWASP Top 10 - Common security risks
- NestJS Security - Official docs
- Helmet.js - Security middleware
- CORS Documentation - MDN guide
If you discover a security vulnerability:
- DO NOT create a public GitHub issue
- Email security concerns to your security team
- Provide detailed information about the vulnerability
- Wait for confirmation before disclosing publicly
Last reviewed: October 2025
Recent Changes:
- ✅ Added API key authentication (Oct 2025)
- ✅ Implemented CORS protection (Oct 2025)
- ✅ Added Helmet security headers (Oct 2025)
- ✅ Fixed SQL injection vulnerability (Oct 2025)