mirror of
https://github.com/vinta/awesome-python.git
synced 2026-05-09 22:53:49 +08:00
fix(website): escape </script> in embedded filter URLs JSON
`| safe` bypasses Jinja autoescape. If a category name ever contained "</script>", the literal substring would close the script block early, leaking JSON content into the DOM and creating an XSS vector. Replace "</" with "<\\/" (still valid JSON) and pass ensure_ascii=False so non-ASCII names render readably. Also add a group_path() helper to parallel category_path()/subcategory_path() and reuse category_urls when seeding filter_urls. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+7
-5
@@ -92,6 +92,10 @@ def category_public_url(category: ParsedSection) -> str:
|
|||||||
return f"{SITE_URL}categories/{category['slug']}/"
|
return f"{SITE_URL}categories/{category['slug']}/"
|
||||||
|
|
||||||
|
|
||||||
|
def group_path(group_slug: str) -> str:
|
||||||
|
return f"/categories/{group_slug}/"
|
||||||
|
|
||||||
|
|
||||||
def group_public_url(group_slug: str) -> str:
|
def group_public_url(group_slug: str) -> str:
|
||||||
return f"{SITE_URL}categories/{group_slug}/"
|
return f"{SITE_URL}categories/{group_slug}/"
|
||||||
|
|
||||||
@@ -315,11 +319,9 @@ def build(repo_root: Path) -> None:
|
|||||||
entries = sort_entries(entries)
|
entries = sort_entries(entries)
|
||||||
category_urls = {cat["name"]: category_path(cat) for cat in categories}
|
category_urls = {cat["name"]: category_path(cat) for cat in categories}
|
||||||
|
|
||||||
filter_urls: dict[str, str] = {}
|
filter_urls: dict[str, str] = dict(category_urls)
|
||||||
for cat in categories:
|
|
||||||
filter_urls[cat["name"]] = category_path(cat)
|
|
||||||
for group in parsed_groups:
|
for group in parsed_groups:
|
||||||
filter_urls[group["name"]] = f"/categories/{group['slug']}/"
|
filter_urls[group["name"]] = group_path(group["slug"])
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
for sub in entry.get("subcategories", []):
|
for sub in entry.get("subcategories", []):
|
||||||
filter_urls[sub["value"]] = sub["url"]
|
filter_urls[sub["value"]] = sub["url"]
|
||||||
@@ -348,7 +350,7 @@ def build(repo_root: Path) -> None:
|
|||||||
build_date=build_date.strftime("%B %d, %Y"),
|
build_date=build_date.strftime("%B %d, %Y"),
|
||||||
sponsors=sponsors,
|
sponsors=sponsors,
|
||||||
category_urls=category_urls,
|
category_urls=category_urls,
|
||||||
filter_urls_json=json.dumps(filter_urls, sort_keys=True),
|
filter_urls_json=json.dumps(filter_urls, sort_keys=True, ensure_ascii=False).replace("</", "<\\/"),
|
||||||
),
|
),
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -655,6 +655,35 @@ class TestBuild:
|
|||||||
assert data["AI & ML"] == "/categories/ai-ml/"
|
assert data["AI & ML"] == "/categories/ai-ml/"
|
||||||
assert data["Machine Learning > Classical"] == "/categories/machine-learning/classical/"
|
assert data["Machine Learning > Classical"] == "/categories/machine-learning/classical/"
|
||||||
|
|
||||||
|
def test_filter_urls_json_escapes_closing_script_tag(self, tmp_path):
|
||||||
|
readme = textwrap.dedent("""\
|
||||||
|
# T
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sneaky </script><script>x=1</script>
|
||||||
|
|
||||||
|
- [a](https://example.com) - A.
|
||||||
|
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Done.
|
||||||
|
""")
|
||||||
|
self._copy_real_templates(tmp_path)
|
||||||
|
(tmp_path / "README.md").write_text(readme, encoding="utf-8")
|
||||||
|
build(tmp_path)
|
||||||
|
|
||||||
|
site = tmp_path / "website" / "output"
|
||||||
|
index_html = (site / "index.html").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
marker = '<script type="application/json" id="filter-urls">'
|
||||||
|
start = index_html.index(marker) + len(marker)
|
||||||
|
end = index_html.index("</script>", start)
|
||||||
|
block = index_html[start:end]
|
||||||
|
assert "</script>" not in block
|
||||||
|
data = json.loads(block)
|
||||||
|
assert any("Sneaky" in key for key in data)
|
||||||
|
|
||||||
def test_build_creates_group_pages(self, tmp_path):
|
def test_build_creates_group_pages(self, tmp_path):
|
||||||
readme = textwrap.dedent("""\
|
readme = textwrap.dedent("""\
|
||||||
# T
|
# T
|
||||||
|
|||||||
Reference in New Issue
Block a user