Your Firebase service account private key has been exposed in .env.local. You must:
- Go to Firebase Console β Project Settings β Service Accounts
- Generate a new private key (this will invalidate the old one)
- Update your
.env.localwith the new credentials using the secure method below - Never commit
.env.localto version control
The security rules have been created but need to be deployed to Firebase:
# Install Firebase CLI if you haven't already
npm install -g firebase-tools
# Login to Firebase
firebase login
# Initialize Firebase in your project (if not done)
firebase init
# Deploy security rules
firebase deploy --only firestore:rules,storageUpdate your .env.local with individual fields (more secure):
# Method 1 (Recommended): Individual fields
FIREBASE_PROJECT_ID=jobs-agency-8f28b
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxxxx@jobs-agency-8f28b.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nYOUR_NEW_PRIVATE_KEY_HERE\n-----END PRIVATE KEY-----"Important Notes:
- The private key must be enclosed in double quotes
- Keep the
\ncharacters in the key - Remove the old
FIREBASE_SERVICE_ACCOUNT_KEYvariable
Set these in your hosting platform's environment variable settings:
FIREBASE_PROJECT_IDFIREBASE_CLIENT_EMAILFIREBASE_PRIVATE_KEY
- Download your service account JSON file
- Encode it to base64:
# On Linux/Mac cat service-account.json | base64 -w 0 # On Windows (PowerShell) [Convert]::ToBase64String([IO.File]::ReadAllBytes("service-account.json"))
- Set
FIREBASE_SERVICE_ACCOUNT_KEY_BASE64in your hosting platform
β User authentication required for most operations β Role-based access control (Job Hunter vs Agency) β Data validation (string lengths, email formats, etc.) β Owner-only operations for profile updates β Verified agencies only can post jobs β Prevents privilege escalation (self-verification)
Location: firestore.rules
β File type validation (PDFs, images only) β File size limits (5MB for images, 10MB for resumes) β User-owned file uploads and deletions β Public read for company logos and job images β Private resumes (authenticated users only)
Location: storage.rules
β Email validation β Phone number validation β String sanitization (XSS prevention) β HTML sanitization β File upload validation β Password strength checking β Rate limiting (basic implementation)
Location: lib/validation.ts
- Rotate Firebase service account key
- Deploy Firestore security rules
- Deploy Storage security rules
- Set up environment variables securely on hosting platform
- Remove
.env.localfrom version control (already in.gitignore) - Enable Firebase Authentication email verification
- Set up HTTPS only (most hosting platforms do this automatically)
- Configure CORS for Firebase Storage
- Set up error monitoring (Sentry, LogRocket, etc.)
- Enable Firebase App Check (prevents API abuse)
- Review and test all security rules
- Implement rate limiting on API routes
- Set up automated security scanning (Dependabot, Snyk)
- Enable 2FA for Firebase Console access
- Set up Firebase Performance Monitoring
- Configure Firebase Analytics with privacy settings
- Implement Content Security Policy (CSP) headers
- Add security headers (X-Frame-Options, etc.)
- Set up automated backups for Firestore
- Implement audit logging for critical operations
- Add CAPTCHA for signup/login forms
- Enable Firebase Authentication rate limiting
import {
validateJobTitle,
validateJobDescription,
validateSalaryRange,
validateSkills
} from '@/lib/validation';
// In your job posting form
const titleValidation = validateJobTitle(jobTitle);
if (!titleValidation.valid) {
console.error(titleValidation.error);
return;
}
const descValidation = validateJobDescription(description);
if (!descValidation.valid) {
console.error(descValidation.error);
return;
}
const salaryValidation = validateSalaryRange(salaryMin, salaryMax);
if (!salaryValidation.valid) {
console.error(salaryValidation.error);
return;
}
const skillsValidation = validateSkills(skills);
if (!skillsValidation.valid) {
console.error(skillsValidation.error);
return;
}
// Use sanitized values
const sanitizedData = {
title: titleValidation.sanitized,
description: descValidation.sanitized,
skills: skillsValidation.sanitized,
// ... other fields
};import { validateFile } from '@/lib/validation';
const fileValidation = validateFile(resumeFile, {
maxSize: 10 * 1024 * 1024, // 10MB
allowedTypes: ['application/pdf'],
allowedExtensions: ['.pdf']
});
if (!fileValidation.valid) {
alert(fileValidation.error);
return;
}import { checkRateLimit } from '@/lib/validation';
// In your API route or form submission
const rateLimit = checkRateLimit(
`login:${email}`,
5, // max 5 attempts
300000 // per 5 minutes
);
if (!rateLimit.allowed) {
const waitTime = Math.ceil((rateLimit.resetAt - Date.now()) / 1000);
alert(`Too many attempts. Please try again in ${waitTime} seconds.`);
return;
}β Bad:
// Trusting user input directly
await createJob({ title: userInput });β Good:
const validation = validateJobTitle(userInput);
if (!validation.valid) throw new Error(validation.error);
await createJob({ title: validation.sanitized });β Bad:
catch (error) {
alert(`Database error: ${error.message}`);
}β Good:
catch (error) {
console.error('Database error:', error);
alert('An error occurred. Please try again later.');
}β Bad:
// No auth check before upload
const uploadResume = async (file) => {
return await storage.ref(`resumes/${file.name}`).put(file);
};β Good:
const uploadResume = async (file) => {
if (!currentUser) throw new Error('Authentication required');
const validation = validateFile(file);
if (!validation.valid) throw new Error(validation.error);
return await storage.ref(`resumes/${currentUser.uid}/${file.name}`).put(file);
};If you discover a security vulnerability:
- Do not disclose it publicly
- Immediately revoke compromised credentials
- Review audit logs for suspicious activity
- Update security rules if needed
- Notify affected users if data was compromised
- Document the incident and response
Last Updated: 2025-10-23 Next Review: Schedule quarterly security audits