Whitelist research source links (#2499)
This commit is contained in:
@@ -76,6 +76,15 @@ function _hlSearch(text) {
|
||||
'<mark class="doclib-search-hl">$1</mark>');
|
||||
} catch { return esc; }
|
||||
}
|
||||
|
||||
function _safeResearchHref(raw) {
|
||||
try {
|
||||
const parsed = new URL(String(raw || '').trim(), window.location.origin);
|
||||
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') return _esc(parsed.href);
|
||||
} catch {}
|
||||
return '';
|
||||
}
|
||||
|
||||
let _libraryEscHandler = null;
|
||||
let _librarySelectMode = false;
|
||||
let _librarySelectedIds = new Set();
|
||||
@@ -2649,7 +2658,7 @@ let _libraryArchivedView = false; // Documents tab showing archived docs?
|
||||
const data = await res.json();
|
||||
_researchItems = data.research || data || [];
|
||||
} catch (e) {
|
||||
grid.innerHTML = `<div class="hwfit-loading">Failed to load: ${e.message}</div>`;
|
||||
grid.innerHTML = `<div class="hwfit-loading">Failed to load: ${_esc(e.message)}</div>`;
|
||||
return;
|
||||
}
|
||||
_renderResearchGrid();
|
||||
@@ -2691,9 +2700,9 @@ let _libraryArchivedView = false; // Documents tab showing archived docs?
|
||||
const sources = Array.isArray(detail.sources) ? detail.sources : [];
|
||||
const sourcesList = sources.slice(0, 12).map((src, i) => {
|
||||
const title = _esc(src.title || src.url || `Source ${i + 1}`);
|
||||
const url = src.url || '';
|
||||
const url = _safeResearchHref(src.url);
|
||||
return url
|
||||
? `<li><a href="${_esc(url)}" target="_blank" rel="noopener">${title}</a></li>`
|
||||
? `<li><a href="${url}" target="_blank" rel="noopener">${title}</a></li>`
|
||||
: `<li>${title}</li>`;
|
||||
}).join('');
|
||||
const sourcesHtml = sources.length
|
||||
|
||||
@@ -1103,8 +1103,10 @@ function _renderResult(job) {
|
||||
html += '<div class="research-job-sources">';
|
||||
for (const s of job.sources.slice(0, 10)) {
|
||||
const title = _esc(s.title || s.url || '');
|
||||
const url = _esc(s.url || '');
|
||||
html += `<a href="${url}" target="_blank" rel="noopener" class="research-source-link">${title}</a>`;
|
||||
const url = _safeSourceHref(s.url);
|
||||
html += url
|
||||
? `<a href="${url}" target="_blank" rel="noopener" class="research-source-link">${title}</a>`
|
||||
: `<span class="research-source-link">${title}</span>`;
|
||||
}
|
||||
if (job.sources.length > 10) html += `<span class="research-source-more">+${job.sources.length - 10} more</span>`;
|
||||
html += '</div>';
|
||||
@@ -1231,3 +1233,11 @@ function _esc(s) {
|
||||
d.textContent = s || '';
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
function _safeSourceHref(raw) {
|
||||
try {
|
||||
const parsed = new URL(String(raw || '').trim(), window.location.origin);
|
||||
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') return _esc(parsed.href);
|
||||
} catch {}
|
||||
return '';
|
||||
}
|
||||
|
||||
26
tests/test_research_source_link_xss.py
Normal file
26
tests/test_research_source_link_xss.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""Regression guards for API-provided research source hrefs."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
_REPO = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def test_document_library_research_preview_whitelists_source_hrefs():
|
||||
src = (_REPO / "static" / "js" / "documentLibrary.js").read_text(encoding="utf-8")
|
||||
|
||||
assert "function _safeResearchHref(raw)" in src
|
||||
assert "parsed.protocol === 'http:' || parsed.protocol === 'https:'" in src
|
||||
assert "const url = _safeResearchHref(src.url);" in src
|
||||
assert 'href="${_esc(url)}"' not in src
|
||||
assert "Failed to load: ${_esc(e.message)}" in src
|
||||
assert "Failed to load: ${e.message}" not in src
|
||||
|
||||
|
||||
def test_research_panel_whitelists_source_hrefs():
|
||||
src = (_REPO / "static" / "js" / "research" / "panel.js").read_text(encoding="utf-8")
|
||||
|
||||
assert "function _safeSourceHref(raw)" in src
|
||||
assert "parsed.protocol === 'http:' || parsed.protocol === 'https:'" in src
|
||||
assert "const url = _safeSourceHref(s.url);" in src
|
||||
assert 'const url = _esc(s.url || \'\');' not in src
|
||||
Reference in New Issue
Block a user