Skip to content

Commit 95e8527

Browse files
authored
fix(auth): require a primary access-token credential at the HTTP middleware (1.0.114) (#137)
* fix(auth): require a primary access-token credential at the HTTP middleware (1.0.114) Tighten AuthInjectionMiddleware so it admits a request only when a primary access-token credential is present (Authorization: Bearer, X-META-ACCESS-TOKEN, or X-PIPEBOARD-API-TOKEN). X-Pipeboard-Token is a supplementary service token used by the duplication callback and is not a standalone authentication credential. It continues to be forwarded alongside a primary credential, so the duplication flow is unaffected. Adds regression tests and bumps the version to 1.0.114. * chore: bump server.json to 1.0.114 to match pyproject.toml
1 parent 93083c6 commit 95e8527

5 files changed

Lines changed: 44 additions & 9 deletions

File tree

‎meta_ads_mcp/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from meta_ads_mcp.core.server import main
88

9-
__version__ = "1.0.113"
9+
__version__ = "1.0.114"
1010

1111
__all__ = [
1212
'get_ad_accounts',

‎meta_ads_mcp/core/http_auth_integration.py‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,15 @@ async def dispatch(self, request: Request, call_next):
256256
auth_token = FastMCPAuthIntegration.extract_token_from_headers(dict(request.headers))
257257
pipeboard_token = FastMCPAuthIntegration.extract_pipeboard_token_from_headers(dict(request.headers))
258258

259-
if not auth_token and not pipeboard_token:
260-
# Reject unauthenticated requests. Without this, the request would fall
261-
# through to tool handlers which transparently use the META_ACCESS_TOKEN
262-
# env var, allowing any network-reachable caller to invoke tools and
263-
# exfiltrate the operator's credential via Graph API error responses.
264-
# See GHSA-9gw6-46qc-99vr.
259+
if not auth_token:
260+
# A request must carry a primary access-token credential: an
261+
# Authorization: Bearer header, X-META-ACCESS-TOKEN, or
262+
# X-PIPEBOARD-API-TOKEN (all resolved by extract_token_from_headers).
263+
# X-Pipeboard-Token is only a supplementary service token for the
264+
# duplication callback — on its own it establishes no request auth
265+
# context, so it must not admit a request. Otherwise the request
266+
# would fall through to tool handlers that use the META_ACCESS_TOKEN
267+
# env var. See GHSA-9gw6-46qc-99vr.
265268
logger.warning(
266269
"HTTP Auth Middleware: rejecting request to %s — no Authorization "
267270
"Bearer or X-PIPEBOARD-API-TOKEN header present", request.url.path,

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "meta-ads-mcp"
7-
version = "1.0.113"
7+
version = "1.0.114"
88
description = "Model Context Protocol (MCP) server for Meta Ads - Use Remote MCP at pipeboard.co for easiest setup"
99
readme = "README.md"
1010
requires-python = ">=3.10"

‎server.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
33
"name": "co.pipeboard/meta-ads-mcp",
44
"description": "Facebook / Meta Ads automation with AI: analyze performance, test creatives, optimize spend.",
5-
"version": "1.0.113",
5+
"version": "1.0.114",
66
"remotes": [
77
{
88
"type": "streamable-http",

‎tests/test_http_auth_security.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,38 @@ def test_middleware_accepts_pipeboard_token():
7070
assert resp.status_code == 200
7171

7272

73+
def test_middleware_rejects_supplementary_token_alone():
74+
"""X-Pipeboard-Token is a supplementary service token (duplication
75+
callback) and must not, on its own, admit a request — without a primary
76+
access-token credential the request must be rejected with 401."""
77+
client = TestClient(_build_app())
78+
resp = client.post(
79+
"/mcp",
80+
json={"jsonrpc": "2.0", "method": "tools/list", "id": 1},
81+
headers={"X-Pipeboard-Token": "anything-not-validated"},
82+
)
83+
assert resp.status_code == 401
84+
assert resp.headers.get("WWW-Authenticate") == "Bearer"
85+
assert resp.json()["error"] == "Unauthorized"
86+
assert "reached_handler" not in resp.text
87+
88+
89+
def test_middleware_accepts_supplementary_token_with_primary_credential():
90+
"""The legitimate flow forwards X-Pipeboard-Token alongside a primary
91+
credential; the request must still be admitted."""
92+
client = TestClient(_build_app())
93+
resp = client.post(
94+
"/mcp",
95+
json={"jsonrpc": "2.0", "method": "tools/list", "id": 1},
96+
headers={
97+
"Authorization": "Bearer some-meta-token-value-xyz",
98+
"X-Pipeboard-Token": "pb-supplementary-token",
99+
},
100+
)
101+
assert resp.status_code == 200
102+
assert resp.json() == {"reached_handler": True}
103+
104+
73105
def test_redact_url_strips_access_token():
74106
url = (
75107
"https://graph.facebook.com/v24.0/me/adaccounts"

0 commit comments

Comments
 (0)