Firestore: Misconfigured TTL in Documents¶
Name: Misconfigured TTL in Firestore Documents
Applicable Services: Cloud Firestore
Description¶
Data Retention and Compliance Risk
The Configuration Issue: Firestore documents that should be temporary lack proper Time-To-Live (TTL) configuration, causing them to persist indefinitely.
Intended Behavior: Temporary documents (sessions, tokens, cache data, temporary uploads) should have TTL policies that automatically delete them after a specified period.
The Risk: Missing TTL configuration leads to data bloat, increased storage costs, privacy violations, and potential compliance issues with data retention regulations.
Impact¶
Business and Compliance Consequences
Misconfigured TTL policies create several risks:
- Compliance Violations: Violates GDPR, CCPA, and other data retention requirements
- Privacy Risk: Sensitive temporary data persists longer than necessary
- Storage Costs: Unnecessary storage charges for expired data
- Performance Impact: Large collections slow down queries and operations
- Security Risk: Stale authentication tokens and sessions remain valid
- Data Governance: Difficulty maintaining clean data lifecycle management
Example Attack Scenarios¶
Exploit Scenario: Stale Session Token Abuse
The Vulnerable Pattern:
// VULNERABLE: Session tokens without TTL
await db.collection('user_sessions').doc(sessionId).set({
userId: user.uid,
token: sessionToken,
createdAt: new Date(),
// No TTL field - session persists forever
permissions: ['read', 'write']
});
Attack Impact: - Old session tokens remain valid indefinitely - Compromised tokens can be used long after intended expiration - Increased attack surface from accumulated stale sessions
Mitigation¶
Configure TTL Policies¶
Automated Cleanup Functions¶
// Implement cleanup functions for collections without TTL
exports.cleanupExpiredSessions = functions.pubsub
.schedule('every 24 hours')
.onRun(async (context) => {
const expiredThreshold = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); // 7 days
const expiredSessions = await db.collection('user_sessions')
.where('createdAt', '<', expiredThreshold)
.get();
const batch = db.batch();
expiredSessions.docs.forEach(doc => {
batch.delete(doc.ref);
});
await batch.commit();
console.log(`Cleaned up ${expiredSessions.size} expired sessions`);
});