Skip to content

fix: API endpoint hardening and JSON serialization fixes#1004

Closed
ankushchk wants to merge 4 commits into
alphaonelabs:mainfrom
ankushchk:fix/brittle-api-endpoints
Closed

fix: API endpoint hardening and JSON serialization fixes#1004
ankushchk wants to merge 4 commits into
alphaonelabs:mainfrom
ankushchk:fix/brittle-api-endpoints

Conversation

@ankushchk
Copy link
Copy Markdown

@ankushchk ankushchk commented Mar 4, 2026

Related issues

Addressed self-identified priority items from the security and stability audit.

Description

This PR hardens the JSON API endpoints in web/views.py to prevent application crashes caused by malformed requests or uninitialized data types.

Changes Made:

  • Robust JSON Parsing: Implemented try-except handling for json.loads to return a 400 Bad Request instead of a server crash (500) if a request contains invalid JSON.
  • Improved Field Validation: Added explicit checks for required keys in POST bodies (title, description, price, etc.) to prevent KeyError crashes.
  • Serialization Fixes: Resolved multiple type errors where database model instances (Subjects) were being passed directly to JsonResponse. String serialization (.name) is now used correctly.
  • Auth Standardization: Ensured all user-specific API endpoints are protected with @login_required decorators to prevent unauthorized access.

Technical Code Changes

1. Serialization Fix

  • Problem: Passing a Database Model directly to JsonResponse caused a TypeError.
  • Solution: Changed course.subject to course.subject.name to ensure JSON compatibility.

2. Input Hardening

  • Problem: Missing URL parameters or bad JSON body caused KeyError and JSONDecodeError (500 Errors).
  • Solution:
    try:
        data = json.loads(request.body)
    except json.JSONDecodeError:
        return JsonResponse({"error": "Invalid JSON"}, status=400)

3. Validation Loop

  • Solution: Added a validation check before processing data:
    required_fields = ["title", "description", "price", "subject"]
    for field in required_fields:
        if field not in data:
            return JsonResponse({"error": f"Missing field: {field}"}, status=400)

Checklist

  • Did you run the pre-commit?
  • Did you test the change? (Verified handling of missing fields, malformed JSON, and successful data serialization)
  • [/] Added screenshots to the PR description (Backend/API changes only)
  • Purpose

    • Harden JSON API endpoints to prevent 500s from malformed requests, standardize auth on user-scoped endpoints, and fix JSON serialization issues.
  • Key changes

    • Robust JSON parsing: wrap json.loads(request.body) in try/except and return 400 {"error": "Invalid JSON"} on JSONDecodeError.
    • Required-field validation: explicit presence/non-empty checks for required fields (courses: title, description, learning_objectives, price, max_students, subject; forum topic: title, content, category; forum reply: topic, content), returning 400 with clear missing-field messages.
    • Subject resolution and serialization: resolve subject via Subject.objects.get(id=...) with 400 {"error": "Invalid subject ID"} on failure; API responses now return course.subject.name (string) instead of passing model instances to JsonResponse.
    • Level handling for courses: trim/lowercase level, default to "beginner" when absent/empty, and validate against Course.level choices (returning 400 on invalid values).
    • Authentication: apply @login_required to user-specific API endpoints to block unauthorized access.
    • Minor: persist resolved Subject and normalized level into created Course objects.
  • Tests / checklist

    • Pre-commit run and basic tests included for malformed JSON, missing-field handling, and successful serialization (screenshots partially addressed).
  • Impact

    • Reduces server errors from bad input, provides clearer 4xx responses for clients, enforces safer API behavior, and ensures responses avoid non-serializable model objects. Callers must supply validated fields and valid subject IDs; unauthenticated requests to user-specific endpoints will now be rejected with authentication requirements.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 4, 2026

👀 Peer Review Required

Hi @ankushchk! This pull request does not yet have a peer review.

Before this PR can be merged, please request a review from one of your peers:

  • Go to the PR page and click "Reviewers" on the right sidebar.
  • Select a team member or contributor to review your changes.
  • Once they approve, this reminder will be automatically removed.

Thank you for contributing! 🎉

@github-actions github-actions Bot added the files-changed: 1 PR changes 1 file label Mar 4, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 4, 2026

Walkthrough

Course API responses now return subject.name instead of nested subject objects. Course creation and forum endpoints add JSON parsing, required-field validation, Subject ID resolution, and normalization/defaulting of level (defaults to "beginner").

Changes

