Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions gcp/website/frontend_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,7 @@ class ComputedHierarchy(NamedTuple):


def construct_hierarchy_string(target_bug_id: str, hierarchy: ComputedHierarchy,
known_ids: set[str]) -> str:
known_ids: set[str]) -> Markup:
"""Constructs a hierarchy string for display.

Args:
Expand All @@ -1112,9 +1112,12 @@ def construct_hierarchy_string(target_bug_id: str, hierarchy: ComputedHierarchy,
known_ids: A set of bug IDs that are known to exist (for link generation).

Returns:
A string representing the hierarchy for display by the frontend.
A Markup string representing the hierarchy for display by the frontend.
Vulnerability IDs are HTML-escaped so that values originating from
source records cannot inject markup when the template renders the
result.
"""
output_lines = []
output_lines: list[Markup] = []
root_nodes = hierarchy.root_nodes
graph = hierarchy.graph

Expand All @@ -1126,28 +1129,29 @@ def print_subtree(vuln_id: str) -> None:
vuln_id (str): The starting vuln_id for printing the subtree.
"""
if vuln_id != target_bug_id:
escaped_id = escape(vuln_id)
if vuln_id in known_ids:
output_lines.append("<li><a href=\"/vulnerability/" + vuln_id + "\">" +
vuln_id + " </a></li>")
output_lines.append(
Markup('<li><a href="/vulnerability/{0}">{0} </a></li>').format(
escaped_id))
else:
output_lines.append("<li>" + vuln_id + "</li>")
output_lines.append(Markup('<li>{0}</li>').format(escaped_id))

if vuln_id in graph:
sorted_children = sorted(graph[vuln_id])
for child in sorted_children:
if child != target_bug_id:
output_lines.append("<ul class=\"substream\">")
output_lines.append(Markup('<ul class="substream">'))
print_subtree(child)
output_lines.append("</ul>")
output_lines.append(Markup('</ul>'))

sorted_root_nodes = sorted(root_nodes)
for root in sorted_root_nodes:
output_lines.append("<ul class=\"aliases\">")
output_lines.append(Markup('<ul class="aliases">'))
print_subtree(root)
output_lines.append("</ul>")
output_lines.append(Markup('</ul>'))

final_string = "".join(output_lines)
return final_string
return Markup('').join(output_lines)


def reverse_tree(graph: dict[str, set[str]]) -> dict[str, set[str]]:
Expand Down
37 changes: 37 additions & 0 deletions gcp/website/frontend_handlers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,43 @@ def test_escapes_xss(self):
self.assertIn('&lt;script&gt;', result)


class ConstructHierarchyStringTest(unittest.TestCase):
"""Tests for frontend_handlers.construct_hierarchy_string."""

def _hierarchy(self, roots, graph):
return frontend_handlers.ComputedHierarchy(
root_nodes=set(roots), graph={
k: set(v) for k, v in graph.items()
})

def test_escapes_known_id(self):
"""IDs that match known_ids must be HTML-escaped inside the <a> tag."""
payload = '<script>alert(1)</script>'
hierarchy = self._hierarchy([payload], {})
result = frontend_handlers.construct_hierarchy_string(
'CVE-TARGET', hierarchy, {payload})
self.assertNotIn('<script>', result)
self.assertIn('&lt;script&gt;', result)

def test_escapes_unknown_id(self):
"""IDs that are not known still appear in a <li> and must be escaped."""
payload = '"><img src=x onerror=alert(1)>'
hierarchy = self._hierarchy([payload], {})
result = frontend_handlers.construct_hierarchy_string(
'CVE-TARGET', hierarchy, set())
self.assertNotIn('<img', result)
self.assertIn('&lt;img', result)
self.assertIn('&#34;', result)

def test_plain_id_rendered_as_link(self):
"""Well-formed IDs still render as a normal anchor."""
hierarchy = self._hierarchy(['CVE-2024-0001'], {})
result = frontend_handlers.construct_hierarchy_string(
'CVE-TARGET', hierarchy, {'CVE-2024-0001'})
self.assertIn('<a href="/vulnerability/CVE-2024-0001">', result)
self.assertIn('CVE-2024-0001 </a>', result)


def setUpModule():
"""Set up the test module."""
# Start the emulator BEFORE creating the ndb client
Expand Down
Loading