Skip to content

fix(pickled-core): close inet_aton short-form bypass of MCP --allow-public guard - #29

Merged
bartrosa merged 1 commit into
mainfrom
cursor/critical-correctness-bugs-9005
Jun 1, 2026
Merged

bartrosa merged 1 commit into
mainfrom
cursor/critical-correctness-bugs-9005

Conversation

@cursor

@cursor cursor Bot commented May 31, 2026

Copy link
Copy Markdown

Bug and impact

pickled-* mcp serve --transport http --host 0 (and several other
inet_aton-compatible aliases of 0.0.0.0) silently binds to every
public interface on POSIX hosts, bypassing the --allow-public guard.

This is the same accidental-exposure footgun PR #27 closed for the IPv6
wildcard (::, [::], ::ffff:0.0.0.0), exploited via a different
vector. On a cloud VM with a routable IPv4 address, any developer who
copy-pastes --host 0 (a common shorthand) immediately makes the MCP
server reachable from the public internet without the explicit opt-in.

Root cause

packages/pickled-core/src/pickled_core/mcp/transport.py::_is_unspecified_address
relies on ipaddress.ip_address to detect wildcard hosts. That parser
intentionally rejects legacy inet_aton forms:

ipaddress.ip_address("0")        # ValueError
ipaddress.ip_address("0.0")      # ValueError
ipaddress.ip_address("0x0")      # ValueError
ipaddress.ip_address("00000000") # ValueError

…but POSIX socket.bind(("0", port)), ("0.0", port),
("0.0.0", port), ("0x0", port), ("00000000", port) and
("0.00.0.0", port) all succeed and canonicalise to 0.0.0.0
(verified locally). uvicorn / asyncio forward the host string verbatim
to socket.bind, so the guard never fires:

>>> _is_unspecified_address("0")      # before this PR
False
>>> socket.socket().bind(("0", 0))
>>> s.getsockname()
('0.0.0.0', 41503)

Fix and validation

When ipaddress.ip_address rejects the host, fall back to
socket.inet_aton. If the packed result equals b"\x00\x00\x00\x00",
treat the host as the unspecified address and refuse the bind unless
--allow-public was passed. Hostnames that incidentally contain digits
(localhost, zero.example.com) and inet_aton specific addresses
(127.0.0.1, 0.0.0.1, 10.0.0.1) are still allowed — we only block
strings that canonicalise to 0.0.0.0.

Validation:

  • 12 new pytest-parametrised cases in test_mcp_transport.py:
    • test_refuse_inet_aton_short_forms_without_flag — every bypass
      form raises RuntimeError("wildcard").
    • test_allow_inet_aton_specific_addresses_without_flag — short
      forms that resolve to specific addresses pass through.
    • test_hostnames_with_zero_letters_are_not_wildcards — DNS-style
      hostnames are still allowed and never resolved.
  • All previous transport tests (IPv6 wildcard, bracketed forms, expanded
    forms, loopback whitelist) still pass — total 34 transport tests.
  • Full workspace suite: uv run pytest -q → 453 passed, 4 skipped.

Companion to PR #27 — same guard, complementary vector.

Open in Web View Automation 

…c guard

POSIX socket.bind accepts legacy inet_aton-style IPv4 forms (e.g. '0',
'0.0', '0.0.0', '0x0', '00000000') and canonicalises them to 0.0.0.0.
asyncio/uvicorn forward the host string unchanged to socket.bind, so
'pickled-* mcp serve --transport http --host 0' silently bound to every
public interface — the same accidental-exposure footgun PR #27 closed
for the IPv6 wildcard, just via a different vector.

ipaddress.ip_address rejects these short forms, so the previous guard
returned False and let them through. The fix falls back to
socket.inet_aton when ipaddress parsing fails and treats any host whose
inet_aton-canonical packed form equals 0.0.0.0 as unspecified.

Adds 12 new pytest-parametrised cases covering the bypass forms,
inet_aton-style specific addresses (still allowed) and hostnames that
incidentally contain 'zero' (still allowed).

Co-authored-by: Bartłomiej Rosa <bartrosa@users.noreply.github.com>
@bartrosa
bartrosa marked this pull request as ready for review June 1, 2026 11:36
@bartrosa
bartrosa merged commit 3e01c4b into main Jun 1, 2026
0 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants