{"record":{"id":"d9adfdb1d7650f2a","repo":"ruvnet/ruflo","slug":"x-ruv-io-untrusted-data-envelope-is-unterminated-truncated","errorCode":null,"errorMessage":"x.ruv.io: untrusted-data envelope is unterminated (truncated or tampered response)","messagePattern":"x\\.ruv\\.io: untrusted-data envelope is unterminated \\(truncated or tampered response\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-tools/x-federation-tools.ts","lineNumber":86,"sourceCode":"// newline and can never open a fence. Counting raw occurrences instead would make\n// a publisher able to hard-fail every read simply by typing the marker into a\n// message, which trades a parse bug for a denial of service.\nconst OPEN_MARKER_ANCHORED = /(?:^|\\n)<<<UNTRUSTED_RELAY_DATA /g;\n\nexport function parseGatewayText(text: string): Record<string, unknown> {\n  // One response carries exactly one envelope. More than one means something\n  // upstream spliced an envelope-shaped string into the response, and picking\n  // either is a guess — refuse rather than choose.\n  const opens = (text.match(OPEN_MARKER_ANCHORED) ?? []).length;\n  if (opens > 1) {\n    throw new Error('x.ruv.io: response carries more than one untrusted-data envelope (tampered response)');\n  }\n  const fenced = UNTRUSTED_FENCE.exec(text);\n  if (fenced) return JSON.parse(fenced[2]) as Record<string, unknown>;\n  // An opening marker with no matching close is a truncated or tampered response.\n  // Fail loudly: parsing the remainder would silently drop relay content.\n  if (opens === 1) {\n    throw new Error('x.ruv.io: untrusted-data envelope is unterminated (truncated or tampered response)');\n  }\n  return JSON.parse(text) as Record<string, unknown>;\n}\n\n/**\n * The payload, for consumers INSIDE this package that immediately index the\n * value (`recent.messages`, `Object.keys(roster)`).\n *\n * At the MCP boundary we return the whole envelope so the caller can see whose\n * words these are. Internally that shape is a hazard: reading `.messages` off an\n * envelope yields undefined and `Object.keys()` yields the envelope's own five\n * keys, so a miscount looks like a real answer. Never do a bare property read on\n * a parseGatewayText result — come through here.\n */\nexport function relayPayload(text: string): Record<string, unknown> {\n  const parsed = parseGatewayText(text);\n  return (parsed.untrusted === true && parsed.data !== undefined\n    ? (parsed.data as Record<string, unknown>)","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/ruvnet/ruflo/blob/9c61c86f06b439af2a95085ae9bb0ca839662e41/v3/@claude-flow/cli/src/mcp-tools/x-federation-tools.ts#L68-L104","documentation":"parseGatewayText throws when it counts exactly one newline-anchored `<<<UNTRUSTED_RELAY_DATA` opening marker but the matching close `<<<END_UNTRUSTED_RELAY_DATA <same-uuid>>>` never appears. A complete envelope always ends with its close; an opener without one means the response was truncated mid-envelope (transport or gateway failure) or deliberately cut/tampered with to smuggle content past the fence. Parsing the remainder would silently drop relay content, so the parser fails loudly instead.","triggerScenarios":"Thrown at v3/@claude-flow/cli/src/mcp-tools/x-federation-tools.ts:86 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Retry the gateway call — truncation is often a transient transport or size-limit issue.","Check whether a proxy, MCP transport, or log sanitizer is cutting long responses and raise the limit or remove the culprit.","Verify the gateway endpoint's integrity; an attacker may be truncating the close marker to escape the untrusted-data fence.","Surface the error to the caller instead of partially consuming the payload — any content after the opener is unverified."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"9c61c86f06b439af2a95085ae9bb0ca839662e41","analyzedAt":"2026-09-22T05:44:27.648Z","contentChangedAt":"2026-09-22T05:44:27.648Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}