fix(security): stop rendering CSV cell values via unescaped innerHTML

renderLibrary and renderTasks built their collapsible section headers
with `summary.innerHTML = \`...${genre}...\`` / `...${project}...` —
genre/project come straight from a CSV cell, so a value like
`<img src=x onerror=...>` would execute as markup instead of rendering
as text. Obsidian's plugin review flags raw innerHTML with dynamic
content for exactly this reason. Switched both to createSpan/text,
matching every other render call in the codebase. Layout is unaffected
(both headers are flex containers, so the plain text node between the
two spans was already an anonymous flex item — now it's a real span,
same visual result). Tests: 115 + 112 passed, typecheck clean.
This commit is contained in:
SVM0N 2026-07-16 16:56:24 +00:00
parent 4faff4588e
commit ccfa983b82
3 changed files with 18 additions and 14 deletions

24
main.js

File diff suppressed because one or more lines are too long

View file

@ -196,7 +196,9 @@ export function renderLibrary(view: CardView, container: HTMLElement): void {
});
const summary = section.createEl("summary", { cls: "csv-library-section-header" });
summary.innerHTML = `<span class="csv-library-arrow">▶</span> ${genre} <span class="csv-library-count">${items.length}</span>`;
summary.createSpan({ cls: "csv-library-arrow", text: "▶" });
summary.createSpan({ text: ` ${genre} ` });
summary.createSpan({ cls: "csv-library-count", text: String(items.length) });
const grid = section.createDiv({ cls: "csv-library-grid" });

View file

@ -314,7 +314,9 @@ function renderSection(
const details = section.createEl("details", { cls: "csv-tasks-group" });
details.open = true;
const summary = details.createEl("summary", { cls: "csv-tasks-group-header" });
summary.innerHTML = `<span class="csv-tasks-arrow">▶</span> ${project} <span class="csv-tasks-count">${items.length}</span>`;
summary.createSpan({ cls: "csv-tasks-arrow", text: "▶" });
summary.createSpan({ text: ` ${project} ` });
summary.createSpan({ cls: "csv-tasks-count", text: String(items.length) });
const wrapper = details.createDiv({ cls: "csv-tasks-table-wrapper" });
const table = wrapper.createEl("table", { cls: "csv-tasks-table" });