{"record":{"id":"87da1d17252f768b","repo":"nexu-io/open-design","slug":"script-elements-are-not-supported-in-live-artifact","errorCode":null,"errorMessage":"script elements are not supported in live artifact previews","messagePattern":"script 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 HTML matches /<\\s*script\\b/i — an opening <script> tag in any case, with any whitespace. Live artifact previews are server-rendered into static HTML and sandboxed; client-side script execution is forbidden, so any <script> element is rejected before interpolation to prevent script injection.","triggerScenarios":"Template contains <script>alert(1)</script>, <script src=...>, <SCRIPT>, or < script> (whitespace before tag name). Even a script tag inside an HTML comment or a string literal will match because the scan is plain regex over the raw template.","commonSituations":"Model trained on web tutorials inserts analytics/tracking scripts; developer copies a CodePen snippet that includes inline JS; misunderstanding that live artifacts are static previews, not full pages; intentionally testing the security filter.","solutions":["Remove the <script> tag entirely; live artifacts do not execute JavaScript.","Move any client-side logic to a real web app; live artifacts only display server-rendered data.","If you need dynamic behavior, request it as a new template directive — do not try to bypass the filter."],"exampleFix":"// before\n<template><div>{{data.title}}</div><script>console.log('hi')</script></template>\n// after\n<template><div>{{data.title}}</div></template>","handlingStrategy":"validation","validationCode":"function sanitizeTemplate(html: string): string {\n  if (/<\\s*script\\b/i.test(html)) throw new Error('script tags not allowed');\n  return html;\n}\n// or strip: html.replace(/<\\s*script\\b[\\s\\S]*?<\\/script>/ig, '')","typeGuard":"function hasNoScriptTag(html: string): boolean {\n  return !/<\\s*script\\b/i.test(html);\n}","tryCatchPattern":"try { validateHtmlTemplateV1Security(tpl); } catch (e) { /* reject template, log message */ throw e; }","preventionTips":["Treat templates as data, not as full HTML documents.","Run validateHtmlTemplateV1Security in your authoring UI before persisting a template.","Strip scripts at authoring time; never rely on runtime catch alone."],"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"}