PST-073: API validation gaps: capture corrections echo the submitted value, and enumerated fields are stored unvalidated #802

Closed
opened 2026-10-10 10:56:43 +00:00 by tiagoagueda · 1 comment
Owner

Found in the security audit of 2026-10-10 (SECURITY-AUDIT-2026-10-10.md), run against main at c30851b from 8 to 10 October 2026, as PST-073. Severity: Informational.

The text below is the audit's entry, unchanged apart from links to the other issues it names. The finding was produced by automated review and checked by a second automated pass; re-check it before acting on it.


  • Severity: Informational
  • Category: error-handling
  • Location: src/postulo/api/api.py (456-463); src/postulo/api/schemas.py (ListingIn.remote_type / employment_type / salary_period)
  • Found by: REST API

Who can do it, and what they need. The token holder, against their own request only.

How it works.

POST /captures with data: {"url": "javascript:alert(1)", ...} -> 422 whose errors[0].input is the submitted string (verified). With a 40k description correction that fails validation, the whole text is echoed back.

Impact. No cross-account data; only the caller's own input is reflected, in JSON. Inconsistent with django-ninja's builder, which deletes input from every other validation error, and a larger-than-needed response body. No attack path.

Verification (verdict: confirmed; severity after verification: Informational). Line numbers: src/postulo/api/api.py:476-482 (the except block in _capture), not 456-463. Only the failing field's own value is echoed, not 'the whole 40k description'; description is truncated, not rejected. Remediation: pass include_input=False to exc.errors() for consistency with the rest of the API.

Suggested remediation. Pass include_input=False to exc.errors(...) so the corrections path matches the rest of the API.

Also covered by this issue.

  • Enumerated listing fields and contact email are stored without validation through the API (src/postulo/api/schemas.py (ListingIn.remote_type / employment_type / salary_period); Informational). No cross-account effect; values outside the model's choices reach get_*_display(), filters and analytics that assume the enumeration, and an invalid email reaches vCard export and mailto links. …
Found in the security audit of 2026-10-10 (`SECURITY-AUDIT-2026-10-10.md`), run against `main` at `c30851b` from 8 to 10 October 2026, as **PST-073**. Severity: **Informational**. The text below is the audit's entry, unchanged apart from links to the other issues it names. The finding was produced by automated review and checked by a second automated pass; re-check it before acting on it. --- - **Severity:** Informational - **Category:** error-handling - **Location:** `src/postulo/api/api.py` (456-463); `src/postulo/api/schemas.py` (ListingIn.remote_type / employment_type / salary_period) - **Found by:** REST API **Who can do it, and what they need.** The token holder, against their own request only. **How it works.** `POST /captures` with `data: {"url": "javascript:alert(1)", ...}` -> 422 whose `errors[0].input` is the submitted string (verified). With a 40k `description` correction that fails validation, the whole text is echoed back. **Impact.** No cross-account data; only the caller's own input is reflected, in JSON. Inconsistent with django-ninja's builder, which deletes `input` from every other validation error, and a larger-than-needed response body. No attack path. **Verification** (verdict: confirmed; severity after verification: Informational). Line numbers: src/postulo/api/api.py:476-482 (the except block in _capture), not 456-463. Only the failing field's own value is echoed, not 'the whole 40k description'; description is truncated, not rejected. Remediation: pass include_input=False to exc.errors() for consistency with the rest of the API. **Suggested remediation.** Pass `include_input=False` to `exc.errors(...)` so the corrections path matches the rest of the API. **Also covered by this issue.** - **Enumerated listing fields and contact email are stored without validation through the API** (`src/postulo/api/schemas.py` (ListingIn.remote_type / employment_type / salary_period); Informational). No cross-account effect; values outside the model's choices reach `get_*_display()`, filters and analytics that assume the enumeration, and an invalid email reaches vCard export and mailto links. …
tiagoagueda added this to the 1.0.0 milestone 2026-10-10 10:58:07 +00:00
Author
Owner

Fixed in 5d588a865 on main.

Fixed in 5d588a865 on main.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#802
No description provided.