Cohort / File(s) Summary
Web Views (Course & Forum APIs)
web/views.py
Updated course list/detail responses to use subject.name. Strengthened api_course_create with JSON parse error handling, required-field checks (title, description, learning_objectives, price, max_students, subject), Subject ID resolution with error on invalid ID, level normalization/default to "beginner", and validation against Course.level choices. Hardened forum endpoints (api_forum_topic_create, api_forum_reply_create) to parse JSON safely and enforce required fields before creation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: API endpoint hardening (validation, error handling) and JSON serialization fixes (returning field values instead of objects).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/views.py (1)

2328-2338: ⚠️ Potential issue | 🟠 Major

Validate and normalize price, max_students, and level before persisting.

Currently these values are accepted as-is. Invalid numeric types can still throw server-side errors, and invalid level values can be stored if not explicitly checked.

Suggested input normalization before Course.objects.create
+    try:
+        price = Decimal(str(data["price"]))
+    except Exception:
+        return JsonResponse({"error": "Invalid price"}, status=400)
+
+    try:
+        max_students = int(data["max_students"])
+        if max_students < 1:
+            raise ValueError
+    except (TypeError, ValueError):
+        return JsonResponse({"error": "Invalid max_students"}, status=400)
+
+    allowed_levels = {choice[0] for choice in Course._meta.get_field("level").choices}
+    level = data.get("level", "beginner")
+    if level not in allowed_levels:
+        return JsonResponse({"error": "Invalid level"}, status=400)
+
     course = Course.objects.create(
         teacher=request.user,
         title=data["title"],
         description=data["description"],
         learning_objectives=data["learning_objectives"],
         prerequisites=data.get("prerequisites", ""),
-        price=data["price"],
-        max_students=data["max_students"],
+        price=price,
+        max_students=max_students,
         subject=subject,
-        level=data.get("level", "beginner"),
+        level=level,
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/views.py` around lines 2328 - 2338, Before calling Course.objects.create,
validate and normalize data["price"], data["max_students"], and
data.get("level"): coerce price to a decimal/float (or Decimal) and max_students
to an int with proper try/except to return a validation error on failure,
enforce non-negative bounds, and ensure level is one of the allowed values
(e.g., "beginner","intermediate","advanced") falling back to "beginner" if
missing or invalid; replace the raw values passed into Course.objects.create
with the normalized variables and return a clear HTTP 400/validation response
when conversion or validation fails so invalid types are never persisted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/views.py`:
- Around line 2462-2466: The current required-field loop (using required_fields
and data) only checks key presence and allows empty strings or null relation IDs
to slip through; update the validation to ensure string fields ("title",
"content") are non-empty after trimming and that relation fields ("category" or
"topic") are present and not null/empty (and optionally castable to an int)
before proceeding, returning JsonResponse({"error": "..."} , status=400) on
failure; apply the same strengthened checks to the other forum create endpoint
that uses the same pattern (the block at the other location using
required_fields/data).
- Around line 2317-2321: The current required-fields loop only checks for key
presence (required_fields and data) and allows null/blank values; update the
validation to return 400 if a field is missing OR data[field] is None OR
(isinstance(data[field], str) and data[field].strip() == '') and for sequence
types (e.g., lists like learning_objectives) treat empty sequences as missing by
checking if not data[field] or len(data[field]) == 0; modify the loop that
iterates required_fields to perform these checks and return
JsonResponse({"error": f"Missing or empty required field: {field}"}, status=400)
when any of those conditions are met so downstream object creation won't receive
null/blank inputs.

---

Outside diff comments:
In `@web/views.py`:
- Around line 2328-2338: Before calling Course.objects.create, validate and
normalize data["price"], data["max_students"], and data.get("level"): coerce
price to a decimal/float (or Decimal) and max_students to an int with proper
try/except to return a validation error on failure, enforce non-negative bounds,
and ensure level is one of the allowed values (e.g.,
"beginner","intermediate","advanced") falling back to "beginner" if missing or
invalid; replace the raw values passed into Course.objects.create with the
normalized variables and return a clear HTTP 400/validation response when
conversion or validation fails so invalid types are never persisted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 50c2e8ee-f3ec-4f8e-8fb6-e1f72836872e

📥 Commits

Reviewing files that changed from the base of the PR and between c94caf8 and 5981703.

📒 Files selected for processing (1)
  • web/views.py

Comment thread web/views.py Outdated
Comment thread web/views.py
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR has 1 unresolved review conversation. Please resolve them before this PR can be merged.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
web/views.py (2)

2462-2468: ⚠️ Potential issue | 🔴 Critical

Non-numeric relation IDs can still bypass hardening and crash these endpoints.

category/topic are only checked for presence/blank. Non-numeric values can still reach ORM lookups and cause server errors instead of returning a 400.

Suggested fix (validate relation IDs before ORM lookup)
     # Required fields check (must be present and not empty)
     required_fields = ["title", "content", "category"]
     for field in required_fields:
         val = data.get(field)
         if val is None or (isinstance(val, str) and not val.strip()):
             return JsonResponse({"error": f"Missing or empty required field: {field}"}, status=400)
+    try:
+        category_id = int(data["category"])
+    except (TypeError, ValueError):
+        return JsonResponse({"error": "Invalid category ID"}, status=400)
 
-    category = get_object_or_404(ForumCategory, id=data["category"])
+    category = get_object_or_404(ForumCategory, id=category_id)
     # Required fields check (must be present and not empty)
     required_fields = ["topic", "content"]
     for field in required_fields:
         val = data.get(field)
         if val is None or (isinstance(val, str) and not val.strip()):
             return JsonResponse({"error": f"Missing or empty required field: {field}"}, status=400)
+    try:
+        topic_id = int(data["topic"])
+    except (TypeError, ValueError):
+        return JsonResponse({"error": "Invalid topic ID"}, status=400)
 
-    topic = get_object_or_404(ForumTopic, id=data["topic"])
+    topic = get_object_or_404(ForumTopic, id=topic_id)
#!/bin/bash
set -euo pipefail

# Verify the current code path still uses raw payload values for relation IDs.
rg -n -C4 'required_fields = \["title", "content", "category"\]|get_object_or_404\(ForumCategory, id=data\["category"\]\)|required_fields = \["topic", "content"\]|get_object_or_404\(ForumTopic, id=data\["topic"\]\)' web/views.py

# Verify there is no local coercion/guard for these IDs in the touched blocks.
rg -n -C2 'int\(data\["category"\]\)|int\(data\["topic"\]\)|Invalid category ID|Invalid topic ID' web/views.py

Also applies to: 2496-2502

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/views.py` around lines 2462 - 2468, The current required_fields check
only ensures presence/blank but allows non-numeric relation IDs to pass; before
calling the ORM lookups (e.g. get_object_or_404(ForumCategory,
id=data["category"]) and get_object_or_404(ForumTopic, id=data["topic"]")),
validate and coerce the incoming relation IDs from data.get("category") /
data.get("topic") to integers and return JsonResponse({"error": "Invalid
category ID"} , status=400) or similar for topic on failure (non-numeric or
out-of-range), so the required_fields loop stays but you explicitly
parse/validate the relation IDs and short-circuit with 400 when int conversion
fails.

2317-2323: ⚠️ Potential issue | 🟡 Minor

Required-field check still allows empty non-string payloads.

Line [2321] rejects None and blank strings, but required fields like learning_objectives can still pass as []/{}.

Suggested tightening for required-field emptiness checks
     required_fields = ["title", "description", "learning_objectives", "price", "max_students", "subject"]
     for field in required_fields:
         val = data.get(field)
-        if val is None or (isinstance(val, str) and not val.strip()):
+        if val is None:
+            return JsonResponse({"error": f"Missing or empty required field: {field}"}, status=400)
+        if isinstance(val, str) and not val.strip():
+            return JsonResponse({"error": f"Missing or empty required field: {field}"}, status=400)
+        if isinstance(val, (list, tuple, dict, set)) and len(val) == 0:
             return JsonResponse({"error": f"Missing or empty required field: {field}"}, status=400)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/views.py` around lines 2317 - 2323, The required-field validation
currently only rejects None and blank strings; update the loop that iterates
over required_fields (the block using required_fields and val = data.get(field))
to also treat empty containers as invalid by checking for empty
lists/tuples/sets/dicts and zero-length iterables: after obtaining val, if val
is None OR (isinstance(val, str) and not val.strip()) OR (isinstance(val, (list,
tuple, set, dict)) and len(val) == 0) (or more generally: truthiness check for
iterable containers), return the same JsonResponse error for that field; keep
the same error message and status code so required fields like
learning_objectives cannot be [] or {}.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/views.py`:
- Around line 2337-2338: Normalize and validate the `level` value from `data`
before passing it to create: trim whitespace, treat null/empty/blank strings as
missing, default to "beginner", and ensure it is one of the allowed values
(e.g., "beginner", "intermediate", "advanced")—if invalid, set to the default or
raise a controlled validation error; replace the direct use of data.get("level",
"beginner") with a local `level` variable computed from `data` (trimmed,
lowercased, validated) and pass that `level` into the create call where
`subject=subject, level=...` is used.

---

Duplicate comments:
In `@web/views.py`:
- Around line 2462-2468: The current required_fields check only ensures
presence/blank but allows non-numeric relation IDs to pass; before calling the
ORM lookups (e.g. get_object_or_404(ForumCategory, id=data["category"]) and
get_object_or_404(ForumTopic, id=data["topic"]")), validate and coerce the
incoming relation IDs from data.get("category") / data.get("topic") to integers
and return JsonResponse({"error": "Invalid category ID"} , status=400) or
similar for topic on failure (non-numeric or out-of-range), so the
required_fields loop stays but you explicitly parse/validate the relation IDs
and short-circuit with 400 when int conversion fails.
- Around line 2317-2323: The required-field validation currently only rejects
None and blank strings; update the loop that iterates over required_fields (the
block using required_fields and val = data.get(field)) to also treat empty
containers as invalid by checking for empty lists/tuples/sets/dicts and
zero-length iterables: after obtaining val, if val is None OR (isinstance(val,
str) and not val.strip()) OR (isinstance(val, (list, tuple, set, dict)) and
len(val) == 0) (or more generally: truthiness check for iterable containers),
return the same JsonResponse error for that field; keep the same error message
and status code so required fields like learning_objectives cannot be [] or {}.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5a2b01e5-aba8-4da8-b173-e49abee0a6dc

📥 Commits

Reviewing files that changed from the base of the PR and between 5981703 and 833e612.

📒 Files selected for processing (1)
  • web/views.py

Comment thread web/views.py Outdated
@github-actions github-actions Bot dismissed their stale review March 4, 2026 11:24

All review conversations have been resolved.

@ankushchk
Copy link
Copy Markdown
Author

@A1L13N can you please review my changes?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
web/views.py (1)

2317-2322: ⚠️ Potential issue | 🟠 Major

Empty arrays/objects still bypass the required-field guard.

Line 2318 marks learning_objectives as required, but this loop only rejects None and blank strings. Payloads like {"learning_objectives": []} still pass the “non-empty” check.

Suggested tightening
     required_fields = ["title", "description", "learning_objectives", "price", "max_students", "subject"]
     for field in required_fields:
         val = data.get(field)
-        if val is None or (isinstance(val, str) and not val.strip()):
+        if (
+            val is None
+            or (isinstance(val, str) and not val.strip())
+            or (isinstance(val, (list, dict)) and not val)
+        ):
             return JsonResponse({"error": f"Missing or empty required field: {field}"}, status=400)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/views.py` around lines 2317 - 2322, The required-fields guard in the loop
(the for field in required_fields block in web/views.py that validates "title",
"description", "learning_objectives", "price", "max_students", "subject") only
rejects None and empty strings, so empty containers like [] or {} still pass;
update the check to also treat empty sequences/mappings as missing by rejecting
values where isinstance(val, (list, tuple, dict, set)) and len(val) == 0 or more
generally where not isinstance(val, str) and hasattr(val, "__len__") and
len(val) == 0; apply this tightened validation for "learning_objectives" (and
other non-string required fields) and return the same JsonResponse error when
the container is empty.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/views.py`:
- Around line 2312-2315: After parsing request.body with json.loads into data,
ensure data is a JSON object (dict) before using data.get(...) by rejecting
non-object payloads (arrays, null, strings, numbers); update the try/except
block around json.loads to, on success, check isinstance(data, dict) and return
JsonResponse({"error": "JSON must be an object"}, status=400) if not, so
subsequent uses of data.get(...) are safe (look for the json.loads(...) call and
the variable data and the places that call data.get(...) to apply this check).
- Around line 2468-2480: The handler currently assumes json.loads(request.body)
yields a dict and that get_object_or_404(ForumCategory, id=data["category"])
will always succeed; first validate that the parsed data is a dict (return
JsonResponse with a clear error if it's a list/null/primitive) before accessing
data.get(...) to avoid attribute errors, and when resolving relations
(ForumCategory) replace or wrap get_object_or_404 with an explicit lookup that
catches ValueError (bad id types) and ForumCategory.DoesNotExist (or return
JsonResponse on failure) so the endpoint always returns JSON validation errors
instead of HTML 404s or uncaught exceptions (update the code paths around
json.loads, the data variable usage, and the ForumCategory lookup).

---

Duplicate comments:
In `@web/views.py`:
- Around line 2317-2322: The required-fields guard in the loop (the for field in
required_fields block in web/views.py that validates "title", "description",
"learning_objectives", "price", "max_students", "subject") only rejects None and
empty strings, so empty containers like [] or {} still pass; update the check to
also treat empty sequences/mappings as missing by rejecting values where
isinstance(val, (list, tuple, dict, set)) and len(val) == 0 or more generally
where not isinstance(val, str) and hasattr(val, "__len__") and len(val) == 0;
apply this tightened validation for "learning_objectives" (and other non-string
required fields) and return the same JsonResponse error when the container is
empty.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d6e1d9ea-c26f-4063-9a4a-50b0e2c82f63

📥 Commits

Reviewing files that changed from the base of the PR and between 833e612 and 65f127e.

📒 Files selected for processing (1)
  • web/views.py

Comment thread web/views.py
Comment on lines +2312 to +2315
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid JSON"}, status=400)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject non-object JSON bodies before field validation.

json.loads() also accepts valid payloads like [], null, or "text". Those skip the JSONDecodeError branch, and data.get(...) on Line 2320 then raises AttributeError, so this endpoint still 500s on malformed-but-valid JSON.

Suggested hardening
     try:
         data = json.loads(request.body)
     except json.JSONDecodeError:
         return JsonResponse({"error": "Invalid JSON"}, status=400)
+    if not isinstance(data, dict):
+        return JsonResponse({"error": "JSON body must be an object"}, status=400)

Also applies to: 2317-2322

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/views.py` around lines 2312 - 2315, After parsing request.body with
json.loads into data, ensure data is a JSON object (dict) before using
data.get(...) by rejecting non-object payloads (arrays, null, strings, numbers);
update the try/except block around json.loads to, on success, check
isinstance(data, dict) and return JsonResponse({"error": "JSON must be an
object"}, status=400) if not, so subsequent uses of data.get(...) are safe (look
for the json.loads(...) call and the variable data and the places that call
data.get(...) to apply this check).

Comment thread web/views.py
Comment on lines +2468 to 2480
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid JSON"}, status=400)

# Required fields check (must be present and not empty)
required_fields = ["title", "content", "category"]
for field in required_fields:
val = data.get(field)
if val is None or (isinstance(val, str) and not val.strip()):
return JsonResponse({"error": f"Missing or empty required field: {field}"}, status=400)

category = get_object_or_404(ForumCategory, id=data["category"])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Return JSON validation errors for malformed forum payloads and bad relation IDs.

These two endpoints still have two malformed-input paths that bypass the new hardening: json.loads("[]")/null makes data.get(...) fail, and the lookups on Lines 2480 and 2514 can still return an HTML 404 page or bubble a conversion error instead of a JSON error response.

Suggested fix pattern for both endpoints
     try:
         data = json.loads(request.body)
     except json.JSONDecodeError:
         return JsonResponse({"error": "Invalid JSON"}, status=400)
+    if not isinstance(data, dict):
+        return JsonResponse({"error": "JSON body must be an object"}, status=400)
@@
-    category = get_object_or_404(ForumCategory, id=data["category"])
+    try:
+        category_id = int(data["category"])
+        if isinstance(data["category"], bool):
+            raise TypeError
+        category = ForumCategory.objects.get(id=category_id)
+    except (TypeError, ValueError, ForumCategory.DoesNotExist):
+        return JsonResponse({"error": "Invalid category ID"}, status=400)
     try:
         data = json.loads(request.body)
     except json.JSONDecodeError:
         return JsonResponse({"error": "Invalid JSON"}, status=400)
+    if not isinstance(data, dict):
+        return JsonResponse({"error": "JSON body must be an object"}, status=400)
@@
-    topic = get_object_or_404(ForumTopic, id=data["topic"])
+    try:
+        topic_id = int(data["topic"])
+        if isinstance(data["topic"], bool):
+            raise TypeError
+        topic = ForumTopic.objects.get(id=topic_id)
+    except (TypeError, ValueError, ForumTopic.DoesNotExist):
+        return JsonResponse({"error": "Invalid topic ID"}, status=400)

Also applies to: 2502-2514

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/views.py` around lines 2468 - 2480, The handler currently assumes
json.loads(request.body) yields a dict and that get_object_or_404(ForumCategory,
id=data["category"]) will always succeed; first validate that the parsed data is
a dict (return JsonResponse with a clear error if it's a list/null/primitive)
before accessing data.get(...) to avoid attribute errors, and when resolving
relations (ForumCategory) replace or wrap get_object_or_404 with an explicit
lookup that catches ValueError (bad id types) and ForumCategory.DoesNotExist (or
return JsonResponse on failure) so the endpoint always returns JSON validation
errors instead of HTML 404s or uncaught exceptions (update the code paths around
json.loads, the data variable usage, and the ForumCategory lookup).

@ankushchk ankushchk closed this by deleting the head repository Mar 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

files-changed: 1 PR changes 1 file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant