{"id":"CVE-2026-76894","kind":"vulnerability","aliases":["CVE-2026-76894","GHSA-vqff-r82h-9crc"],"related":[],"title":"ckan-mcp-server: DataStore Table UI resource does not validate postMessage origin (in) and posts to targetOrigin \"*\" (out)","description":"## Summary\n\nThe 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.\n\n## Affected code\n\n`src/resources/datastore-table-ui.ts` (inline HTML):\n\n```js\nwindow.addEventListener('message', function (event) {\n  var msg = event.data;                    // no check of event.origin\n  if (!msg || typeof msg !== 'object') return;\n  if (msg.id !== undefined && _pending[msg.id]) { ... resolve/reject pending MCP request ... }\n  ...\n  if (data && (data.fields || data.records)) initTable(data);   // renders attacker-supplied data\n});\n\n// outbound:\nwindow.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params || {} }, '*');\nmcpNot: window.parent.postMessage({ jsonrpc:'2.0', method:method, params:params||{} }, '*');\n```\n\nThere is no allowlist of accepted origins for inbound messages and no fixed target origin for outbound messages.\n\n## Impact\n\n- **Content spoofing.** A frame that can `postMessage` to the UI can supply\n  arbitrary `fields`/`records`, causing the table to render fabricated rows and\n  links (e.g. a phishing `__link_url`) that appear to come from the queried\n  portal. (Values are HTML-escaped, so this is display spoofing, not script\n  injection — no XSS.)\n- **Handshake/UX interference.** Because inbound messages matching a pending `id`\n  resolve/reject the UI's MCP requests, an attacker frame can spoof responses to\n  the `ui/initialize` handshake or otherwise disrupt the panel.\n- **Outbound leakage.** Posting to `'*'` means the UI's JSON-RPC messages are\n  delivered to whatever the parent origin is; if the resource is ever embedded by\n  a context other than the trusted host, those messages leak to it.\n\nExploitability depends on the host embedding model (in a correct MCP Apps host the\nparent is the trusted host, which limits who can message the frame), so this is a\nhardening / defense-in-depth issue rather than a directly remotely-triggerable\none — hence Low.\n\n## Proof of concept\n\n- `poc/verify-no-origin-check.mjs` — static confirmation that the listener has no\n  origin check and outbound uses `'*'`:\n\n  ```\n  [x] validates event.origin             : NO\n  [x] outgoing postMessage targetOrigin  : '*' (any)\n  RESULT: VULNERABLE\n  ```\n\n- `poc/parent-spoof-poc.html` — a page that embeds the UI and injects fabricated\n  rows cross-origin with no handshake; the UI renders them.\n\n## Remediation\n\n- Validate `event.origin` against the expected host origin on every inbound\n  message; drop messages from other origins.\n- Send outbound `postMessage` with an explicit target origin (the host origin\n  established at initialize time) instead of `'*'`.\n- Optionally, bind to a nonce/channel established during `ui/initialize` so only\n  the negotiated host can drive the panel.","summary":"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).","severity":"low","cvss":3.0999999046325684,"epss":null,"exploited":false,"exploited_at":null,"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"],"how_to_check":null,"fix":"Upgrade @aborruso/ckan-mcp-server to 0.4.112 or later.","status":"open","published_at":"2026-10-16T19:23:48+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","triaged_at":"2026-10-23T20:56:45+00:00","updated_at":"2026-10-23T20:56:45+00:00","category":"ai-tooling","confidence":0.6,"triage":{"at":"2026-10-23T20:56:45Z","model":"claude-haiku-5-5","output":{"fix":"Upgrade @aborruso/ckan-mcp-server to 0.4.112 or later.\nUnknown whether the fix is limited to the version bump; the sources do not say how to confirm it.","reach":"local","impact":"low","summary":"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).","category":"ai-tooling","who_cares":["mcp","claude-code","npm","ckan-mcp-server"],"confidence":0.6,"needs_auth":"no","worst_case":"Attacker frame spoofs table content and phishing links in the host panel","analyst_note":"","how_to_check":"unknown","needs_analyst":false,"not_affected_if":["default-config","managed-hosting"],"user_interaction":"unknown","needs_special_setup":"yes","severity_if_unknown":"low"},"cost_usd":0.00058,"input_tokens":3959,"output_tokens":368,"severity_before":"low"},"analyst_status":null,"analyst_note":null,"search":"'-2026':20A,28A '-345':42B '-76894':21A,29A '-940':44B '2.0':221C,231C '9crc':26A 'abl':142C 'aborruso/ckan-mcp-server':30A 'accept':57B,241C 'affect':175C 'ai':32B 'ai-tool':31B 'allowlist':239C 'app':53B,104C,387C 'appear':285C 'arbitrari':267C 'attack':215C,323C 'attacker-suppli':214C 'bind':505C 'broadcast':170C 'cannot':93B 'caus':270C 'check':189C,434C 'ckan':2A,35B,49B 'ckan-mcp-serv':1A,34B,48B 'claud':39B 'claude-cod':38B 'code':40B,176C 'come':287C 'communic':117C 'concept':424C 'confirm':427C 'content':255C 'context':365C 'correct':385C 'cross':461C 'cross-origin':460C 'cve':19A,27A 'cwe':41B,43B 'data':149C,208C,212C,217C 'data.fields':209C 'data.records':210C 'datastor':5A,54B,109C 'defens':408C 'defense-in-depth':407C 'deliv':350C 'depend':377C 'depth':410C 'direct':415C 'display':300C 'disrupt':334C 'document':146C 'drive':518C 'drop':482C 'e.g':279C 'emb':453C 'embed':362C,381C 'escap':100B,296C 'establish':498C,509C 'event':184C 'event.data':187C 'event.origin':129C,191C,440C,472C 'ever':361C 'everi':479C 'expect':475C 'explicit':492C 'exploit':376C 'fabric':78B,275C,458C 'field':268C 'fix':248C 'frame':70B,258C,324C,401C 'function':183C 'ghsa':23A 'ghsa-vqff-r82h-9crc':22A 'handshak':86B,159C,331C,465C 'handshake/ux':307C 'harden':406C 'henc':420C 'host':114C,370C,380C,388C,394C,476C,496C,516C 'html':99B,179C,295C 'html-escap':98B,294C 'id':222C,223C,315C 'impact':254C 'inbound':124C,244C,310C,480C 'initi':500C 'initt':211C 'inject':148C,304C,457C 'inlin':178C 'instead':502C 'interact':108C 'interfer':154C,308C 'issu':411C 'js':180C 'json':166C,346C 'json-rpc':165C,345C 'jsonrpc':220C,230C 'leak':373C 'leakag':338C 'limit':396C 'link':82B,278C,282C 'listen':126C,430C 'low':421C 'match':312C 'mcp':3A,36B,45B,50B,52B,85B,103C,157C,205C,320C,386C 'mcpnot':228C 'mean':341C 'messag':65B,73B,125C,134C,168C,182C,245C,253C,311C,348C,372C,399C,481C,483C 'method':224C,225C,232C,233C 'model':382C 'msg':186C,193C,195C 'msg.id':199C,202C 'negoti':515C 'nonce/channel':508C 'npm':46B 'object':196C 'one':419C 'option':504C 'origin':13A,61B,68B,139C,173C,242C,250C,355C,433C,462C,477C,486C,494C,497C 'otherwis':333C 'outbound':133C,218C,252C,337C,436C,488C 'outgo':443C 'page':451C 'panel':336C,520C 'param':226C,227C,234C,235C 'parent':120C,354C,390C 'pend':201C,204C,314C 'perform':127C 'phish':81B,281C 'poc/parent-spoof-poc.html':449C 'poc/verify-no-origin-check.mjs':425C 'portal':291C 'post':16A,339C 'postmessag':12A,58B,122C,261C,444C,489C 'proof':422C 'queri':290C 'r82h':25A 'rather':412C 'reach':144C 'record':269C 'remedi':470C 'remot':417C 'remotely-trigger':416C 'render':77B,111C,213C,274C,468C 'request':206C,321C 'resolve/reject':203C,316C 'resourc':8A,106C,359C 'respons':87B,327C 'restrict':174C 'result':447C 'return':197C 'row':79B,276C,459C 'rpc':167C,347C 'run':94B 'say':91B 'script':95B,303C 'send':63B,487C 'sent':136C 'server':4A,37B,51B 'sourc':90B 'spoof':84B,256C,301C,326C 'src/resources/datastore-table-ui.ts':177C 'static':426C 'summari':101C 'suppli':216C,266C 'tabl':6A,55B,110C,152C,272C 'target':67B,138C,249C,493C 'targetorigin':18A,445C 'time':501C 'tool':33B 'trigger':418C 'trust':369C,393C 'typeof':194C 'ui':7A,56B,75B,105C,158C,162C,264C,318C,343C,455C,467C 'ui/initialize':330C,511C 'undefin':200C 'url':283C 'use':437C 'valid':11A,130C,439C,471C 'valu':96B,292C 'var':185C 'via':121C 'vqff':24A 'vulner':448C 'webview/iframe':116C 'whatev':352C 'window.addeventlistener':181C 'window.parent.postmessage':219C,229C 'window/frame':141C 'without':171C 'x':438C,442C 'xss':306C","nicknames":[],"mitigations":[],"who_affected":null,"tier":null,"verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-16T19:23:48+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-vqff-r82h-9crc","url":"https://github.com/advisories/GHSA-vqff-r82h-9crc","severity":"low","published_at":"2026-10-16T19:23:48+00:00","modified_at":"2026-10-16T19:24:46+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","fetched_at":"2026-10-23T19:11:32+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}