SEC2-069: The HTTP transports accept unlimited wrong bearer secrets; the core throttles wrong API tokens per address #21
Labels
No labels
bug
documentation
enhancement
security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo-mcp#21
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
postulo-mcpat3819dbd, 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_mcp/serving.py:42(MINIMUM_SECRET = 16)src/postulo_mcp/serving.py:62-76(SharedSecret.verify_token)src/postulo_mcp/serving.py:140-157(plan:len(secret) < MINIMUM_SECRETis the only check)Issue
SharedSecret.verify_tokenanswers each request on its own:The comparison is constant-time, which is right, but nothing counts refusals: the SDK's
BearerAuthBackendanswers 401 and the next attempt is free.planaccepts any secret of sixteencharacters "of anything", so a person who sets a passphrase rather than the
token_urlsafe(32)the message suggests has a secret that can be guessed online at line rate, and a
--hostotherthan loopback puts that port on the network (the README says so;
__main__warns on stderr). Thecore holds its own API to a different standard:
api/auth.pybars an address after too manywrong tokens (postulo/postulo#799).
Impact
Only with
--transport sse|streamable-httpand a weak secret, and over the network only with--hostset away from loopback: somebody who can reach the port can try secrets without limituntil one opens the person's whole job search (and, with
--write, the right to change it). Withthe suggested random secret the attempt is hopeless, which is why this is recorded rather than
rated.
Fix
Refuse weak secrets in
planrather than only short ones: require at least 128 bits, for examplelen(secret) >= 22and more than ten distinct characters, and change the README's sentence tomatch. This needs nothing from the MCP SDK. Optionally, add a refusal counter in
SharedSecretkeyed by client address with a back-off after N misses, if the SDK makes the address available
to a
TokenVerifier. #14 touches the same lines and should land together.Done when
planrefuses a three-word passphrase of over sixteen characters and accepts atoken_urlsafe(32)value, in a test beside the existingplantests.Related
#14 (open) is the non-ASCII
TypeErrorinverify_token(a crash, not a limit); #13 (open) isthe Host check; #3 (closed) introduced the shared secret and asked for no throttle.