A cloud backup solution that employs the "ChaCha20 + Serpent-256 CBC + HMAC-SHA3-512" authenticated encryption scheme for data encryption and ML-KEM-1024 for quantum-resistant key exchange.
Check it out at https://plum-cave.netlify.app/
SourceForge page: https://sourceforge.net/projects/plum-cave/
ChaCha20 + Twofish-256 CBC + HMAC-SHA3-512 Version: https://sourceforge.net/projects/plum-cave-twofish/
The account password can contain non-ASCII characters!
The app is fully localized into:
✓ English
✓ Hebrew
✓ Argentinian Spanish
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// === 1. /data/{userEmail}/backups, limit to 10 backups ===
match /data/{userEmail}/backups/{backupId} {
allow get, list: if resource.data.isPublic == true
|| (request.auth != null && request.auth.token.email == userEmail);
allow update: if resource.data.isPublic == true
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly(['downloads'])
&& request.resource.data.downloads == resource.data.downloads + 1;
allow create: if request.auth != null
&& request.auth.token.email == userEmail
&& (
!exists(/databases/$(database)/documents/data/$(userEmail)/backups)
|| get(/databases/$(database)/documents/data/$(userEmail)/backups).list().size() < 10
);
allow update, delete: if request.auth != null && request.auth.token.email == userEmail;
}
// === 1b. Allow owner full access to all subcollections and docs under each backup ===
match /data/{userEmail}/backups/{backupId}/{document=**} {
allow read, write, delete: if request.auth != null && request.auth.token.email == userEmail;
allow get, list: if exists(/databases/$(database)/documents/data/$(userEmail)/backups/$(backupId))
&& get(/databases/$(database)/documents/data/$(userEmail)/backups/$(backupId)).data.isPublic == true;
}
// === 2. /data/{userEmail}/receivedBackups/{document=**} ===
match /data/{userEmail}/receivedBackups/{document=**} {
allow write: if request.auth != null; // Any authenticated user can write
allow read, delete: if request.auth != null && request.auth.token.email == userEmail; // Only owner can read/delete
}
// === 3. /data/{userEmail}/public/{document=**} ===
match /data/{userEmail}/public/{document=**} {
allow read: if true; // Anyone (even unauthenticated) can read
allow write, delete: if request.auth != null && request.auth.token.email == userEmail; // Only owner can write/delete
}
// === 4. /data/{userEmail}/private/encrypted/projectInfo, limit to 2 folders ===
match /data/{userEmail}/private/encrypted/projectInfo/{projectId} {
allow create: if request.auth != null && request.auth.token.email == userEmail
&& (
!exists(/databases/$(database)/documents/data/$(userEmail)/private/encrypted/projectInfo)
|| get(/databases/$(database)/documents/data/$(userEmail)/private/encrypted/projectInfo).list().size() < 2
);
allow read, update, delete: if request.auth != null && request.auth.token.email == userEmail;
}
// === 5. /data/{userEmail}/private/encrypted/projectInfo/{projectId}/backups, limit to 5 per project ===
match /data/{userEmail}/private/encrypted/projectInfo/{projectId}/backups/{backupId} {
allow create: if request.auth != null && request.auth.token.email == userEmail
&& (
!exists(/databases/$(database)/documents/data/$(userEmail)/private/encrypted/projectInfo/$(projectId)/backups)
|| get(/databases/$(database)/documents/data/$(userEmail)/private/encrypted/projectInfo/$(projectId)/backups).list().size() < 5
);
allow read, update, delete: if request.auth != null && request.auth.token.email == userEmail;
}
// === 6. /data/{userEmail}/private/{document=**} ===
match /data/{userEmail}/private/{document=**} {
allow read, write, delete: if request.auth != null && request.auth.token.email == userEmail;
}
// === 7. /data/{userEmail}/private root ===
match /data/{userEmail}/private {
allow read: if request.auth != null;
allow write, delete: if request.auth != null && request.auth.token.email == userEmail;
}
// === 8. Default deny ===
match /{document=**} {
allow read, write, delete: if false;
}
}
}
Firestore rules are easy to edit, especially using AI tools like Perplexity or Mistral's Le Chat, so I'll only explain how to remove the in-app-enforced limitations.
Adjusting Project Limit
To modify maximum allowed projects per user:
- find
if (projectsSnapshot.size >= 2) {line inDashboard.tsxfile - Replace
2with your desired limit - To remove the project limit entirely:
- Delete the entire
ifblock - Remove its corresponding
elsestatement wrapper - Retain internal code
- Delete closing
}for the block
- Delete the entire
Modifying Backup Limits
To adjust maximum allowed backups per project:
-
Find
if (backupsSnapshot.size >= 5) {line inDashboard.tsxfile -
Change
5to preferred threshold -
To eliminate the backup limit: Remove the entire conditional block (
if (backupsSnapshot.size >= 5) { const errorMessage = ` <p style="margin-bottom: 10px;" dir="${isRTL ? 'rtl' : 'ltr'}">${t('backup_limit_reached_message')}</p> <p dir="${isRTL ? 'rtl' : 'ltr'}">${t('delete_backup_to_add_new')}</p>`; showErrorModal(errorMessage); return; }
To run the app with your own Firebase instance:
-
Create a web app in Firebase
-
Copy the database access credentials and paste them into app/lib/firebase.ts, replacing the mock-up credentials
-
Enable the "Authentication" and "Firestore Database" services
-
Configure Firestore Database rules
-
Compile the web app
-
Host the statically-compiled app on Netlify or any hosting of your choice
The existence of this project (at least in its current form) wouldn't've been possible without the following:
View transitions - Demo by Stefan Judis
Toolbars With Sliding Selection by Jon Kantner
Gsap Slider by Yudiz Solutions Limited
glowy hover effect by Ines
Free Security Animation by DE GUZMAN, Jalei
Free Lock-Key_Animation Animation by Abdul Latif
Free Uploading to cloud Animation by Nazar
Free Share icon waiting Animation by jjjoven
Text scroll and hover effect with GSAP and clip by Juxtopposed
Animated Tooltip by Aceternity UI
Custom Progress Bar by Florin Pop
すりガラスなプロフィールカード by あしざわ - Webクリエイター
Signup Form from Aceternity UI
Bouncing Cube Loader by Haja Randriakoto
Used Namer UI components:
-
Halomot Button
-
Fancy Hero Section
-
Pricing Card
-
Fancy Navbar
-
Dreamy Input
-
Structured Block
-
Unfolding Sidebar
-
Random Number Generator
-
File Encrypter




