Feb 26, 2026·6 min read·30 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')
An unauthenticated remote denial of service vulnerability exists in the Unleash feature management platform. By submitting a crafted JSON payload containing deeply nested structures to an OpenAPI-validated endpoint, an attacker can trigger uncontrolled recursion within the error formatting module. This leads to a call-stack exhaustion (RangeError: Maximum call stack size exceeded) inside the Node.js runtime, causing the service to crash immediately without recovery.
CVE-2026-63004 is a server-side request forgery (SSRF) vulnerability in the Unleash feature management platform. Authenticated administrators with CREATE_ADDON or UPDATE_ADDON privileges can exploit this vulnerability to initiate requests to loopback addresses, private networks, and cloud metadata endpoints, potentially leading to information disclosure and credential extraction.
Prior to version 8.0.3, Unleash's Markdown event formatter directly mutated the global template-escaping function of the shared mustache Node.js module, resulting in a process-wide security degradation where HTML/Markdown escaping was permanently disabled for the application lifetime.
A critical SQL injection vulnerability exists in the GeoTools open-source Java library. This vulnerability is situated within the post-processing phase of OGC Filter conversion inside the PostGIS DataStore module. Specifically, the `jsonArrayContains` function does not validate or sanitize its arguments before constructing PostgreSQL SQL/JSON path evaluation queries. An unauthenticated remote attacker can exploit this weakness by submitting crafted filters via standard OGC services like WFS or WMS to execute arbitrary SQL commands on the underlying database system.
CVE-2026-61824 is a high-severity Cross-Site Scripting (XSS) vulnerability in kepano/defuddle before version 0.19.1. Custom site extractors for platforms such as X/Twitter, Substack, and YouTube constructed HTML representations via template string interpolation without output escaping. This allowed malicious pages to bypass standard parser sanitization routines and execute arbitrary JavaScript.
A high-severity vulnerability exists in KeystoneJS, a popular Node.js CMS and GraphQL framework, where the query resolution engine fails to validate signed negative integers within the pagination subsystem. Unauthenticated remote attackers can leverage this flaw to bypass the 'graphql.maxTake' safety boundary. By sending large negative values in the 'take' query parameter, the underlying Prisma ORM interprets the value as an instruction to fetch rows from the end of the collection, allowing malicious actors to bypass pagination limits, trigger database resource exhaustion, and execute application-level Denial of Service (DoS) attacks.