# CVE-2026-23744 — MCPJam Inspector unauthenticated public bind

| | |
|---|---|
| **Advisory** | [GHSA-232v-j27c-5pp6](https://github.com/advisories/GHSA-232v-j27c-5pp6) |
| **CVSS v3** | 9.8 |
| **Affected** | `mcpjam-inspector` ≤ 1.4.2 |
| **Fixed in** | 1.4.3 |
| **agent-airlock preset** | `mcpjam_cve_2026_23744_defaults()` (v0.5.6+) |
| **Module** | `agent_airlock.mcp_spec.bind_address_guard` |

## What broke

MCPJam Inspector ≤ 1.4.2 bound to `0.0.0.0` by default with **no
authentication**. Anyone on the same LAN — Wi-Fi network, corporate
VPN, cloud VPC — could reach the inspector's RCE surface. The
default startup pattern was effectively:

```javascript
// vulnerable startup — public bind, no auth
app.listen(8080, "0.0.0.0");  // CVE-2026-23744
```

The bug class generalises beyond MCPJam: any local MCP dev server
that binds to a public address without authentication is the same
shape.

## How agent-airlock blocks it

The v0.5.6 `bind_address_guard` ships two errors and one config:

```python
from agent_airlock.mcp_spec.bind_address_guard import (
    BindAddressGuardConfig,
    validate_bind_address,
)
from agent_airlock import (
    BindAddressPublicError,           # public bind without opt-in
    UnauthenticatedPublicBindError,   # public bind opted in, but no auth
)

# Default — refuse any non-loopback bind:
validate_bind_address("127.0.0.1", BindAddressGuardConfig())  # OK
validate_bind_address("0.0.0.0",  BindAddressGuardConfig())  # raises BindAddressPublicError

# Explicit opt-in for prod public bind, BUT auth must be in place:
validate_bind_address(
    "0.0.0.0",
    BindAddressGuardConfig(allow_public_bind=True, auth_required=True),
)  # OK

# The exact CVE-2026-23744 shape — public bind opted in, but no auth:
validate_bind_address(
    "0.0.0.0",
    BindAddressGuardConfig(allow_public_bind=True, auth_required=False),
)  # raises UnauthenticatedPublicBindError
```

### Public-bind aliases

The guard recognises four equivalent ways of saying "all interfaces":

| Literal | Meaning |
|---|---|
| `0.0.0.0` | "any IPv4" — the canonical CVE shape |
| `::` | "any IPv6" |
| `[::]` | bracketed IPv6 — common in URL parsers |
| `0:0:0:0:0:0:0:0` | expanded IPv6 |

It also calls through to `ipaddress.ip_address(...).is_unspecified`
so any equivalent representation (e.g. an `IPv6Address` constructed
from `0`) is also flagged.

## The companion preset

`mcpjam_cve_2026_23744_defaults()` is **scoped to dev-server tool
names**:

```python
from agent_airlock.policy_presets import mcpjam_cve_2026_23744_defaults

cfg = mcpjam_cve_2026_23744_defaults()
cfg["check"]("mcpjam",     "0.0.0.0", auth_required=False)  # raises BindAddressPublicError
cfg["check"]("inspector",  "127.0.0.1", auth_required=False)  # OK (loopback)
cfg["check"]("readPod",    "0.0.0.0", auth_required=False)  # OK (out of scope — not a dev server)
```

The pattern is `r"(?i)^(?:mcpjam|inspector|dev[-_ ]?server|studio)\b"`.
Tools outside the pattern are unaffected — the preset is deliberately
scoped to the dev-server name shapes the advisory called out.

## Primary sources

- [GHSA-232v-j27c-5pp6 / CVE-2026-23744](https://github.com/advisories/GHSA-232v-j27c-5pp6) (CVSS 9.8)
- [SentinelOne CVE-2026-23744](https://www.sentinelone.com/vulnerability-database/cve-2026-23744/)
- Fixture: `tests/cves/fixtures/cve_2026_23744_mcpjam.json`
