fix: multiple critical bugs in frontend

- sidebar: fix library/tag selection event handlers not firing (callbacks never invoked)
- sidebar: fix handleSelectLibrary always passing empty string instead of library id
- dashboard: fix tag filter not persisting when navigating from document view
- app: fix XSS vulnerability in showToast (API error messages not escaped)
- app: fix XSS vulnerability in confirmDelete modal message
- document: fix path traversal risk in export filename
This commit is contained in:
Hiro
2026-03-28 12:06:16 +00:00
parent af7f639c20
commit 461a17bc45
4 changed files with 80 additions and 43 deletions

View File

@@ -11,7 +11,7 @@ export function renderSidebar({ libraries, tags, selectedLibrary, selectedTag, o
return `
<div class="tree-node">
<div class="tree-item ${isSelected ? 'active' : ''}" onclick="handleSelectLibrary('${lib.id}')">
<div class="tree-item ${isSelected ? 'active' : ''}" data-action="library" data-library-id="${lib.id}">
<span class="tree-toggle ${hasChildren ? 'expanded' : ''}" style="padding-left:${depth * 12}px">
${hasChildren ? '▶' : ''}
</span>
@@ -25,13 +25,20 @@ export function renderSidebar({ libraries, tags, selectedLibrary, selectedTag, o
.join('');
};
// Store callbacks in a way that's safe and doesn't rely on inline script execution
const callbacks = {
onSelectLibrary,
onSelectTag,
onHome
};
return `
<aside class="sidebar">
<div class="sidebar-scroll">
<div class="sidebar-section">
<h3>📚 Libraries</h3>
<div class="library-tree">
<div class="tree-item ${!selectedLibrary ? 'active' : ''}" onclick="handleHome()">
<div class="tree-item ${!selectedLibrary ? 'active' : ''}" data-action="home">
<span class="icon">🏠</span>
<span class="label">All Documents</span>
</div>
@@ -42,7 +49,7 @@ export function renderSidebar({ libraries, tags, selectedLibrary, selectedTag, o
<h3>🏷️ Tags</h3>
<div class="tag-list">
${tags.map(tag => `
<div class="tag-item ${selectedTag === tag.name ? 'active' : ''}" onclick="handleSelectTag('${escapeHtml(tag.name)}')">
<div class="tag-item ${selectedTag === tag.name ? 'active' : ''}" data-action="tag" data-tag="${escapeHtml(tag.name)}">
<span>#${escapeHtml(tag.name)}</span>
<span class="tag-count">${tag.count}</span>
</div>
@@ -50,31 +57,32 @@ export function renderSidebar({ libraries, tags, selectedLibrary, selectedTag, o
</div>
</div>
<div class="quick-links">
<a class="quick-link" onclick="handleHome()">📋 All Documents</a>
<a class="quick-link" data-action="home">📋 All Documents</a>
<a class="quick-link" onclick="window.app.navigate('editor')">+ New Document</a>
</div>
</div>
</aside>
<script>
window.handleSelectLibrary = (id) => {
${onSelectLibrary ? `window.document.dispatchEvent(new CustomEvent('select-library', {detail: '${''}'}))` : ''}
};
window.handleSelectTag = (tag) => {
${onSelectTag ? `window.document.dispatchEvent(new CustomEvent('select-tag', {detail: tag}))` : ''}
};
window.handleHome = () => {
${onHome ? `window.document.dispatchEvent(new CustomEvent('go-home'))` : ''}
};
window.document.addEventListener('select-library', (e) => {
${onSelectLibrary ? onSelectLibrary.toString().replace(/\s+/g, ' ') : ''}
});
window.document.addEventListener('select-tag', (e) => {
${onSelectTag ? onSelectTag.toString().replace(/\s+/g, ' ') : ''}
});
window.document.addEventListener('go-home', (e) => {
${onHome ? onHome.toString().replace(/\s+/g, ' ') : ''}
});
(function() {
var callbacks = window.__sidebarCallbacks;
document.querySelectorAll('[data-action="home"]').forEach(function(el) {
el.addEventListener('click', function() {
if (callbacks && callbacks.onHome) callbacks.onHome();
});
});
document.querySelectorAll('[data-action="library"]').forEach(function(el) {
el.addEventListener('click', function() {
var id = this.getAttribute('data-library-id');
if (callbacks && callbacks.onSelectLibrary) callbacks.onSelectLibrary(id);
});
});
document.querySelectorAll('[data-action="tag"]').forEach(function(el) {
el.addEventListener('click', function() {
var tag = this.getAttribute('data-tag');
if (callbacks && callbacks.onSelectTag) callbacks.onSelectTag(tag);
});
});
})();
</script>
`;
}