nexu-io/open-design · error · Error

script elements are not supported in live artifact previews

Error message

script elements are not supported in live artifact previews

What it means

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.

Source

Thrown at apps/daemon/src/live-artifacts/render.ts:34

const TEMPLATE_INTERPOLATION = /{{\s*([^{}]+?)\s*}}/g;
const RAW_TEMPLATE_INTERPOLATION = /{{{[^{}]*}}}|{{\s*&[^{}]*}}/;
const TEMPLATE_PATH = /^(?:data|[A-Za-z_][A-Za-z0-9_]*)(?:\.(?:[A-Za-z_][A-Za-z0-9_-]*|\d+))*$/;
// `data-od-repeat="item in data.items"` — one loop variable over one `data.*` array.
const REPEAT_DIRECTIVE = /\s*\bdata-od-repeat\s*=\s*"([^"]*)"/i;
const REPEAT_DIRECTIVE_SPEC = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+in\s+(data(?:\.(?:[A-Za-z_][A-Za-z0-9_-]*|\d+))*)\s*$/;
const EXECUTABLE_TEMPLATE_PATTERNS: Array<{ pattern: RegExp; message: string }> = [
  { pattern: /<\s*script\b/i, message: 'script elements are not supported in live artifact previews' },
  { pattern: /<\s*iframe\b/i, message: 'iframe elements are not supported in live artifact previews' },
  { pattern: /\bsrcdoc\s*=/i, message: 'srcdoc attributes are not supported in live artifact previews' },
  { pattern: /\son[a-z][a-z0-9_-]*\s*=/i, message: 'event handler attributes are not supported in live artifact previews' },
  { pattern: /(?:href|src|action|formaction)\s*=\s*['"]?\s*javascript\s*:/i, message: 'javascript: URLs are not supported in live artifact previews' },
  { pattern: /\bdata-od-(?:html|raw|bind-html)\b/i, message: 'raw HTML insertion directives are not supported' },
];

export function validateHtmlTemplateV1Security(templateHtml: string): void {
  for (const { pattern, message } of EXECUTABLE_TEMPLATE_PATTERNS) {
    if (pattern.test(templateHtml)) throw new Error(message);
  }
}

export function escapeHtmlTemplateValue(value: unknown): string {
  return String(value)
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
    .replaceAll("'", '&#39;');
}

/**
 * A binding resolver for one scope. Given a trimmed binding path (e.g.
 * `data.title` or a loop variable path like `item.label`) it returns the
 * already-escaped scalar string to substitute, or throws for an unsupported
 * path. Loop scopes delegate non-matching heads (including `data.*`) to their
 * parent so global bindings keep working inside a repeat.

View on GitHub (pinned to 5be4028344)

Solutions

  1. Remove the <script> tag entirely; live artifacts do not execute JavaScript.
  2. Move any client-side logic to a real web app; live artifacts only display server-rendered data.
  3. If you need dynamic behavior, request it as a new template directive — do not try to bypass the filter.

Example fix

// before
<template><div>{{data.title}}</div><script>console.log('hi')</script></template>
// after
<template><div>{{data.title}}</div></template>
Defensive patterns

Strategy: validation

Validate before calling

function sanitizeTemplate(html: string): string {
  if (/<\s*script\b/i.test(html)) throw new Error('script tags not allowed');
  return html;
}
// or strip: html.replace(/<\s*script\b[\s\S]*?<\/script>/ig, '')

Type guard

function hasNoScriptTag(html: string): boolean {
  return !/<\s*script\b/i.test(html);
}

Try / catch

try { validateHtmlTemplateV1Security(tpl); } catch (e) { /* reject template, log message */ throw e; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/87da1d17252f768b. Report an issue: GitHub.