Last updated: January 1, 2026
Our Security Architecture
OneShotCV is built with a privacy-first, security-by-design approach. We don't just say we're secure โ here's exactly how we protect you:
๐ Client-Side Processing
Your resume data never leaves your browser.
- All form inputs, text editing, and preview rendering happen 100% client-side in JavaScript
- PDF generation uses html2pdf.js library that runs entirely in your browser
- No API calls are made with your resume content
- No servers can see, log, or store your personal information
Why this matters: Even if our servers were compromised, there's nothing to steal. Your data physically doesn't exist in our infrastructure.
Here's the actual code:
// Client-side PDF generation (public/js/export.js)
async function generatePDF() {
// Collect form data in browser
const resumeData = collectAllFormData();
// Generate HTML from data (still in browser)
const html = generateResumeHTML(resumeData);
// Convert to PDF using html2pdf.js (runs in browser)
const opt = {
margin: [0.5, 0.5, 0.5, 0.5],
filename: 'Resume.pdf',
html2canvas: { scale: 2 },
jsPDF: { unit: 'in', format: 'letter' }
};
// This entire conversion happens CLIENT-SIDE
// No server ever sees this data
return html2pdf().set(opt).from(html).save();
}
๐ณ Payment Security (PCI Compliant)
When you choose to pay $3 for export, we use Stripe โ a PCI Level 1 certified payment processor trusted by millions of businesses.
- We never see your credit card number, CVV, or billing details
- Stripe handles all sensitive payment data using bank-level encryption
- We only receive confirmation that payment succeeded (no card details)
- CORS whitelist prevents unauthorized sites from creating payments with our account
Security measure: Our payment API only accepts requests from oneshotcv.app and www.oneshotcv.app. This prevents phishing attacks where malicious sites try to use our Stripe account.
Here's the actual code:
// CORS whitelist in api/create-checkout.js
const allowedOrigins = [
'https://oneshotcv.app',
'https://www.oneshotcv.app',
'http://localhost:8000' // dev only
];
const requestOrigin = req.headers.origin;
if (allowedOrigins.includes(requestOrigin)) {
res.setHeader('Access-Control-Allow-Origin', requestOrigin);
}
// Origin validation for redirect URLs
const validOrigins = [
'https://oneshotcv.app',
'https://www.oneshotcv.app'
];
const successOrigin = validOrigins.includes(origin)
? origin
: 'https://oneshotcv.app'; // fallback to prod
Additional CORS protections:
- Origin validation prevents unauthorized browser-based usage and phishing flows
- No open redirect vulnerabilities (payment success URLs are validated against whitelist)
- Environment variable checks prevent server crashes from misconfigurations
- CDN timeouts ensure graceful failure if external libraries don't load
Note: CORS is browser-enforced and prevents client-side attacks. Server-side API abuse is mitigated by rate limiting (see below).
โฑ๏ธ Rate Limiting & DoS Protection
To prevent abuse and denial-of-service attacks, our payment API implements rate limiting:
- 5 requests per minute per IP address โ prevents spam session creation
- Requests exceeding limit receive 429 (Too Many Requests) status
- In-memory rate limiting with automatic cleanup of expired entries
- IP-based tracking using
X-Forwarded-Forheaders (from Vercel edge)
Why this matters: Even if an attacker bypasses CORS from their server, they can't flood our Stripe account with thousands of checkout sessions. Rate limiting keeps abuse minimal.
Technical note: Rate limiting is best-effort per edge instance and resets on cold starts. This is sufficient for abuse prevention but not designed as a DDoS mitigation system (for that, we rely on Vercel's edge network protections).
Here's the actual code:
// Rate limiting in api/create-checkout.js
const rateLimitMap = new Map();
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
const MAX_REQUESTS = 5; // 5 requests per minute per IP
function checkRateLimit(ip) {
const now = Date.now();
const record = rateLimitMap.get(ip) || {
count: 0,
resetTime: now + RATE_LIMIT_WINDOW
};
// Reset counter if window expired
if (now > record.resetTime) {
record.count = 0;
record.resetTime = now + RATE_LIMIT_WINDOW;
}
record.count++;
rateLimitMap.set(ip, record);
return record.count <= MAX_REQUESTS;
}
// In request handler
const clientIp = req.headers['x-forwarded-for']?.split(',')[0]
|| req.headers['x-real-ip'] || 'unknown';
if (!checkRateLimit(clientIp)) {
return res.status(429).json({
error: 'Too many requests. Please wait a minute.'
});
}
Attack vectors we've addressed:
- โ Browser-based attacks โ CORS whitelist blocks unauthorized domains
- โ Server-side API abuse โ Rate limiting prevents session spam
- โ Phishing attempts โ Users still see Stripe's domain + our business name at checkout
- โ Data theft โ Impossible (resume data never touches our servers)
- โ Payment fraud โ Stripe handles all payment validation and fraud detection
๐ HTTPS Everywhere
All connections to OneShotCV use TLS 1.3 encryption provided by Vercel's edge network.
- SSL certificate automatically renews (no expiration risk)
- HSTS header enforces HTTPS-only connections
- Modern cipher suites only (no weak encryption)
๐ซ What We DON'T Do
- โ No third-party analytics that track you across websites
- โ No advertising pixels or cookies
- โ No social media tracking scripts (Facebook Pixel, Google Analytics, etc.)
- โ No server-side logging of resume content
- โ No IP address logging or geolocation tracking
- โ No email collection or newsletters
- โ No account creation (can't hack what doesn't exist)
๐ Privacy-First Analytics
We use Vercel Analytics โ a privacy-friendly alternative to Google Analytics.
- No cookies or personal identifiers
- No IP address storage โ IPs are not logged or retained
- No fingerprinting โ we don't track unique visitors across sessions
- Only aggregated pageview counts (e.g., "500 people visited today")
- No user profiling or cross-site tracking
- Fully GDPR compliant without cookie banners
Why we track anything: We need to know if the site is working and being used. But we don't need to know who you are.
๐พ Local Storage Only
Your resume auto-saves to your browser's localStorage every 500ms while you type.
- Stored only on your device (never synced to cloud)
- Cleared when you clear browser data
- Not accessible by other websites (browser sandbox security)
- Persists across page refreshes (convenient, but local)
Important: If you switch devices or clear your browser, your resume is gone. We can't recover it because we never had it. Export JSON backups regularly.
Here's the actual code:
// Auto-save to localStorage (public/js/form.js)
function saveToLocalStorage() {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
const data = {
header: resumeData.header,
summary: resumeData.summary,
entries: { /* ... */ }
};
// Saved ONLY in browser, never transmitted
localStorage.setItem('oneshotcv-resume', JSON.stringify(data));
}, 500);
}
Transparency & Accountability
We're committed to security transparency:
- No hidden server-side processing (everything client-side)
- No obfuscated tracking code
- Simple architecture that's easy to audit
- Open communication about security practices
Found a security issue? Email us at oneshotcv@pm.me โ we take security seriously and respond fast.
๐งช Regular Security Audits
We conduct regular internal security reviews of our codebase and infrastructure:
- January 1, 2026: Added rate limiting (5 req/min) to prevent API abuse
- December 31, 2025: Added CORS whitelist and origin validation
- Ongoing code audits for common vulnerabilities (XSS, CSRF, injection attacks)
- Regular dependency updates for security patches
- Payment flow validation and testing
- Monitoring of Stripe's security advisories
๐ฑ Mobile Security
On mobile devices, PDF generation isn't supported due to browser limitations. We guide users to:
- Export JSON backup (encrypted if you enable device encryption)
- Transfer to desktop via secure channels (email, cloud storage)
- Import and export on desktop where PDF generation works
We disable PDF export on mobile to prevent failed transactions and confusion.
โ๏ธ Legal Compliance
- GDPR: Compliant (we don't collect personal data, so nothing to regulate)
- CCPA: Compliant (no data collection = no data to sell)
- PCI DSS: Compliant via Stripe (we're not in scope as we don't handle cards)
๐จ In Case of Breach
Scenario: What if our servers were compromised?
Impact: Minimal. An attacker could:
- Deface the website (annoying but fixable in minutes)
- See aggregated pageview statistics (no personal data)
- Access our Stripe secret key (we'd rotate it immediately)
What they CANNOT access:
- Your resume content (never stored on our servers)
- Your payment details (handled by Stripe, not us)
- Your personal information (we don't collect it)
Response plan: We'd notify users via the homepage, rotate all API keys, and investigate the breach. But your personal data would remain secure because it was never at risk.
๐ฏ Security Principles
- Data minimization: Don't collect what you don't need
- Client-side processing: Keep sensitive data local
- Encryption in transit: HTTPS everywhere
- Third-party trust: Use battle-tested providers (Stripe, Vercel)
- Transparency: Public documentation
- Fail safely: Errors don't leak data
Questions?
If you have security concerns or questions, email us at oneshotcv@pm.me.
We're committed to maintaining the highest security standards while keeping the service simple and privacy-respecting.
All security measures implemented and tested. Zero known vulnerabilities.