Files
PraisonAIUI/examples/python/29-full-extensibility/plugin.js
T
77d7d4241a fix: safe rendering in agent-dashboard (fixes #59) (#87)
* feat: deprecate unsafe innerHTML examples and fix XSS vulnerabilities (fixes #59)

- Add deprecation warning to example 12-agent-dashboard with runtime DeprecationWarning
- Replace innerHTML usage in plugin.js with safe DOM APIs (textContent, createElement)
- Example 12 now warns users to migrate to examples/15-dashboard-test/ or examples/13-real-dashboard/
- Prevents XSS injection through API responses in admin dashboard

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>

* fix: replace innerHTML with safe DOM methods to prevent XSS (fixes #59)

- Fixed renderApiPageData function XSS in error handling
- Fixed renderComponents function XSS in JSON viewer
- Fixed renderCard function XSS in title, value, footer fields
- Fixed renderChart function XSS in title field
- Fixed renderTable function XSS in headers and cell data
- Fixed aiui.createBoard error handler XSS in error messages
- Fixed navigation items XSS in page titles and icons
- Fixed header branding XSS in site title and logo
- Fixed A2UI surface fallback XSS in JSON output
- Fixed component default case XSS in JSON output
- Fixed key-value list XSS in labels and values

All innerHTML usage with user-controlled API data has been replaced
with safe DOM methods (createElement, textContent, appendChild) to
prevent XSS vulnerabilities in the admin dashboard.

Co-authored-by: Mervin Praison <[email protected]>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude <[email protected]>
Co-authored-by: Mervin Praison <[email protected]>
Co-authored-by: MervinPraison <[email protected]>
2026-06-15 16:25:58 +01:00

107 lines
4.3 KiB
JavaScript

/*
* Client-side extension demo for PraisonAIUI.
*
* Demonstrates:
* 1. window.aiui.registerComponent(type, renderFn) — adds a new component type
* 2. window.aiui.registerView(pageId, render, cleanup) — adds a custom page view
*
* To load this file, paste its contents into the browser DevTools Console
* after the page loads. (A Python-side API to inject custom JS is a planned
* enhancement — see 28-full-extensibility/README.md.)
*/
(function () {
if (!window.aiui) {
console.warn('[plugin.js] window.aiui not ready yet. Run after the page loads.');
return;
}
// ── 1. Register a custom `timeline` component renderer ──────────
window.aiui.registerComponent('timeline', function (comp) {
const el = document.createElement('div');
el.className = 'db-timeline';
el.style.cssText = 'display:flex;flex-direction:column;gap:12px;padding:16px';
(comp.events || []).forEach(function (ev) {
const row = document.createElement('div');
row.style.cssText =
'display:flex;gap:16px;align-items:center;padding:12px;' +
'background:var(--db-card-bg, #1f2937);border-radius:8px;' +
'border-left:4px solid var(--db-accent, #818cf8)';
const iconDiv = document.createElement('div');
iconDiv.style.cssText = 'font-size:24px';
iconDiv.textContent = ev.icon || '•';
const contentDiv = document.createElement('div');
contentDiv.style.cssText = 'flex:1';
const labelDiv = document.createElement('div');
labelDiv.style.cssText = 'font-weight:600;color:var(--db-text, #fff)';
labelDiv.textContent = ev.label || '';
const timeDiv = document.createElement('div');
timeDiv.style.cssText = 'font-size:12px;color:var(--db-text-muted, #94a3b8);margin-top:4px';
timeDiv.textContent = ev.time || '';
contentDiv.appendChild(labelDiv);
contentDiv.appendChild(timeDiv);
row.appendChild(iconDiv);
row.appendChild(contentDiv);
el.appendChild(row);
});
return el;
});
console.log('[plugin.js] Registered custom component: timeline');
// ── 2. Register a client-only custom view ──────────────────────
window.aiui.registerView('custom-view', async function (container) {
container.innerHTML = '';
const box = document.createElement('div');
box.style.cssText = 'padding:24px;background:var(--db-card-bg, #1f2937);' +
'border-radius:12px;color:var(--db-text, #fff)';
const h2 = document.createElement('h2');
h2.style.cssText = 'margin:0 0 16px 0';
h2.textContent = '⚡ Client-Only View';
const p1 = document.createElement('p');
p1.textContent = 'This entire page is rendered by JavaScript in the browser — it does ';
const strongCode = document.createElement('strong');
strongCode.textContent = 'not';
p1.appendChild(strongCode);
p1.appendChild(document.createTextNode(' call '));
const code1 = document.createElement('code');
code1.textContent = '/api/pages/custom-view/data';
p1.appendChild(code1);
p1.appendChild(document.createTextNode('.'));
const p2 = document.createElement('p');
p2.textContent = 'Registered via ';
const code2 = document.createElement('code');
code2.textContent = 'window.aiui.registerView(\'custom-view\', ...)';
p2.appendChild(code2);
p2.appendChild(document.createTextNode('.'));
const p3 = document.createElement('p');
p3.textContent = 'Current time: ';
const clockSpan = document.createElement('span');
clockSpan.id = 'aiui-clock';
p3.appendChild(clockSpan);
box.appendChild(h2);
box.appendChild(p1);
box.appendChild(p2);
box.appendChild(p3);
container.appendChild(box);
const clock = box.querySelector('#aiui-clock');
const tick = function () { clock.textContent = new Date().toLocaleTimeString(); };
tick();
window.__aiui_clock_timer = setInterval(tick, 1000);
}, function () {
// Cleanup when user navigates away
clearInterval(window.__aiui_clock_timer);
console.log('[plugin.js] Cleaned up custom-view clock');
});
console.log('[plugin.js] Registered custom view: custom-view');
console.log('[plugin.js] ✅ All extensions loaded. Visit "Custom Component" and "Client-Only View" pages.');
})();