666ghj/MiroFish · error · StarHistoryError

generated SVG contains forbidden XML directives

Error message

generated SVG contains forbidden XML directives

What it means

Raised by `_validate_svg` (scripts/star_history.py:1083) when the payload contains XML processing directives: `<!DOCTYPE`, `<!ENTITY`, or `<?...` (e.g. `<?xml ...?>`). Because the SVG is embedded/published as a fixed, review-approved document, directives that could declare entities (XXE/billion-laughs vectors) or processing instructions are forbidden outright.

Source

Thrown at scripts/star_history.py:1083

                )
        return None
    return None


def _validate_svg(payload: bytes) -> None:
    try:
        decoded_payload = payload.decode("utf-8", errors="strict")
    except UnicodeDecodeError as exc:
        raise StarHistoryError("generated SVG must be strict UTF-8") from exc
    if decoded_payload.startswith("\ufeff") or "\x00" in decoded_payload:
        raise StarHistoryError("generated SVG must be canonical UTF-8")
    upper_payload = decoded_payload.upper()
    if (
        "<!DOCTYPE" in upper_payload
        or "<!ENTITY" in upper_payload
        or "<?" in decoded_payload
    ):
        raise StarHistoryError("generated SVG contains forbidden XML directives")
    try:
        root = ET.fromstring(decoded_payload)
    except ET.ParseError as exc:
        raise StarHistoryError("generated SVG is not valid XML") from exc
    svg_namespace = "{http://www.w3.org/2000/svg}"
    if root.tag != f"{svg_namespace}svg":
        raise StarHistoryError("generated SVG root is invalid")
    allowed_attributes: dict[str, set[str]] = {
        "svg": {
            "viewBox",
            "width",
            "height",
            "preserveAspectRatio",
            "role",
            "aria-labelledby",
        },
        "title": {"id"},
        "desc": {"id"},

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Remove the XML declaration / DOCTYPE / processing instructions from the generation pipeline — the canonical output starts directly with `<svg ...>`.
  2. If using ElementTree serialization, keep `xml_declaration` off: `ET.tostring(root, encoding="unicode")` (no declaration).
  3. Drop xmllint/pretty-print post-processing steps that prepend `<?xml version="1.0"?>`.
  4. If you must keep a declaration for some consumer, strip it only after `_validate_svg`: publish `b"<?xml version=...?>" + payload` outside the validated artifact.

Example fix

# before: serializer emits a declaration -> validator sees '<?'
svg_text = ET.tostring(root, encoding="utf-8", xml_declaration=True)

# after: no declaration; canonical payload
svg_text = ET.tostring(root, encoding="unicode").encode("utf-8")
Defensive patterns

Strategy: try-catch

Validate before calling

def has_forbidden_directives(payload: bytes) -> bool:
    up = payload.upper()
    return b"<!DOCTYPE" in up or b"<!ENTITY" in up or b"<?" in payload

assert not has_forbidden_directives(payload)

Try / catch

try:
    _validate_svg(payload)
except StarHistoryError as exc:
    if "forbidden XML directives" in str(exc):
        raise ValueError("strip XML declaration/DOCTYPE/PI before validation") from exc
    raise

Prevention

When it happens

Trigger: Any occurrence of the substrings `<!DOCTYPE` or `<!ENTITY` (case-insensitive via `upper_payload`) or `<?` anywhere in the decoded payload. Typical producers: XML serializers configured to emit an XML declaration, templates that start with `<?xml version="1.0"?>`, or a hand-edited SVG that gained a DOCTYPE.

Common situations: Regenerating the SVG with `xml.etree.ElementTree.tostring(xml_declaration=True)`; editors that auto-insert DOCTYPE declarations; forks adding an `<?xml-stylesheet?>` processing instruction; CI steps that pretty-print the SVG with xmllint (adds `<?xml?>`).

Understand the failure class

Related errors


AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14). Data as JSON: /api/errors/a4955e408d5a78b6. Report an issue: GitHub.