SEC2-009: Wrong tokens sent to openapi.json, an unknown API address or a wrong method are neither counted nor barred (#799 incomplete) #951
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#951
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 on 2026-10-16 in a read-only security review of
postuloat56fc9d531, checked by a second, independent pass.Severity: Informational · Category: Authentication/Authorization · Reference: CWE-307 (Improper Restriction of Excessive Authentication Attempts); OWASP A07:2021 (Identification and Authentication Failures)
Location:
src/postulo/api/auth.py:166-200(for_readers_of_the_api, line 186 callslookupdirectly; the 401 is built by hand at 191-196)src/postulo/api/api.py:945-951(_has_a_live_token, used by the 405 wrapper at 912-942 and_no_such_addressat 953-960)src/postulo/api/auth.py:42-59(the counting that these paths skip)Issue
#799 added
_barred/_wrong_tokentoTokenAuthandScopedAuth, so sixty wrong tokensfrom one address in an hour bar it. Its remediation note named
_no_such_addressandfor_readers_of_the_apias well, and those still calllookup()bare:So an address that is barred on every operation is still answered on
GET /api/v1/openapi.json,on any unknown path under
/api/v1/and on a wrong method, and guesses made there are notcounted. The token is 32 random bytes hashed with SHA-256, so guessing is not a credential
risk; this is the project's own hardening applied unevenly, as #799 itself was rated. The
schema view also spends no allowance for a live token, and
get_openapi_schemarebuilds thewhole description per request.
Impact
None beyond cheap 401s and schema builds, which the threat model places with the reverse
proxy. Recorded so the pattern is whole.
Fix
One helper in
auth.py, saylive_token(request) -> ApiToken | None, that runs_barred,lookupand_wrong_tokenas the auth classes do, used byfor_readers_of_the_apiand_has_a_live_token.for_readers_of_the_apiis a plain Django view with no API handler overit, so it must catch the
problems.Refusedthe helper raises and build the 429 by hand, as italready builds the 401 (
auth.py:191-196). Done when a test besidetests/test_hardening_799.py,parametrised over
openapi.json, an unknown/api/v1/path and aDELETEon/api/v1/me,shows a barred address answered 429 on each and the guesses counted.
Related
#799 (closed) - names these two paths in its suggested remediation; the landed fix covers the
two
HttpBearerclasses only. #760 and #430 (closed) - built the 405 and unknown-address wrappers.