---
id: "CVE-2026-76894"
kind: "vulnerability"
aliases: ["CVE-2026-76894", "GHSA-vqff-r82h-9crc"]
title: "ckan-mcp-server: DataStore Table UI resource does not validate postMessage origin (in) and posts to targetOrigin \"*\" (out)"
severity: "low"
cvss: 3.0999999046325684
epss: null
exploited: false
status: "open"
category: "ai-tooling"
products: [{"name": "@aborruso/ckan-mcp-server", "fixed": ["0.4.112"], "affected": "< 0.4.112", "ecosystem": "npm"}]
ecosystems: ["npm"]
tags: ["ai-tooling", "ckan-mcp-server", "claude-code", "cwe-345", "cwe-940", "mcp", "npm"]
not_affected_if: ["default-config", "managed-hosting"]
published_at: "2026-10-16T19:23:48+00:00"
first_seen_at: "2026-10-23T19:11:32+00:00"
updated_at: "2026-10-23T20:56:45+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-vqff-r82h-9crc", "first_seen_at": "2026-10-23T19:11:32+00:00"}]
---

# ckan-mcp-server: DataStore Table UI resource does not validate postMessage origin (in) and posts to targetOrigin "*" (out)

The ckan-mcp-server MCP Apps DataStore table UI accepts postMessage from any origin and sends its messages with target origin '*'. A frame that can message the UI can render fabricated rows and phishing links, and spoof MCP handshake responses, but the sources say it cannot run script (values are HTML-escaped).

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- npm:@aborruso/ckan-mcp-server affected < 0.4.112 fixed 0.4.112

## Fix

Upgrade @aborruso/ckan-mcp-server to 0.4.112 or later.

## Timeline

- 2026-10-23T19:11:32+00:00 ghsa https://github.com/advisories/GHSA-vqff-r82h-9crc

## From the sources

## Summary

The MCP Apps UI resource (an interactive DataStore table rendered in the host's webview/iframe) communicates with its parent via `postMessage`. Its inbound `message` listener performs no `event.origin` validation, and its outbound messages are sent with target origin `'*'`. Any window/frame able to reach the document can inject data into the table and interfere with the MCP UI handshake, and the UI's own JSON-RPC messages are broadcast without an origin restriction.

## Affected code

`src/resources/datastore-table-ui.ts` (inline HTML):

```js
window.addEventListener('message', function (event) {
  var msg = event.data;                    // no check of event.origin
  if (!msg || typeof msg !== 'object') return;
  if (msg.id !== undefined && _pending[msg.id]) { ... resolve/reject pending MCP request ... }
  ...
  if (data && (data.fields || data.records)) initTable(data);   // renders attacker-supplied data
});

// outbound:
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params || {} }, '*');
mcpNot: window.parent.postMessage({ jsonrpc:'2.0', method:method, params:params||{} }, '*');
```

There is no allowlist of accepted origins for inbound messages and no fixed target origin for outbound messages.

## Impact

- **Content spoofing.** A frame that can `postMessage` to the UI can supply
  arbitrary `fields`/`records`, causing the table to render fabricated rows and
  links (e.g. a phishing `__link_url`) that appear to come from the queried
  portal. (Values are HTML-escaped, so this is display spoofing, not script
  injection — no XSS.)
- **Handshake/UX interference.** Because inbound messages matching a pending `id`
  resolve/reject the UI's MCP requests, an attacker frame can spoof responses to
  the `ui/initialize` handshake or otherwise disrupt the panel.
- **Outbound leakage.** Posting to `'*'` means the UI's JSON-RPC messages are
  delivered to whatever the parent origin is; if the resource is ever embedded by
  a context other than the trusted host, those messages leak to it.

Exploitability depends on the host embedding model (in a correct MCP Apps host the
parent is the trusted host, which limits who can message the frame), so this is a
hardening / defense-in-depth issue rather than a directly remotely-triggerable
one — hence Low.

## Proof of concept

- `poc/verify-no-origin-check.mjs` — static confirmation that the listener has no
  origin check and outbound uses `'*'`:

  ```
  [x] validates event.origin             : NO
  [x] outgoing postMessage targetOrigin  : '*' (any)
  RESULT: VULNERABLE
  ```

- `poc/parent-spoof-poc.html` — a page that embeds the UI and injects fabricated
  rows cross-origin with no handshake; the UI renders them.

## Remediation

- Validate `event.origin` against the expected host origin on every inbound
  message; drop messages from other origins.
- Send outbound `postMessage` with an explicit target origin (the host origin
  established at initialize time) instead of `'*'`.
- Optionally, bind to a nonce/channel established during `ui/initialize` so only
  the negotiated host can drive the panel.

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
