This document outlines the production-ready security features implemented in YieldPilot.
Security headers are automatically applied in development mode via Vite middleware:
Strict-Transport-Security: HSTS for HTTPS enforcementX-Content-Type-Options: Prevents MIME type sniffingX-Frame-Options: Clickjacking protectionReferrer-Policy: Controls referrer informationPermissions-Policy: Feature policy restrictions
For production deployment, configure these headers in your hosting provider:
Vercel (vercel.json):
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains; preload" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "geolocation=(), microphone=(), camera=(), payment=(), usb=(), interest-cohort=()" }
]
}
]
}Netlify (netlify.toml):
[[headers]]
for = "/*"
[headers.values]
Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options = "nosniff"
X-Frame-Options = "DENY"
Referrer-Policy = "strict-origin-when-cross-origin"
Permissions-Policy = "geolocation=(), microphone=(), camera=(), payment=(), usb=(), interest-cohort=()"A strict CSP is configured in index.html that allows:
- Self: Application's own origin
- Supabase:
*.supabase.cofor backend API and realtime - Apify:
api.apify.comfor property data scraping - PostHog:
*.posthog.comfor analytics - Sentry:
*.sentry.iofor error tracking
Edit the <meta> tag in index.html to add/remove allowed origins:
<meta http-equiv="Content-Security-Policy" content="...">- User ownership: Users can only view deals they own (
user_idmatch) - Public demo mode: Unauthenticated users see limited preview (10 deals)
- Demo account bypass: Specific test account can view all deals
- Update the demo user UUID in migration:
20251026_add_user_security.sql
- Update the demo user UUID in migration:
- Users can only create/read/update/delete their own summaries
- Enforced via
user_idcolumn with RLS policies
- Access controlled through parent
listingstable - Users can only access metrics for listings they own
- Limit: 10 requests per minute per IP/user
- Window: 60 seconds sliding window
- Response: HTTP 429 with
Retry-Afterheader - Headers:
X-RateLimit-Limit: Maximum requests allowedX-RateLimit-Remaining: Requests remaining in windowX-RateLimit-Reset: Time when limit resets
In-memory store for development. For production scale, consider:
- Upstash Redis: Serverless Redis for edge functions
- Cloudflare Workers KV: Distributed edge storage
- Supabase Vault: Encrypted KV store
- Create a Sentry project at sentry.io
- Add DSN to
.env:VITE_SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
- Automatic error capture: Unhandled exceptions and promise rejections
- Performance monitoring: 10% sample rate in production
- Session replay: Records user sessions on errors
- PII redaction: Removes sensitive data:
- Email addresses
- Phone numbers
- Passwords and tokens
- API keys and authorization headers
import { captureError, captureEvent } from '@/lib/sentry';
// Capture errors with context
try {
// code
} catch (error) {
captureError(error, { dealId: '123', userId: 'abc' });
}
// Capture custom events
captureEvent('pdf_generated', { dealId: '123', duration: 2500 });- Create a PostHog project at posthog.com
- Add credentials to
.env:VITE_POSTHOG_KEY=phc_xxx VITE_POSTHOG_HOST=https://eu.posthog.com
- Page views: Automatic
- Property paste: URL entry and source detection
- Ingestion: Start, success, failure with timing
- PDF export: Start, success, failure with duration
- Deal interactions: View, save, watchlist
- Summary generation: AI vs heuristic fallback
- No autocapture: Only explicit events tracked
- Text masking: Disabled (no PII in forms)
- User identification: Only for authenticated users
import { analytics } from '@/lib/analytics';
// Track events
analytics.ingestStart('zoopla', 50);
analytics.ingestSuccess('zoopla', 42, 12500);
analytics.pdfExportSuccess('deal-123', 3200);
// Identify user (on login)
analytics.identify(userId, {
subscription: 'pro',
signupDate: '2025-01-01'
});
// Reset (on logout)
analytics.reset();To enable the demo account bypass for testing:
- Create a demo user account
- Get the user UUID from Supabase dashboard or API
- Update the migration or run:
DROP POLICY IF EXISTS "Demo account can view all deals" ON deals_feed; CREATE POLICY "Demo account can view all deals" ON deals_feed FOR SELECT USING ( auth.uid() = 'your-demo-user-uuid-here'::uuid AND is_active = true );
- Configure security headers in hosting provider
- Review and customize CSP origins
- Set up Sentry error tracking (optional)
- Set up PostHog analytics (optional)
- Update demo account UUID in RLS policy
- Test rate limiting under load
- Review RLS policies for data access
- Enable HTTPS (automatic on Vercel/Netlify)
- Set up monitoring and alerting
- Review Sentry errors weekly
- Monitor rate limit violations
- Audit RLS policies quarterly
- Update dependencies monthly
- Review CSP violations (if reported)
- Rotate API keys annually
For security vulnerabilities, please email security@yieldpilot.com (or your contact).
Do not disclose security issues publicly until they are resolved.