|
4 | 4 | Parses TRX (results) + Cobertura (coverage) + Structured StdOut (HTTP, assertions, context) |
5 | 5 | into a single interactive HTML report. |
6 | 6 |
|
7 | | -SECURITY ENHANCEMENTS: |
| 7 | +SECURITY FEATURES: |
8 | 8 | - Uses defusedxml for secure XML parsing to prevent XXE attacks |
9 | | -- Robust path traversal prevention for all file operations |
| 9 | +- Robust path traversal prevention for all file operations |
10 | 10 | - Input validation and sanitization for all user-provided paths |
11 | | -- Safe handling of external entity resolution in XML processing |
| 11 | +- No insecure XML parsing fallbacks (security-first approach) |
12 | 12 |
|
13 | | -Dependencies: |
14 | | -- defusedxml (optional but recommended for security) |
15 | | -- Python 3.7+ for optimal security features |
| 13 | +Requirements: |
| 14 | +- defusedxml>=0.7.1 (REQUIRED for secure XML parsing) |
| 15 | +- Python 3.7+ recommended |
16 | 16 | """ |
17 | 17 |
|
18 | 18 | import xml.etree.ElementTree as ET |
|
23 | 23 | import argparse |
24 | 24 | from datetime import datetime |
25 | 25 |
|
26 | | -# Try to import defusedxml for safer XML parsing |
| 26 | +# Import defusedxml for secure XML parsing (required dependency) |
27 | 27 | try: |
28 | 28 | import defusedxml.ElementTree as SafeET |
29 | 29 | DEFUSED_XML_AVAILABLE = True |
30 | | -except ImportError: |
31 | | - SafeET = None |
32 | | - DEFUSED_XML_AVAILABLE = False |
| 30 | +except ImportError as e: |
| 31 | + print("ERROR: defusedxml is required for secure XML parsing.") |
| 32 | + print("Install it with: pip install defusedxml") |
| 33 | + print("This prevents XXE vulnerabilities in XML processing.") |
| 34 | + sys.exit(1) |
33 | 35 |
|
34 | 36 |
|
35 | | -def _make_xml_parser(): |
36 | | - """ |
37 | | - Create a hardened XML parser that prevents XXE and other XML-based attacks. |
38 | | - Uses defusedxml for safer XML parsing when available. |
39 | | - """ |
40 | | - if DEFUSED_XML_AVAILABLE: |
41 | | - return None # defusedxml uses its own parser |
42 | | - |
43 | | - # Fallback to standard parser with security restrictions |
44 | | - parser = ET.XMLParser() |
45 | | - |
46 | | - # For Python 3.8+, disable resolve_entities |
47 | | - if sys.version_info >= (3, 8): |
48 | | - try: |
49 | | - parser = ET.XMLParser(resolve_entities=False) |
50 | | - except TypeError: |
51 | | - pass |
52 | | - |
53 | | - # Additional hardening for older versions |
54 | | - if hasattr(parser, 'parser'): |
55 | | - try: |
56 | | - # Disable external entity processing |
57 | | - parser.parser.DefaultHandler = lambda data: None |
58 | | - parser.parser.ExternalEntityRefHandler = lambda *args: False |
59 | | - parser.parser.EntityDeclHandler = lambda *args: False |
60 | | - except AttributeError: |
61 | | - pass |
62 | | - |
63 | | - return parser |
64 | 37 |
|
65 | 38 |
|
66 | 39 | def _sanitize_output_path(output_path): |
@@ -133,15 +106,9 @@ def __init__(self, trx_path, coverage_path=None): |
133 | 106 | # ──────────────────── TRX PARSING ──────────────────── |
134 | 107 |
|
135 | 108 | def parse_trx(self): |
136 | | - # Safely parse TRX file with defusedxml when available |
| 109 | + # Safely parse TRX file with defusedxml (required for security) |
137 | 110 | try: |
138 | | - if DEFUSED_XML_AVAILABLE: |
139 | | - tree = SafeET.parse(self.trx_path) |
140 | | - else: |
141 | | - # Warn about potential security risk |
142 | | - print("Warning: defusedxml not available. Using standard XML parser with limited security mitigations.") |
143 | | - parser = _make_xml_parser() |
144 | | - tree = ET.parse(self.trx_path, parser=parser) |
| 111 | + tree = SafeET.parse(self.trx_path) |
145 | 112 | root = tree.getroot() |
146 | 113 | except Exception as e: |
147 | 114 | raise ValueError(f"Failed to parse TRX file safely: {e}") from e |
@@ -259,12 +226,8 @@ def parse_coverage(self): |
259 | 226 | if not self.coverage_path or not os.path.exists(self.coverage_path): |
260 | 227 | return |
261 | 228 | try: |
262 | | - # Safely parse coverage file with defusedxml when available |
263 | | - if DEFUSED_XML_AVAILABLE: |
264 | | - tree = SafeET.parse(self.coverage_path) |
265 | | - else: |
266 | | - parser = _make_xml_parser() |
267 | | - tree = ET.parse(self.coverage_path, parser=parser) |
| 229 | + # Safely parse coverage file with defusedxml (required for security) |
| 230 | + tree = SafeET.parse(self.coverage_path) |
268 | 231 | root = tree.getroot() |
269 | 232 | self.coverage['lines_pct'] = float(root.get('line-rate', 0)) * 100 |
270 | 233 | self.coverage['branches_pct'] = float(root.get('branch-rate', 0)) * 100 |
|
0 commit comments