Modern data backup solutions require encryption, flexible scheduling, and multi-destination replication, but existing tools either lack user-friendly interfaces or force reliance on proprietary cloud services. Managing encrypted backups across diverse storage providers with granular control remains a complex task for both beginners and advanced users.
What This Does
Pluton is a self-hosted backup solution that combines Restic's incremental encryption with Rclone's multi-cloud support, wrapped in a React frontend. The core functionality is implemented in backend/src/managers/ (12 files) and backend/src/jobs/ (9 files). The system handles backup creation (BackupTask.ts), replication (ReplicationHandler.ts), and restoration (RestoreHandler.ts) through a job queue (JobQueue.ts). Encryption is managed via SecurityKeyManager.ts, while storage destinations are abstracted through BaseStorageManager.ts.
The frontend (frontend/) provides a web interface for managing backups, schedules, and restores. It interacts with the backend API defined in backend/src/routes/ (7 files). The system supports over 70 storage providers through Rclone integration, with configuration handled by StorageController.ts and StorageService.ts.
How It Is Wired
Execution begins at backend/src/index.ts, which initializes the Express server and sets up routes. The primary data flow routes through:
JobProcessor.ts – Orchestrates task execution via SystemTaskManager.ts
BackupHandler.ts – Coordinates the backup process with SecurityKeyManager.ts and BaseStorageManager.ts
RestoreHandler.ts – Manages data restoration operations
# Clone the repository
git clone https://github.com/moses-y/pluton.git
# Install dependencies (requires pnpm)
cd pluton/backend
pnpm install
# Build the backend
pnpm run build
Configuration
Environment variables are specified in .env.template. Key configurations include:
Database settings (DATABASE_URL)
Storage provider credentials
Notification channels (SMTP, Slack, Discord)
Running it
# Start the backend
pnpm run dev
# For Docker deployment, use the provided docker-compose.yml
docker-compose up -d
Real-World Use
A typical workflow involves:
Configuring storage destinations via the web interface
Creating backup plans with retention policies
Scheduling automated backups
Managing restores through the UI or CLI
Example backup job creation:
# Trigger a backup via API
curl -X POST http://localhost:5173/api/backups -H "Content-Type: application/json" -d '{"planId": 1, "source": "/path/to/data"}'
Code Health & Issues
Security: Potential secret exposure in frontend/.env.dev (marked low severity)
Test Coverage: 124 test files found, but coverage metrics not available
Documentation: 9 doc files present, but some areas (e.g., advanced notifications) lack detail
The Bottom Line
Pluton provides a functional, self-hosted backup solution with strong encryption and multi-cloud support. The architecture is modular but complex, with significant dependencies on external tools (Restic/Rclone). Best suited for users comfortable with self-hosting and Docker. The project shows potential but would benefit from improved documentation and test coverage.
What the analyser found
Deployment readiness
6/7
✓Container image
✓CI pipeline
✓Lockfile committed
✓Test suite
✓README
✓License
✗No committed secrets
Composition
840 files
TypeScript414
TSX139
SCSS94
JSON19
Shell9
JavaScript8
ReactExpressDocker
Module dependencies
The 10 most depended-upon modules of 565, from static import analysis. Red outline marks a module in an import cycle.
Ranked by severity × confidence × production reach. Reach is the honest discriminator across a collection that is mostly other people's code: the same finding matters more in something that ships.
high4
medium6
low1
Pin third-party GitHub Actions to a commit SHAhigh11 occurrences
A tag can be moved, so the action running with your token and secrets is whatever its owner last pushed; this is how tj-actions/changed-files leaked secrets from thousands of repos.
Fix: Replace each @vN with the 40-character commit SHA, keep # vN as a comment, and let Dependabot bump the SHAs.
Upgrade the pinned dependency carrying a published advisoryhigh10 occurrences
nodemailer@8.0.1 GHSA-2x7j-588g-ccc2 (high) +9 more
The pinned version is the one that installs, so a published advisory against it describes this deployment rather than a hypothetical one, and the advisory is also a public description of how to exploit it.
Fix: Upgrade to the fixed version named in the advisory, then commit the lockfile.
Remove the committed .env and rotate what it holdshigh
frontend/.env.dev
frontend/.env.dev
A tracked .env is the most common route for a working key to reach a public clone, and it is the file the app actually loads, so the value is usually live.
Fix: git rm --cached the file, add it to .gitignore, rotate every credential it names, commit a .env.example with empty values.
Open a pull request instead of pushing to the default branchhigh
.github/workflows/release-assets.yml
git push
Automated commits land on the branch that deploys, with no test having run against the result.
Fix: Push to a bot branch and open a pull request, or restrict the push to a tag ref.
Enable Dependabot or Renovatemedium
3 manifest(s), no update bot configured
Without a bot a published advisory sits unpatched until someone audits by hand, which across 1,322 repositories means never.
Fix: Commit .github/dependabot.yml covering the repo ecosystems plus github-actions.
Pin the container base image by digestmedium
Dockerfile
debian:bookworm-slim
An untagged or mutable base means today's build and last month's contain different libc and a different CVE set, with no record of which shipped.
Fix: Use image:tag@sha256:<digest> and enable Dependabot's docker ecosystem.
Gate pull requests on a dependency vulnerability scanmedium
.github/workflows
no dependency scan in CI
This is the one gate that would catch a known-vulnerable package before it reaches a build, and no repository in the sample had it.
Fix: Add dependency-review-action on pull_request, or osv-scanner on push and a schedule.
Move large binaries to Git LFS or out of the repositorymedium
installers/windows/tools/vc_redist.x64.exe
1 blobs over 5MB: vc_redist.x64.exe 24.4MB
One repository here carries twenty blobs over 5MB including a 10.9MB spreadsheet, so every clone and every CI checkout pays for data nobody diffs.
Fix: Track those extensions with LFS, or move datasets to object storage and fetch them in a setup step.
Set persist-credentials: false on checkoutmedium
.github/workflows/ci-backend.yml
checkout keeps the token, then dependencies are installed
The token stays in .git/config for every later step, so a malicious postinstall script reads a pushable credential without one ever being passed to it.
Fix: Add with: persist-credentials: false, and pass an explicit token only to the step that pushes.
Add a non-root USER to the imagemedium
Dockerfile
CMD or ENTRYPOINT with no USER directive
A process running as root in the container is root against every mounted volume, and it turns any container escape or writable-mount mistake from a contained problem into a host one.
Fix: Create an unprivileged user, chown what it needs, and end the Dockerfile with USER.
Set timeout-minutes on the workflow jobslow3 occurrences
.github/workflows/ci-backend.yml
3 workflow(s) declare no job timeout
A wedged step runs to the six-hour platform default, which on a two-hourly schedule means three runs overlap behind it.
Fix: Add timeout-minutes with a realistic bound to each job.
Checked deterministically against the repository tree and a bounded set of its files: committed credentials, unpinned actions and base images, missing lockfiles and update bots, workflows that discard failures, published advisories against the declared dependencies, runtime configuration, licensing and notebook reproducibility. No language model is involved in this section.