666ghj/MiroFish · critical · StarHistoryError

generated SVG contains an unsafe attribute value

Error message

generated SVG contains an unsafe attribute value

What it means

Raised by _validate_svg in scripts/star_history.py while auditing every attribute of the generated SVG. An attribute value failed the character-safety test: it must be pure ASCII, contain no backslash, no CSS comment delimiters (/* or */), and no control characters below 0x20. This is a hard invariant of the render pipeline: the SVG is published to the repo, so any value that could escape its attribute context (CSS injection, escapes, control bytes) aborts generation.

Source

Thrown at scripts/star_history.py:1230

                "href": WATERMARK_LOGO_DATA_URI,
            }
            if element.attrib == avatar_attributes:
                avatar_count += 1
            elif element.attrib == watermark_attributes:
                watermark_count += 1
            else:
                raise StarHistoryError("generated SVG contains an unreviewed image")
        for raw_name, value in element.attrib.items():
            if raw_name.startswith("{") or raw_name not in allowed_for_element:
                raise StarHistoryError("generated SVG contains a forbidden attribute")
            if (
                not value.isascii()
                or "\\" in value
                or "/*" in value
                or "*/" in value
                or any(ord(character) < 0x20 for character in value)
            ):
                raise StarHistoryError("generated SVG contains an unsafe attribute value")
            exact_values = exact_attribute_values.get(raw_name)
            if exact_values is not None and value not in exact_values:
                raise StarHistoryError("generated SVG contains an unsafe attribute value")
            if raw_name == "href":
                if local_name != "image" or value not in {
                    OWNER_AVATAR_DATA_URI,
                    WATERMARK_LOGO_DATA_URI,
                }:
                    raise StarHistoryError("generated SVG contains an external resource")
            lowered = value.lower().replace(" ", "")
            if "url(" in lowered and lowered not in {
                "url(#xkcdify)",
                "url(#clip-circle-title)",
            }:
                raise StarHistoryError("generated SVG contains an external resource")
            if lowered.startswith(("http:", "https:", "//")):
                raise StarHistoryError("generated SVG contains an external resource")
    if avatar_count != 1 or watermark_count != 1:

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Keep all dynamic/localized text in element text nodes (e.g. <text>...</text>), never in attributes; attributes in this pipeline are static layout values only.
  2. If you intentionally added a new attribute value, verify it is ASCII, backslash-free, and free of /* */ and control characters, then register it in the exact_attribute_values allowlist in _validate_svg (scripts/star_history.py:1149).
  3. Diff the two generated SVGs (static/image/star-history-light.svg / -dark.svg) against git HEAD to find which attribute changed since the last good render.
  4. Re-run the generator with the previous history.json to confirm the failure follows your template edit, not the data.

Example fix

// before: dynamic value placed in an attribute
element.set("aria-label", f"Stars for {owner_name}")  // non-ASCII owner name triggers error

// after: keep attributes static, put text in the node
element.text = f"Stars for {owner_name}"
element.set("role", "img")  // static, ASCII, allowlisted
Defensive patterns

Strategy: validation

Validate before calling

def svg_attribute_is_safe(value: str) -> bool:
    return (
        value.isascii()
        and "\\" not in value
        and "/*" not in value
        and "*/" not in value
        and all(ord(c) >= 0x20 for c in value)
    )

# before generating, check every attribute you plan to set
assert all(svg_attribute_is_safe(v) for v in my_dynamic_values)

Type guard

def is_safe_svg_attribute_value(value: object) -> TypeGuard[str]:
    if not isinstance(value, str):
        return False
    return (
        value.isascii()
        and "\\" not in value
        and "/*" not in value
        and "*/" not in value
        and all(ord(c) >= 0x20 for c in value)
    )

Try / catch

try:
    _write_outputs(root, state)
except StarHistoryError as exc:
    if "unsafe attribute value" in str(exc):
        # dump the offending attribute for triage, then fix the template/data
        log.error("SVG attribute safety failure: %s", exc)
    raise

Prevention

When it happens

Trigger: render_svg() produced an element attribute containing a non-ASCII character (e.g. a translated label or owner name leaked into an attribute instead of text content), a backslash, a CSS comment sequence, or a raw control byte (newline/tab inside an attribute value). Typical root cause: editing the SVG template or exact_attribute_values/fill palettes and passing dynamic data through an attribute.

Common situations: Contributors localizing titles or descriptions and putting Unicode text into attributes; templating code that string-interpolates unescaped repo metadata; an editor inserting escaped characters; modifying the xkcd theme colors to values with escape sequences.

Related errors


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