nexu-io/open-design · error · Error

javascript: URLs are not supported in live artifact previews

Error message

javascript: URLs are not supported in live artifact previews

What it means

Thrown by validateHtmlTemplateV1Security when an href, src, action, or formaction attribute is assigned a javascript: URL (e.g. href="javascript:..."). javascript: URLs execute their payload as script when the user activates the link/form, so they are blocked at template-validation time even though interpolation is otherwise permitted.

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. Replace javascript: URLs with real https:// (or other safe scheme) URLs.
  2. If you need behavior, it cannot live in the URL; live artifacts have no script execution at all.

Example fix

// before
<template><a href="javascript:doX()">{{data.label}}</a></template>
// after
<template><a href="{{data.url}}">{{data.label}}</a></template>
Defensive patterns

Strategy: validation

Validate before calling

function assertNoJavascriptUrl(html: string): void {
  if (/(?:href|src|action|formaction)\s*=\s*['"]?\s*javascript\s*:/i.test(html)) {
    throw new Error('javascript: URLs not allowed');
  }
}

Type guard

function isJavascriptUrlFree(html: string): boolean {
  return !/(?:href|src|action|formaction)\s*=\s*['"]?\s*javascript\s*:/i.test(html);
}

Try / catch

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

Prevention

When it happens

Trigger: <a href="javascript:alert(1)">, <form action="javascript:...">, <button formaction="javascript:...">, <img src="javascript:...">; the scheme may be upper/mixed case and the value may be unquoted or single-quoted (regex tolerates both).

Common situations: Model writes an interactive link using the javascript: scheme (legacy pattern); developer copies a 1990s-style anchor; intent to test the security filter; case-variation attempt (JAVASCRIPT:) to evade naive matchers (this regex is case-insensitive so it still catches).

Related errors


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