mirror of
https://github.com/safishamsi/graphify.git
synced 2026-07-12 02:17:04 +00:00
harden XML parsing against billion-laughs DoS in extract_csproj and extract_lpk
stdlib ET does not cap entity expansion — a crafted .csproj or .lpk with nested internal entities can exhaust memory. Pre-screen input bytes for <!DOCTYPE and <!ENTITY before parsing (legitimate MSBuild/Lazarus files never contain these). Also adds the missing 2 MiB size cap to extract_lpk (csproj already had one). No new dependencies required. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+40
-3
@@ -7389,6 +7389,30 @@ def extract_delphi_form(path: Path) -> dict:
|
||||
return {"nodes": nodes, "edges": edges, "input_tokens": 0, "output_tokens": 0}
|
||||
|
||||
|
||||
# Size cap for project XML files we parse with stdlib ElementTree.
|
||||
# Real .csproj/.fsproj/.vbproj/.lpk files are well under 2 MiB; anything
|
||||
# larger is either malformed or hostile.
|
||||
_PROJECT_XML_MAX_BYTES = 2 * 1024 * 1024
|
||||
|
||||
|
||||
def _project_xml_is_safe(src: bytes) -> bool:
|
||||
"""Reject XML that declares DTDs or entities.
|
||||
|
||||
Stdlib ``xml.etree.ElementTree`` does not cap entity expansion, so a
|
||||
crafted project file could trigger a billion-laughs style DoS. External
|
||||
entity resolution is already disabled by pyexpat defaults, but rejecting
|
||||
``<!DOCTYPE`` / ``<!ENTITY`` outright is defense in depth.
|
||||
|
||||
Legitimate MSBuild and Lazarus package files never contain a DOCTYPE
|
||||
or ENTITY declaration, so this is a zero-false-positive screen.
|
||||
"""
|
||||
# Only the prolog can hold a DTD/internal subset, but be conservative
|
||||
# and scan the full byte range -- these formats use ASCII tags so a
|
||||
# case-insensitive substring match is sufficient.
|
||||
lowered = src.lower()
|
||||
return b"<!doctype" not in lowered and b"<!entity" not in lowered
|
||||
|
||||
|
||||
def extract_lazarus_package(path: Path) -> dict:
|
||||
"""Extract package metadata from Lazarus .lpk package files (XML format).
|
||||
|
||||
@@ -7408,8 +7432,18 @@ def extract_lazarus_package(path: Path) -> dict:
|
||||
"""
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
xml_root = ET.fromstring(text)
|
||||
src = path.read_bytes()
|
||||
except OSError as e:
|
||||
return {"nodes": [], "edges": [], "error": str(e)}
|
||||
|
||||
if len(src) > _PROJECT_XML_MAX_BYTES:
|
||||
return {"nodes": [], "edges": [], "error": "package file too large"}
|
||||
if not _project_xml_is_safe(src):
|
||||
return {"nodes": [], "edges": [],
|
||||
"error": "refusing XML with DOCTYPE/ENTITY declaration"}
|
||||
|
||||
try:
|
||||
xml_root = ET.fromstring(src)
|
||||
except Exception as e:
|
||||
return {"nodes": [], "edges": [], "error": str(e)}
|
||||
|
||||
@@ -7798,8 +7832,11 @@ def extract_csproj(path: Path) -> dict:
|
||||
except OSError:
|
||||
return {"nodes": [], "edges": [], "error": f"cannot read {path}"}
|
||||
|
||||
if len(src) > 2_097_152:
|
||||
if len(src) > _PROJECT_XML_MAX_BYTES:
|
||||
return {"nodes": [], "edges": [], "error": "project file too large"}
|
||||
if not _project_xml_is_safe(src):
|
||||
return {"nodes": [], "edges": [],
|
||||
"error": "refusing XML with DOCTYPE/ENTITY declaration"}
|
||||
|
||||
try:
|
||||
tree = ET.fromstring(src)
|
||||
|
||||
Reference in New Issue
Block a user