Security rules for HTML files produced by this skill. Every generated file must be safe to open, share, and deploy.
No <link> to external CSS, no <script src="..."> from CDNs, no @import for fonts, no font-face references to external URLs. Everything must be inline. The file must work offline and behind air-gapped networks.
Rationale: External dependencies create tracking vectors, require network access, and break when CDNs go down.
<!-- FORBIDDEN -->
<link rel="stylesheet" href="https://cdn.example.com/tailwind.css">
<script src="https://cdn.example.com/react.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet">
<!-- REQUIRED -->
<style>
/* All styles inline */
:root { --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
</style>Never use eval(), new Function(), setTimeout(string), or setInterval(string). All logic must be in explicit function declarations or arrow functions.
Rationale: String-based code execution is the #1 vector for injection attacks if any part of the content comes from user input.
// FORBIDDEN
eval('alert("hello")');
new Function('return 1')();
setTimeout('doSomething()', 100);
setInterval('tick()', 1000);
// REQUIRED
setTimeout(function () { doSomething(); }, 100);
setInterval(function () { tick(); }, 1000);When building DOM from data (user input, API responses, localStorage), use document.createElement(), element.textContent, and element.setAttribute(). Only use innerHTML when the content is entirely controlled and known-safe (e.g., your own template strings with no user input interpolated).
Rationale: innerHTML with user data enables XSS. Even if current content is safe, future changes might introduce vectors.
// FORBIDDEN
card.innerHTML = '<h3>' + userInput + '</h3>';
// REQUIRED
var h3 = document.createElement('h3');
h3.textContent = userInput;
card.appendChild(h3);
// ACCEPTABLE (entirely controlled content)
element.innerHTML = '<div class="stage"><div class="es-a"><h3>No tasks yet</h3></div></div>';Generated files must not make fetch(), XMLHttpRequest, WebSocket, EventSource, or <img src="..."> requests to external servers. The file is a static document.
Rationale: Network requests from HTML files can exfiltrate data or load malicious content.
<!-- FORBIDDEN -->
<img src="https://tracker.example.com/pixel.gif">
<script>fetch('/api/data');</script>
<link rel="dns-prefetch" href="//tracker.example.com">The file must work under a strict CSP:
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'
This means:
- No inline event handlers (
onclick,onload,onerror, etc.) - No
javascript:URIs - All event handling via
addEventListener
<!-- FORBIDDEN -->
<button onclick="handleClick()">Go</button>
<a href="javascript:void(0)">Link</a>
<body onload="init()">
<!-- REQUIRED -->
<button id="btn">Go</button>
<script>
document.getElementById('btn').addEventListener('click', handleClick);
</script>Never use data:text/html, data:text/javascript, or data:application/json URIs. data:image/svg+xml is acceptable only if the SVG contains no <script> elements, no on* event handlers, and no href="javascript:" attributes.
<!-- FORBIDDEN -->
<iframe src="data:text/html,<script>alert(1)</script>">
<a href="data:text/html,...">Link</a>
<!-- ACCEPTABLE (safe SVG) -->
<img src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%3E%3Crect%20width='100'%20height='100'/%3E%3C/svg%3E">When implementing "Copy as markdown/JSON" export buttons:
- Build the output string programmatically (not from innerHTML)
- Use
textContentor manual escaping for any user-influenced data - Never copy raw
innerHTMLto the clipboard
// FORBIDDEN
navigator.clipboard.writeText(element.innerHTML);
// REQUIRED
function buildMarkdown(tickets) {
return tickets.map(function (t) {
return '- **' + t.id + '** ' + t.title + ' — ' + t.tag;
}).join('\n');
}
navigator.clipboard.writeText(buildMarkdown(currentTickets));Never use href="javascript:..." or action="javascript:...". Use <button> elements with addEventListener instead.
<!-- FORBIDDEN -->
<a href="javascript:showPanel('x')">Show X</a>
<form action="javascript:void(0)">
<!-- REQUIRED -->
<button id="showX">Show X</button>
<script>
document.getElementById('showX').addEventListener('click', function () {
showPanel('x');
});
</script>When inserting system-generated content (file paths, code snippets, ticket titles) into the DOM, always escape HTML entities:
function escapeHtml(s) {
return String(s)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}When implementing drag-and-drop (triage boards, etc.), create elements via document.createElement() and element.textContent rather than building HTML strings. This prevents injection through ticket titles or descriptions.
Run through this list before delivering any .html file:
- File opens correctly from
file://with no console errors - No
<script src>,<link href>, or@import url()pointing to external resources - No
eval(),new Function(), or string-basedsetTimeout/setInterval - No
innerHTMLwith user-controlled data - No
fetch(),XMLHttpRequest, orWebSocketcalls - No
onclick,onload, oron*inline handlers — onlyaddEventListener - No
javascript:URIs - No
data:URIs for executable content - Clipboard output is built programmatically, not from innerHTML
- Content-Security-Policy compatible