{"record":{"id":"ca6405a3c12c1631","repo":"nexu-io/open-design","slug":"event-handler-attributes-are-not-supported-in-live","errorCode":null,"errorMessage":"event handler attributes are not supported in live artifact previews","messagePattern":"event handler attributes 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 matches /\\son[a-z][a-z0-9_-]*\\s*=/i — any inline event-handler attribute such as onclick=, onload=, on-error=, on-input=. Inline handlers are the simplest XSS vector: they execute attacker-controlled strings as JavaScript on DOM events. Live artifact previews forbid them so that interpolated data cannot become executable code.","triggerScenarios":"Any element carries an on* attribute: <button onclick=...>, <body onload=...>, <img onerror=...>, <input onfocus=...>; matches case-insensitively and tolerates whitespace around the =.","commonSituations":"Model adds interactivity via onclick handlers copied from a tutorial; developer prototypes form validation with oninput; image fallback pattern <img onerror=...>; misunderstanding that live artifacts have no event layer.","solutions":["Remove the on* attribute; live artifacts do not run JavaScript handlers.","Express desired behavior through the template's data bindings and repeat directives, not client-side event code."],"exampleFix":"// before\n<template><button onclick='doX()'>{{data.label}}</button></template>\n// after\n<template><button>{{data.label}}</button></template>","handlingStrategy":"validation","validationCode":"function assertNoInlineHandlers(html: string): void {\n  if (/\\son[a-z][a-z0-9_-]*\\s*=/i.test(html)) throw new Error('inline event handlers not allowed');\n}","typeGuard":"function isEventHandlerFree(html: string): boolean {\n  return !/\\son[a-z][a-z0-9_-]*\\s*=/i.test(html);\n}","tryCatchPattern":"try { validateHtmlTemplateV1Security(tpl); } catch (e) { throw e; }","preventionTips":["Never author on* attributes in templates.","If you copied a snippet from the web, strip all on* attributes before saving.","Add a lint rule that rejects on*= in template strings."],"tags":["security","xss","html-attribute","event-handler","live-artifacts","validation"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}