mirror of
https://github.com/safishamsi/graphify.git
synced 2026-09-24 22:45:43 +00:00
fix(export): close stored XSS + broken neighbor links in graph.html (#1838)
The HTML report's neighbor "focus" links dropped an unescaped JSON.stringify(nid) into a double-quoted inline onclick. The stringified value carries its own quotes, so the attribute was truncated on every node (links never worked), and a node id/label containing a double-quote broke out of the attribute and injected live event handlers. AST ids are [a-z0-9_]-safe, but ids/labels from documents or titles scraped via `graphify add <url>` are not, so a hostile source could plant an executable handler into a locally-opened report. Carry the id in an HTML-escaped data-nid attribute and dispatch via one delegated listener bound to document (survives the innerHTML rebuild that recreates #neighbors-list). Closes the injection and repairs the links. Reported by @edgestack-ai. Co-Authored-By: edgestack-ai <edgestack-ai@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
edgestack-ai
Claude Opus 4.8
parent
94d3099540
commit
98c7ec039a
@@ -159,6 +159,27 @@ def test_to_html_contains_visjs():
|
||||
assert "vis-network" in content
|
||||
|
||||
|
||||
def test_to_html_neighbor_links_have_no_inline_onclick_xss():
|
||||
"""#1838: neighbor links dropped an unescaped JSON.stringify(nid) into a
|
||||
quoted inline onclick — which broke every link (the value's own quotes
|
||||
truncated the attribute) and let a node id/label containing a double-quote
|
||||
(from a document or a scraped `graphify add` URL) inject a live event handler
|
||||
into the local report (stored XSS). The template must instead carry the id in
|
||||
an escaped data attribute and dispatch via one delegated listener."""
|
||||
G = make_graph()
|
||||
communities = cluster(G)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
out = Path(tmp) / "graph.html"
|
||||
to_html(G, communities, str(out))
|
||||
html = out.read_text()
|
||||
# The vulnerable inline handler is gone entirely...
|
||||
assert 'onclick="focusNode(' not in html
|
||||
assert "JSON.stringify(nid)" not in html
|
||||
# ...replaced by an escaped data attribute + a single delegated listener.
|
||||
assert 'data-nid="${esc(nid)}"' in html
|
||||
assert "closest('.neighbor-link')" in html
|
||||
|
||||
|
||||
def test_to_html_pins_visjs_version_with_sri():
|
||||
"""vis-network script tag must use a pinned versioned URL with a sha384
|
||||
Subresource Integrity hash and crossorigin=anonymous. Without this,
|
||||
|
||||
Reference in New Issue
Block a user