diff --git a/poetry.lock b/poetry.lock index 8891448..b455963 100644 --- a/poetry.lock +++ b/poetry.lock @@ -461,6 +461,17 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==46.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + [[package]] name = "desktop-entry-lib" version = "5.0" @@ -1182,4 +1193,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "17e97a5516576384aafd227385b42be9178527537a52ab44e8797816534b5193" +content-hash = "b9153226d96d26f633a7d95ba83b05e78a0063d4c5471b5e0d5f928a4cae0a57" diff --git a/pyproject.toml b/pyproject.toml index 977325b..01b192b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ repository = "https://git.mig5.net/mig5/jinjaturtle" python = "^3.10" PyYAML = "^6.0" tomli = { version = "^2.0.0", python = "<3.11" } +defusedxml = "^0.7.1" [tool.poetry.group.dev.dependencies] pytest = "^7.0" diff --git a/src/jinjaturtle/cli.py b/src/jinjaturtle/cli.py index 8158cf4..ce096c4 100644 --- a/src/jinjaturtle/cli.py +++ b/src/jinjaturtle/cli.py @@ -2,6 +2,7 @@ from __future__ import annotations import argparse import sys +from defusedxml import defuse_stdlib from pathlib import Path from .core import ( @@ -47,6 +48,7 @@ def _build_arg_parser() -> argparse.ArgumentParser: def _main(argv: list[str] | None = None) -> int: + defuse_stdlib() parser = _build_arg_parser() args = parser.parse_args(argv) diff --git a/src/jinjaturtle/core.py b/src/jinjaturtle/core.py index a4dce7e..753da81 100644 --- a/src/jinjaturtle/core.py +++ b/src/jinjaturtle/core.py @@ -2,7 +2,7 @@ from __future__ import annotations import configparser import json -import xml.etree.ElementTree as ET +import xml.etree.ElementTree as ET # nosec import yaml from collections import Counter, defaultdict @@ -103,8 +103,9 @@ def parse_config(path: Path, fmt: str | None = None) -> tuple[str, Any]: if fmt == "xml": text = path.read_text(encoding="utf-8") - parser = ET.XMLParser(target=ET.TreeBuilder(insert_comments=False)) - root = ET.fromstring(text, parser=parser) + # defusedxml.defuse_stdlib() is called in CLI entrypoint + parser = ET.XMLParser(target=ET.TreeBuilder(insert_comments=False)) # nosec + root = ET.fromstring(text, parser=parser) # nosec return fmt, root raise ValueError(f"Unsupported config format: {fmt}") @@ -868,8 +869,9 @@ def _generate_xml_template_from_text(role_prefix: str, text: str) -> str: prolog, body = _split_xml_prolog(text) # Parse with comments included so are preserved - parser = ET.XMLParser(target=ET.TreeBuilder(insert_comments=True)) - root = ET.fromstring(body, parser=parser) + # defusedxml.defuse_stdlib() is called in CLI entrypoint + parser = ET.XMLParser(target=ET.TreeBuilder(insert_comments=True)) # nosec + root = ET.fromstring(body, parser=parser) # nosec _apply_jinja_to_xml_tree(role_prefix, root)