nexu-io/open-design · error · Error

srcdoc attributes are not supported in live artifact preview

Error message

srcdoc attributes are not supported in live artifact previews

What it means

Thrown by validateHtmlTemplateV1Security when the template matches /\bsrcdoc\s*=/i — a srcdoc attribute anywhere in the markup. srcdoc is the inline-HTML delivery channel for iframes and is blocked independently of the iframe tag itself, so that even an iframe-less element carrying srcdoc (or a future element using the attribute) cannot smuggle HTML into a nested browsing context.

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 srcdoc attribute; render content inline in the template body instead.
  2. If you need nested HTML, it must be authored directly in the template (subject to the same security scan), not passed as a string attribute.

Example fix

// before
<template><div srcdoc='{{data.html}}'></div></template>
// after
<template><div>{{data.html}}</div></template>
Defensive patterns

Strategy: validation

Validate before calling

function assertNoSrcdoc(html: string): void {
  if (/\bsrcdoc\s*=/i.test(html)) throw new Error('srcdoc attribute not allowed');
}

Type guard

function isSrcdocFree(html: string): boolean {
  return !/\bsrcdoc\s*=/i.test(html);
}

Try / catch

try { validateHtmlTemplateV1Security(tpl); } catch (e) { throw e; }

Prevention

When it happens

Trigger: Any attribute written as srcdoc=... on any element, including <div srcdoc=...>, <iframe srcdoc='...'>, or quoted/unquoted forms; also matches inside text content if it happens to look like the attribute assignment.

Common situations: Model embeds an iframe using srcdoc instead of src to dodge the iframe-element check; developer copies an old embed snippet that used srcdoc; misunderstanding that the filter keys on the attribute, not the element.

Related errors


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