mirror of
https://github.com/HKUDS/CLI-Anything.git
synced 2026-08-28 23:27:04 +08:00
Merge pull request #296 from aaronjmars/security/xxe-defusedxml
fix(security): route untrusted XML parsing through defusedxml
This commit is contained in:
@@ -10,6 +10,7 @@ import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from xml.etree import ElementTree as ET
|
||||
from defusedxml.ElementTree import fromstring as _defused_fromstring
|
||||
|
||||
from ..utils import drawio_xml
|
||||
|
||||
@@ -93,7 +94,7 @@ class Session:
|
||||
return False
|
||||
self._redo_stack.append(self._snapshot())
|
||||
prev = self._undo_stack.pop()
|
||||
self.root = ET.fromstring(prev)
|
||||
self.root = _defused_fromstring(prev)
|
||||
self._modified = bool(self._undo_stack)
|
||||
return True
|
||||
|
||||
@@ -103,7 +104,7 @@ class Session:
|
||||
return False
|
||||
self._undo_stack.append(self._snapshot())
|
||||
nxt = self._redo_stack.pop()
|
||||
self.root = ET.fromstring(nxt)
|
||||
self.root = _defused_fromstring(nxt)
|
||||
self._modified = True
|
||||
return True
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ Structure:
|
||||
import os
|
||||
import time
|
||||
from xml.etree import ElementTree as ET
|
||||
from defusedxml.ElementTree import parse as _defused_parse
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@@ -37,7 +38,7 @@ def parse_drawio(path: str) -> ET.Element:
|
||||
"""Parse a .drawio XML file. Returns the root <mxfile> element."""
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f"File not found: {path}")
|
||||
tree = ET.parse(path)
|
||||
tree = _defused_parse(path)
|
||||
return tree.getroot()
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ setup(
|
||||
install_requires=[
|
||||
"click>=8.0.0",
|
||||
"prompt-toolkit>=3.0.0",
|
||||
"defusedxml>=0.7.1",
|
||||
],
|
||||
extras_require={
|
||||
"dev": [
|
||||
|
||||
@@ -5,6 +5,7 @@ and style string helpers.
|
||||
"""
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
from defusedxml.ElementTree import parse as _defused_parse, fromstring as _defused_fromstring
|
||||
import re
|
||||
from typing import Dict, Optional, Any
|
||||
|
||||
@@ -79,12 +80,12 @@ def create_svg_element(
|
||||
|
||||
def parse_svg(svg_string: str) -> ET.Element:
|
||||
"""Parse an SVG string into an ElementTree Element."""
|
||||
return ET.fromstring(svg_string)
|
||||
return _defused_fromstring(svg_string)
|
||||
|
||||
|
||||
def parse_svg_file(path: str) -> ET.Element:
|
||||
"""Parse an SVG file into an ElementTree Element."""
|
||||
tree = ET.parse(path)
|
||||
tree = _defused_parse(path)
|
||||
return tree.getroot()
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ setup(
|
||||
install_requires=[
|
||||
"click>=8.0.0",
|
||||
"prompt-toolkit>=3.0.0",
|
||||
"defusedxml>=0.7.1",
|
||||
],
|
||||
extras_require={
|
||||
"dev": [
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
import tempfile
|
||||
import zipfile
|
||||
import xml.etree.ElementTree as ET
|
||||
from defusedxml.ElementTree import fromstring as _defused_fromstring
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
@@ -125,7 +126,7 @@ def import_odf(
|
||||
return project
|
||||
|
||||
try:
|
||||
root = ET.fromstring(content_xml)
|
||||
root = _defused_fromstring(content_xml)
|
||||
except ET.ParseError as e:
|
||||
raise ValueError(f"Invalid ODF content.xml in: {path}") from e
|
||||
if inferred_type == "writer":
|
||||
@@ -157,7 +158,7 @@ def _apply_metadata(project: Dict[str, Any], meta_xml: str, source_path: str) ->
|
||||
return
|
||||
|
||||
try:
|
||||
root = ET.fromstring(meta_xml)
|
||||
root = _defused_fromstring(meta_xml)
|
||||
except ET.ParseError as e:
|
||||
raise ValueError(f"Invalid ODF meta.xml in: {source_path}") from e
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ Key ODF structure:
|
||||
import os
|
||||
import zipfile
|
||||
import xml.etree.ElementTree as ET
|
||||
from defusedxml.ElementTree import fromstring as _defused_fromstring
|
||||
from typing import Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
|
||||
@@ -660,21 +661,21 @@ def validate_odf(path: str) -> Dict[str, Any]:
|
||||
# Validate XML in content.xml
|
||||
if "content.xml" in names:
|
||||
try:
|
||||
ET.fromstring(zf.read("content.xml"))
|
||||
_defused_fromstring(zf.read("content.xml"))
|
||||
except ET.ParseError as e:
|
||||
errors.append(f"Invalid XML in content.xml: {e}")
|
||||
|
||||
# Validate XML in styles.xml
|
||||
if "styles.xml" in names:
|
||||
try:
|
||||
ET.fromstring(zf.read("styles.xml"))
|
||||
_defused_fromstring(zf.read("styles.xml"))
|
||||
except ET.ParseError as e:
|
||||
errors.append(f"Invalid XML in styles.xml: {e}")
|
||||
|
||||
# Validate XML in meta.xml
|
||||
if "meta.xml" in names:
|
||||
try:
|
||||
ET.fromstring(zf.read("meta.xml"))
|
||||
_defused_fromstring(zf.read("meta.xml"))
|
||||
except ET.ParseError as e:
|
||||
errors.append(f"Invalid XML in meta.xml: {e}")
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ setup(
|
||||
install_requires=[
|
||||
"click>=8.0.0",
|
||||
"prompt-toolkit>=3.0.0",
|
||||
"defusedxml>=0.7.1",
|
||||
],
|
||||
extras_require={
|
||||
"dev": [
|
||||
|
||||
@@ -124,7 +124,8 @@ class FileTransformBackend(Backend):
|
||||
def _xml_set_attr(self, p: dict) -> dict:
|
||||
"""Set an XML element attribute matched by XPath."""
|
||||
from xml.etree import ElementTree as ET
|
||||
tree = ET.parse(p["input_file"])
|
||||
from defusedxml.ElementTree import parse as _defused_parse
|
||||
tree = _defused_parse(p["input_file"])
|
||||
root = tree.getroot()
|
||||
elements = root.findall(p["xpath"])
|
||||
if not elements:
|
||||
@@ -137,7 +138,8 @@ class FileTransformBackend(Backend):
|
||||
def _xml_get_attr(self, p: dict) -> dict:
|
||||
"""Get an XML element attribute matched by XPath."""
|
||||
from xml.etree import ElementTree as ET
|
||||
tree = ET.parse(p["input_file"])
|
||||
from defusedxml.ElementTree import parse as _defused_parse
|
||||
tree = _defused_parse(p["input_file"])
|
||||
root = tree.getroot()
|
||||
elements = root.findall(p["xpath"])
|
||||
values = [el.get(p["attr"]) for el in elements]
|
||||
|
||||
@@ -38,6 +38,7 @@ setup(
|
||||
"click>=8.0.0",
|
||||
"prompt-toolkit>=3.0.0",
|
||||
"PyYAML>=6.0",
|
||||
"defusedxml>=0.7.1",
|
||||
],
|
||||
extras_require={
|
||||
"dev": [
|
||||
|
||||
@@ -6,6 +6,7 @@ Handles reading and writing .mscz (ZIP containing .mscx XML) and
|
||||
|
||||
import os
|
||||
import xml.etree.ElementTree as ET
|
||||
from defusedxml.ElementTree import parse as _defused_parse, fromstring as _defused_fromstring
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
@@ -91,7 +92,7 @@ def read_mscz(path: str) -> dict:
|
||||
if name.endswith(".mscx"):
|
||||
result["mscx_filename"] = name
|
||||
xml_bytes = zf.read(name)
|
||||
result["mscx"] = ET.ElementTree(ET.fromstring(xml_bytes))
|
||||
result["mscx"] = ET.ElementTree(_defused_fromstring(xml_bytes))
|
||||
elif name == "score_style.mss" or name.endswith("/score_style.mss"):
|
||||
result["style"] = zf.read(name).decode("utf-8")
|
||||
elif name == "audiosettings.json" or name.endswith("/audiosettings.json"):
|
||||
@@ -154,7 +155,7 @@ def read_mxl(path: str) -> ET.ElementTree:
|
||||
for name in zf.namelist():
|
||||
if name.endswith(".xml") and not name.startswith("META-INF"):
|
||||
xml_bytes = zf.read(name)
|
||||
return ET.ElementTree(ET.fromstring(xml_bytes))
|
||||
return ET.ElementTree(_defused_fromstring(xml_bytes))
|
||||
|
||||
raise ValueError(f"No MusicXML file found inside {path}")
|
||||
|
||||
@@ -344,6 +345,6 @@ def read_score_tree(path: str) -> ET.ElementTree:
|
||||
elif fmt == "mxl":
|
||||
return read_mxl(path)
|
||||
elif fmt == "musicxml":
|
||||
return ET.parse(path)
|
||||
return _defused_parse(path)
|
||||
else:
|
||||
raise ValueError(f"Cannot read XML tree from format: {fmt} ({path})")
|
||||
|
||||
@@ -33,6 +33,7 @@ setup(
|
||||
install_requires=[
|
||||
"click>=8.0.0",
|
||||
"prompt-toolkit>=3.0.0",
|
||||
"defusedxml>=0.7.1",
|
||||
],
|
||||
extras_require={
|
||||
"dev": [
|
||||
|
||||
@@ -10,6 +10,7 @@ import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import xml.etree.ElementTree as ET
|
||||
from defusedxml.ElementTree import fromstring as _defused_fromstring
|
||||
|
||||
from ..utils import mlt_xml
|
||||
|
||||
@@ -113,7 +114,7 @@ class Session:
|
||||
self._redo_stack.append(self._snapshot())
|
||||
prev = self._undo_stack.pop()
|
||||
mlt_xml._clear_parent_map()
|
||||
self.root = ET.fromstring(prev)
|
||||
self.root = _defused_fromstring(prev)
|
||||
mlt_xml._register_tree(self.root)
|
||||
self._resolve_refs()
|
||||
self._modified = bool(self._undo_stack)
|
||||
@@ -126,7 +127,7 @@ class Session:
|
||||
self._undo_stack.append(self._snapshot())
|
||||
nxt = self._redo_stack.pop()
|
||||
mlt_xml._clear_parent_map()
|
||||
self.root = ET.fromstring(nxt)
|
||||
self.root = _defused_fromstring(nxt)
|
||||
mlt_xml._register_tree(self.root)
|
||||
self._resolve_refs()
|
||||
self._modified = True
|
||||
|
||||
@@ -7,6 +7,7 @@ xml.etree.ElementTree from the Python standard library.
|
||||
import copy
|
||||
import uuid
|
||||
import xml.etree.ElementTree as ET
|
||||
from defusedxml.ElementTree import parse as _defused_parse
|
||||
from typing import Optional
|
||||
|
||||
# Global parent mapping because ET.Element has no getparent().
|
||||
@@ -51,7 +52,7 @@ def new_id(prefix: str = "producer") -> str:
|
||||
def parse_mlt(filepath: str) -> ET.Element:
|
||||
"""Parse an MLT XML file and return the root element."""
|
||||
_clear_parent_map()
|
||||
tree = ET.parse(filepath)
|
||||
tree = _defused_parse(filepath)
|
||||
root = tree.getroot()
|
||||
_register_tree(root)
|
||||
return root
|
||||
|
||||
@@ -36,6 +36,7 @@ setup(
|
||||
install_requires=[
|
||||
"click>=8.0.0",
|
||||
"prompt-toolkit>=3.0.0",
|
||||
"defusedxml>=0.7.1",
|
||||
],
|
||||
extras_require={
|
||||
"dev": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
from defusedxml.ElementTree import parse as _defused_parse
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -236,7 +237,7 @@ def list_styles(runtime: RuntimeContext) -> list[dict[str, Any]]:
|
||||
styles: list[dict[str, Any]] = []
|
||||
for path in sorted(styles_dir.glob("*.csl")):
|
||||
try:
|
||||
root = ET.parse(path).getroot()
|
||||
root = _defused_parse(path).getroot()
|
||||
except ET.ParseError:
|
||||
styles.append({"path": str(path), "id": None, "title": path.stem, "valid": False})
|
||||
continue
|
||||
|
||||
@@ -45,6 +45,7 @@ setup(
|
||||
install_requires=[
|
||||
"click>=8.0.0",
|
||||
"prompt-toolkit>=3.0.0",
|
||||
"defusedxml>=0.7.1",
|
||||
],
|
||||
extras_require={
|
||||
"dev": [
|
||||
|
||||
Reference in New Issue
Block a user