{"record":{"id":"d5e5dca9f30a89a9","repo":"nexu-io/open-design","slug":"javascript-urls-are-not-supported-in-live-artifac","errorCode":null,"errorMessage":"javascript: URLs are not supported in live artifact previews","messagePattern":"javascript: URLs 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 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.","triggerScenarios":"<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).","commonSituations":"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).","solutions":["Replace javascript: URLs with real https:// (or other safe scheme) URLs.","If you need behavior, it cannot live in the URL; live artifacts have no script execution at all."],"exampleFix":"// before\n<template><a href=\"javascript:doX()\">{{data.label}}</a></template>\n// after\n<template><a href=\"{{data.url}}\">{{data.label}}</a></template>","handlingStrategy":"validation","validationCode":"function assertNoJavascriptUrl(html: string): void {\n  if (/(?:href|src|action|formaction)\\s*=\\s*['\"]?\\s*javascript\\s*:/i.test(html)) {\n    throw new Error('javascript: URLs not allowed');\n  }\n}","typeGuard":"function isJavascriptUrlFree(html: string): boolean {\n  return !/(?:href|src|action|formaction)\\s*=\\s*['\"]?\\s*javascript\\s*:/i.test(html);\n}","tryCatchPattern":"try { validateHtmlTemplateV1Security(tpl); } catch (e) { throw e; }","preventionTips":["Use only http(s) URLs in href/src/action/formaction.","Beware case variations — the filter is case-insensitive but authoring discipline still helps.","Reject any URL string starting with 'javascript:' at the data layer too."],"tags":["security","xss","url-scheme","live-artifacts","validation"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}