PST-038: Oversized integers and non-ASCII digits in ids raise unhandled exceptions (HTTP 500) on many endpoints #767
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#767
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found in the security audit of 2026-10-10 (
SECURITY-AUDIT-2026-10-10.md), run againstmainatc30851bfrom 8 to 10 October 2026, as PST-038. Severity: Low.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.
src/postulo/core/bulk.py(70-95);src/postulo/jobs/capture_views.py(jobs/capture_views.py:597-601);src/postulo/applications/forms.py(289-293);src/postulo/applications/views.py(applications/views.py:431)Who can do it, and what they need. Any signed-in account. Precondition: a session. Verified with /tmp/.../verify/own-jobs/test_big_ints.py.
How it works.
chosen, or /jobs/captures/discard/ with selected=<30 digits>).int(raw)), and CaptureDiscardSelectedView accepts any digit string (value.isdigit())..filter(pk__in=...); Django 6.1'sInlookup has no IntegerFieldOverflow guard (only Exact/Gt/Lt do), so the SQLite adapter raisesOverflowError: Python int too large to convert to SQLite INTEGER(PostgreSQL would raise a DataError).pk=paths (mergewith, bindlisting, bulkindustry, ?company=) answer 404/302/200 because the exact lookup is guarded.Impact. Repeatable server errors and log noise from any account; no data is read or changed and the response reveals nothing. The project's own words in bulk.py say 'a form that 500s on a stray value is a form somebody can break for fun'.
Evidence.
Verification (verdict: confirmed; severity after verification: Low). Two corrections. (1) The claim that PostgreSQL would raise a DataError is unverified here (no PostgreSQL available); with psycopg 3 (the project's declared driver, pyproject.toml:81) an oversized Python int is adapted as numeric and PostgreSQL compares bigint IN (numeric) without error, so the crash is most likely SQLite-specific. SQLite is nevertheless the shipped default, so the finding stands. (2) Add: a legitimate id in the same
chosenlist is lost too, because the whole request fails (MIXED case), though ATOMIC_REQUESTS means nothing is half-applied. Remediation: inchosen_ids()drop any value outside the pk field's range (e.g.connection.ops.integer_field_range(...)or simply 0 < n < 2**63), apply the same bound inCaptureDiscardSelectedView.post(or route it throughbulk.chosen_ids), and add a case to tests/security/test_bulk_actions.py besidetest_only_ids_are_only_ids.Suggested remediation. In chosen_ids() and CaptureDiscardSelectedView drop any id outside the database's signed 64-bit range (or simply any digit string longer than 18-19 characters) before it reaches the queryset, the way as_pk() already swallows non-numbers; add a test that posts a 30-digit id to each bulk endpoint and expects a redirect, not an exception.
Also covered by this issue.
src/postulo/jobs/capture_views.py(jobs/capture_views.py:597-601); Low). An unhandled exception (HTTP 500, logged with the request line) for the member's own request; for the spreadsheet import a persistent self-inflicted 500 of the import page until the session is reset. No cross-account effect. …src/postulo/applications/forms.py(289-293); Informational). Unhandled exception and logged traceback for a malformed request; on PostgreSQL a DataError (integer out of range). No data exposure and no effect on anyone else. …src/postulo/applications/views.py(applications/views.py:431); Informational). Unhandled 500 for the member's own request; same class as the confirmed OverflowError finding for CompanyBulkView, ListingBulkView and CaptureDiscardSelectedView, whose list is therefore incomplete. No data crosses accounts.Fixed in
8aff05966on main.