SEC2-050: postulo-mcp locks mcp 2.1.1, whose Streamable HTTP server keeps sessions without bound (CVE-2026-59951), and CI never audits it #17
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#17
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: Low · Category: Dependencies · Reference: CWE-400 (Uncontrolled Resource Consumption); CWE-1104 (Use of Unmaintained Third Party Components); OWASP A06:2021 (Vulnerable and Outdated Components)
Location:
uv.lock(mcp 2.1.1,pyjwt 2.13.0)pyproject.toml:18(dependencies = ["mcp>=2.0", "httpx>=0.28"])src/postulo_mcp/serving.py:32(HTTP_TRANSPORTS = ("sse", "streamable-http")),src/postulo_mcp/serving.py:135-137(run_arguments: host, port andtransport_securityonly)src/postulo_mcp/__main__.py:45-48,130(--transportchoices;server.run(transport=...)).forgejo/workflows/ci.yml:20-27(unversioned installer;uv syncwithout--locked; no audit step)Issue
Unlike the other plugins, postulo-mcp is its own process ("Postulo itself is not a dependency at all",
pyproject.toml:16-18), so its lock is what runs. A pip-audit 2.10.1 run on 2026-10-16 against the exported lock reportsmcp 2.1.1with CVE-2026-59951 (GHSA-84m7-p3x7-pcfv, published 2026-10-14, fixed in 1.30.0 / 2.2.0): a Streamable HTTP server in stateful mode (the default ofMCPServer.run()) never reclaimed session state, registered a session even when its first request was refused, and had no cap on the number of sessions, so a client that can reach the endpoint can grow server memory without bound; the advisory says that authentication limits who can open sessions, not how many. postulo-mcp offers that transport (--transport streamable-http) and passes nostateless_http=True. The default transport is stdio, which is not affected, so the exposure needs an operator who chose to listen on a port;serving.py:3-7already treats that as the dangerous configuration.The lock also holds
pyjwt 2.13.0(fourteen advisories, fixed in 2.14.0 / 2.15.0), pulled in bymcp's auth module. postulo-mcp verifies its own shared secret withSharedSecret.verify_token(serving.py:73-76, acompare_digest) and decodes no JWT, so that one is minor on its own.Impact
Anyone who can reach the port of an instance started with
--transport streamable-httpcan exhaust the server's memory with unauthenticated or refused requests until it is restarted, taking the agent's access to the job search with it.Fix
Nothing in
server.pykeeps per-session state, so makerun_arguments()returnstateless_http=Trueforstreamable-http(mcp 2.1.1 already accepts it, so the mitigation does not wait for the upgrade), and lowermax_request_body_sizefor a personal host. Thenuv lock --upgrade-package mcpto the current release (>= 2.2.0), which brings pyjwt >= 2.15.1 throughpyjwt[crypto]. Done when: pip-audit against the exported lock reports nothing, and a test asserts thatplan("streamable-http", ...).run_arguments()carriesstateless_http=True.Related
#8 (open) - CI image,
--lockedand the Python matrix: the CI half (uv sync --locked, pinned installer) belongs there. postulo/postulo-apprise#13 (open) - names this repository'sci.yml:22installer line and missing audit step. #3 (closed) - "no unauthenticated HTTP transport", the shared secret the advisory says does not bound session count.