Feb 26, 2026·6 min read·8 visits
Vikunja < 2.0.0 contains a 'Zip Slip' vulnerability in its CLI restore command. Malicious backups can overwrite system files. Worse, the code wipes the database *before* validating the backup; if the backup is malformed, the app panics, crashes, and leaves you with an empty database.
A critical vulnerability in Vikunja's restore functionality allows for arbitrary file overwrites via Path Traversal (Zip Slip) and permanent data loss due to improper error handling. The application destructively wipes the existing database before validating the integrity of the backup archive, leading to potential Denial of Service (DoS) or Remote Code Execution (RCE).
Vikunja is the open-source darling for self-hosters who want to organize their lives without handing their data over to Big Tech. It is written in Go, sleek, and generally robust. However, every application has that one closet where they shove the messy code. In Vikunja's case, that closet was the CLI restore command.
The restore function is intended to take a backup ZIP file and reinstate your tasks, lists, and users. Ideally, a restore operation should be atomic: check the backup, prepare the transaction, and only then commit changes. Vikunja took a different approach. It was the digital equivalent of a mover who burns down your current house before checking if the furniture truck actually arrived.
This vulnerability (CVE-2026-27819) isn't just about reading files you shouldn't. It is about a catastrophic failure in logic that combines a classic directory traversal attack (Zip Slip) with a 'Kamikaze' execution flow that can leave a production instance permanently broken and empty.
The vulnerability stems from two distinct but compounding failures in vikunja/pkg/modules/dump/restore.go. The first is blind trust in user input. When processing a ZIP archive, the application iterates through files and checks if they start with database/. If they do, it strips that prefix and uses the rest of the string as the filename for the restore operation.
This is the textbook definition of Zip Slip (CWE-22). The application failed to sanitize the path for directory traversal characters (../). If I hand the application a ZIP file containing an entry named database/../../../../etc/shadow, Vikunja obliges and attempts to write to that location relative to its execution root.
The second flaw is Uncaught Exception (CWE-248) combined with Improper Sequencing. The code attempts to access a slice index (len(ms)-2) without verifying the slice actually has enough elements. If you feed it a malformed or empty backup, the Go runtime panics. Because this panic happens after the database wipe command, the application crashes, the process dies, and your data is gone forever.
Let's look at the vulnerable logic. It's a masterclass in 'check the wrong thing, then do the dangerous thing'.
// Vulnerable Code Pattern
func Restore(path string) error {
// 1. OPEN THE ZIP
z, _ := zip.OpenReader(path)
// 2. WIPE THE DATABASE (The Point of No Return)
// This runs before we even know if the zip is valid!
db.WipeEverything()
// 3. ITERATE FILES
for _, f := range z.File {
// The Zip Slip:
// Checks prefix, but doesn't sanitize directory traversal
if strings.HasPrefix(f.Name, "database/") {
fileName := strings.TrimPrefix(f.Name, "database/")
// ... writes to fileName ...
}
}
// 4. THE PANIC
// Accessing slice without bounds check
// If ms is empty, this crashes the app.
version := ms[len(ms)-2]
}The fix introduced in commit 1b3d8dc59cb5f2b759ab0ad2bc9915b993e3cb73 completely restructures this flow. It introduces a strict filename parser:
func parseDbFileName(fname string) (string, bool) {
// Explicitly forbids slashes and requires .json
if strings.Contains(fname, "/") || strings.Contains(fname, "\\") {
return "", false
}
if !strings.HasSuffix(fname, ".json") {
return "", false
}
return strings.TrimSuffix(fname, ".json"), true
}Critically, the patched version now reads the entire backup into memory (with a 500MB cap) and validates every single JSON file before calling db.WipeEverything(). This prevents the DoS scenario effectively.
Exploiting this requires creating a malicious ZIP file. Standard archiving tools usually prevent you from adding relative paths like ../, so we'd python-script this.
We want to overwrite config.yml to change the database host to a malicious server we control, or perhaps overwrite a binary if permissions allow.
config.yml (locally).database/../../config.yml.vikunja restore malicious.zip.database/ prefix, strips it, and writes the file to ../../config.yml relative to the CWD.This is for the chaotic evil attacker. We simply want to destroy the instance.
vikunja restore broken.zip.db.WipeEverything().index out of range panic.While this vulnerability requires CLI access (or an admin trigger), the impact is Critical.
Confidentiality & Integrity: The Zip Slip component allows an attacker to overwrite sensitive configuration files or inject code (e.g., via cron jobs or webhooks if they are stored as files). This is a direct path to Remote Code Execution (RCE) depending on the file system permissions of the user running Vikunja.
Availability: The destructive nature of the restore logic is arguably worse for the average user. A corrupted backup file shouldn't result in a total loss of production data. In a worst-case scenario, an automated backup/restore testing pipeline could inadvertently wipe a staging or production database simply because the backup artifact was incomplete.
The remediation strategy adopted by the Vikunja team is robust. By separating validation from execution, they moved from a 'Fail-Unsafe' to a 'Fail-Safe' architecture.
For Developers:
filepath.Base() or explicit allow-lists.len() before you leap.For Users:
Upgrade to v2.0.0 immediately. If you cannot upgrade, treat the restore command as a loaded gun—verify your ZIPs manually and take a snapshot of your VM/DB before running it.
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Vikunja Vikunja | < 2.0.0 | 2.0.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 (Path Traversal) & CWE-248 (Uncaught Exception) |
| Attack Vector | Local (CLI) / Network (if upload allowed) |
| CVSS | 9.1 (Critical) |
| Impact | Arbitrary File Overwrite / Permanent Data Loss |
| Fixed Version | v2.0.0 |
| Exploit Status | Proof of Concept (Internal) |
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')