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
7 changes: 6 additions & 1 deletion collector/jq_collector/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@
import zipfile
from datetime import datetime
from typing import Any
from xml.etree import ElementTree

import httpx

# coverage.xml comes out of another repo's CI artifact, so it is untrusted
# input. defusedxml refuses entity declarations and external references, and
# raises a ValueError subclass for them, which coverage_percent already treats
# as a malformed report rather than a failed refresh.
from defusedxml import ElementTree

from .config import Config
from .forge import GOOD_CONCLUSIONS, INCONCLUSIVE_CONCLUSIONS
from .state import MergedPull, PullRequest, RemoteRepo, WorkflowRun
Expand Down
3 changes: 2 additions & 1 deletion collector/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dependencies = [
"prometheus-client>=0.20",
"httpx>=0.27",
"PyYAML>=6.0",
"defusedxml>=0.7",
]

[build-system]
Expand All @@ -17,7 +18,7 @@ build-backend = "hatchling.build"
packages = ["jq_collector"]

[dependency-groups]
dev = ["pytest>=8.0", "pytest-cov>=5.0", "ruff>=0.16", "mypy>=1.10", "types-PyYAML>=6.0"]
dev = ["pytest>=8.0", "pytest-cov>=5.0", "ruff>=0.16", "mypy>=1.10", "types-PyYAML>=6.0", "types-defusedxml>=0.7"]

[tool.pytest.ini_options]
# The package too, for its doctests: an example in a docstring is a claim,
Expand Down
6 changes: 5 additions & 1 deletion collector/tests/test_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,12 @@ def test_an_expired_artifact_download_is_survivable(make_client):
zipped("<coverage/>"), # no line-rate
zipped("not xml", "coverage.xml"), # unparseable
zipped(REPORT, "something-else.txt"), # no coverage.xml inside
# Well-formed, and the stdlib parser would read it as 100%: the entity
# expands to a valid rate. Entities are how an XML bomb is built, so a
# report that declares one is refused rather than expanded.
zipped('<!DOCTYPE c [<!ENTITY r "1">]><coverage line-rate="&r;"/>'),
],
ids=["not-a-zip", "no-line-rate", "bad-xml", "no-coverage-xml"],
ids=["not-a-zip", "no-line-rate", "bad-xml", "no-coverage-xml", "declares-an-entity"],
)
def test_a_malformed_report_is_ci_s_problem_not_a_failed_refresh(make_client, blob):
"""Everything else in the refresh has already been gathered by this point."""
Expand Down
22 changes: 22 additions & 0 deletions collector/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading