Backup routes are not admin-gated - cloud credentials readable by any authenticated user #80
Labels
No labels
bug
duplicate
enhancement
future
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/TeaLeaves#80
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: High · Confidence: High · Effort: XS · Category: security
Evidence
api/src/app.ts:123-135-/api/settingsand/api/adminare wrapped inrequireAdmin;/api/backupat line 124 is behind onlyrequireAuth.api/src/routes/backup.ts- no internal admin check anywhere in the router.api/src/routes/backup.ts:80-88-GET /destinationsreturnsextra_env.api/src/services/backup.ts:47-59-extra_envholds cloud secrets (AWS_*/B2_*/AZURE_*/.); onlyrestic_passwordis excluded.api/src/routes/invites.ts:77-82,api/src/routes/projects.ts:407-435- local collaborators accept invites and become full authenticated non-admin users.Problem
Every other server-infra surface (settings, admin) is admin-gated. The backup router - which manages restic repos/credentials, spawns
pg_dump/restic, and can stage/apply DB restores - sits behind onlyrequireAuth, and its destinations list returns long-lived cloud-storage secret keys.Impact
Any logged-in non-admin (e.g. a project collaborator) can read cloud-storage secret keys, create/delete/trigger backup destinations, run restic against repos, and stage/apply restores. No exploit path in a strict single-account instance, but the app genuinely provisions non-admin accounts.
Fix
Mount as
app.use('/api/backup', requireAdmin, backupRouter)and dropextra_envvalues from the list response (return key names / a "configured" boolean).Acceptance criteria
/api/backup/*route.GET /api/backup/destinationsno longer returnsextra_envsecret values./api/backup/jobsand/destinations.Filed from the 2026-07-15 codebase audit. Full report:
docs/.internal/report-2026-07-15.md(gitignored).Fixed in
9a87882(v7.1.0 wave 1)./api/backupis now mountedapp.use('/api/backup', requireAdmin, backupRouter)inapi/src/app.ts— same server-infra tier as settings/admin. Every/api/backup/*route returns 403 to a non-admin.GET /api/backup/destinationsno longer returnsextra_envvalues. It maps each row toextra_env_keys(key names only);restic_passwordwas already excluded. Editing is write-only — the frontend omitsextra_envfrom a PATCH unless the admin re-enters JSON, so existing secrets are preserved (mirrors the existing restic-password "leave blank to keep" pattern).BackupPanel.tsxupdated accordingly (shows configured key names as a hint).api/src/test/integration/backup.test.tsasserts non-admin → 403 on/jobsand/destinations, and that a created destination's secret values (extra_env) are never serialized (onlyextra_env_keysexposed).All three acceptance criteria met. CI #3640 green; deployed to dev.