Skip to content

Commit 9df8951

Browse files
committed
fix(gui/security): escape filterFormatters.software to close newline-bypass XSS in /relays
Mirror the encode-on-output mitigation shipped in PR #900 for tableFormatters.software (commit 5f3aff4) — wrap the formatter return in escapeHtml so the same neutralization applies on the filter render path at DataViewFilters.svelte:748. The space-only guard in makeSoftwareReadable() rejects literal spaces but lets newlines pass, so the payload <img\nsrc=x\nonerror=...> defeated the guard and reached the {@html} sink. After this change the structural HTML metacharacters are entity-encoded so no DOM is built and no event handler can fire. Also tightens the regression test: structural-escape assertions (no raw <, >, or ") replace the unsatisfiable substring check on 'onerror=' (which appears as inert identifier text in the escaped output — neutralized but not stripped). Out of scope (separate concern flagged by reporter): cache versioning / aggregate:complete IndexedDB+localStorage purge on deploy. Already- poisoned browser state may continue executing on reloads until fresh data overwrites it.
1 parent ad96d1b commit 9df8951

2 files changed

Lines changed: 13 additions & 2 deletions

File tree

apps/gui/src/lib/config/dataTable/relays.formatters.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,22 @@ describe('XSS regression: relay filter formatters', () => {
171171
// spaces but lets newlines through. Without escapeHtml, the
172172
// payload reaches {@html} in DataViewFilters.svelte and the img
173173
// onerror fires even when the filter pane is display:none.
174+
// After the fix, the structural HTML metacharacters are entity-
175+
// encoded, so no <img> element is constructed and the surrounding
176+
// attribute-quote `"` is escaped to &quot; — onerror= survives as
177+
// inert text inside the escaped string but cannot fire because
178+
// there is no parsed element to attach it to.
174179
const NEWLINE_BYPASS = `<img\nsrc=x\nonerror=location.href="https://attacker.example">`;
175180
const out = relayFilterFormatters.software(NEWLINE_BYPASS);
181+
// Structural metacharacters MUST be escaped so no DOM is built.
176182
expect(out).not.toContain('<img');
177-
expect(out).not.toContain('onerror=');
183+
expect(out).not.toContain('<');
184+
expect(out).not.toContain('>');
185+
expect(out).not.toContain('"');
186+
// And the canonical escape markers MUST be present.
178187
expect(out).toContain('&lt;img');
188+
expect(out).toContain('&quot;');
189+
expect(out).toContain('&gt;');
179190
});
180191

181192
it('software filter formatter escapes a script-tag payload', () => {

apps/gui/src/lib/config/dataTable/relays.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ export const filterFormatters: Formatters = {
565565
},
566566
software: (software) => {
567567
if(typeof software !== 'string') return '-';
568-
return makeSoftwareReadable(software);
568+
return escapeHtml(makeSoftwareReadable(software));
569569
},
570570
operatorPubkey: (pk: string): string => {
571571
if(!pk) return ' ';

0 commit comments

Comments
 (0)