{"record":{"id":"fed2138e89697de6","repo":"nexu-io/open-design","slug":"iframe-elements-are-not-supported-in-live-artifact","errorCode":null,"errorMessage":"iframe elements are not supported in live artifact previews","messagePattern":"iframe elements are not supported in live artifact previews","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/live-artifacts/render.ts","lineNumber":34,"sourceCode":"\nconst TEMPLATE_INTERPOLATION = /{{\\s*([^{}]+?)\\s*}}/g;\nconst RAW_TEMPLATE_INTERPOLATION = /{{{[^{}]*}}}|{{\\s*&[^{}]*}}/;\nconst TEMPLATE_PATH = /^(?:data|[A-Za-z_][A-Za-z0-9_]*)(?:\\.(?:[A-Za-z_][A-Za-z0-9_-]*|\\d+))*$/;\n// `data-od-repeat=\"item in data.items\"` — one loop variable over one `data.*` array.\nconst REPEAT_DIRECTIVE = /\\s*\\bdata-od-repeat\\s*=\\s*\"([^\"]*)\"/i;\nconst REPEAT_DIRECTIVE_SPEC = /^\\s*([A-Za-z_][A-Za-z0-9_]*)\\s+in\\s+(data(?:\\.(?:[A-Za-z_][A-Za-z0-9_-]*|\\d+))*)\\s*$/;\nconst EXECUTABLE_TEMPLATE_PATTERNS: Array<{ pattern: RegExp; message: string }> = [\n  { pattern: /<\\s*script\\b/i, message: 'script elements are not supported in live artifact previews' },\n  { pattern: /<\\s*iframe\\b/i, message: 'iframe elements are not supported in live artifact previews' },\n  { pattern: /\\bsrcdoc\\s*=/i, message: 'srcdoc attributes are not supported in live artifact previews' },\n  { pattern: /\\son[a-z][a-z0-9_-]*\\s*=/i, message: 'event handler attributes are not supported in live artifact previews' },\n  { pattern: /(?:href|src|action|formaction)\\s*=\\s*['\"]?\\s*javascript\\s*:/i, message: 'javascript: URLs are not supported in live artifact previews' },\n  { pattern: /\\bdata-od-(?:html|raw|bind-html)\\b/i, message: 'raw HTML insertion directives are not supported' },\n];\n\nexport function validateHtmlTemplateV1Security(templateHtml: string): void {\n  for (const { pattern, message } of EXECUTABLE_TEMPLATE_PATTERNS) {\n    if (pattern.test(templateHtml)) throw new Error(message);\n  }\n}\n\nexport function escapeHtmlTemplateValue(value: unknown): string {\n  return String(value)\n    .replaceAll('&', '&amp;')\n    .replaceAll('<', '&lt;')\n    .replaceAll('>', '&gt;')\n    .replaceAll('\"', '&quot;')\n    .replaceAll(\"'\", '&#39;');\n}\n\n/**\n * A binding resolver for one scope. Given a trimmed binding path (e.g.\n * `data.title` or a loop variable path like `item.label`) it returns the\n * already-escaped scalar string to substitute, or throws for an unsupported\n * path. Loop scopes delegate non-matching heads (including `data.*`) to their\n * parent so global bindings keep working inside a repeat.","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/live-artifacts/render.ts#L16-L52","documentation":"Thrown by validateHtmlTemplateV1Security when the template matches /<\\s*iframe\\b/i. iframes are blocked because live artifact previews are rendered into a controlled host document; embedded frames could break out of the intended content boundary, load untrusted origins, or be used for clickjacking/SSRF via src URLs. The preview surface stays frame-free.","triggerScenarios":"Template contains <iframe src='https://...'></iframe>, <iframe srcdoc=...>, <IFRAME>, or any opening iframe tag with leading whitespace.","commonSituations":"Model embeds a YouTube/CodePen iframe for rich preview; developer pastes an embed snippet; intent to show another live artifact inside this one; misunderstanding the sandbox model.","solutions":["Replace the iframe with native HTML rendering of the data (e.g. show a thumbnail image linked to the external URL, do not embed it).","If the goal is to display external content, surface it as data (a link, an image) rather than a nested browsing context.","Request a first-party embed directive if you genuinely need framed content; do not bypass."],"exampleFix":"// before\n<template><iframe src='{{data.url}}'></iframe></template>\n// after\n<template><a href='{{data.url}}'>Open</a></template>","handlingStrategy":"validation","validationCode":"function assertNoIframe(html: string): void {\n  if (/<\\s*iframe\\b/i.test(html)) throw new Error('iframe tags not allowed');\n}","typeGuard":"function isIframeFree(html: string): boolean {\n  return !/<\\s*iframe\\b/i.test(html);\n}","tryCatchPattern":"try { validateHtmlTemplateV1Security(tpl); } catch (e) { /* reject template */ throw e; }","preventionTips":["Render external content as data (links/images), not as nested frames.","Run the validator in CI on committed templates.","Educate template authors that live artifacts are frame-free."],"tags":["security","xss","html-template","live-artifacts","validation"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}