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
- 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.
- 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).
- 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.
- 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
- Keep all dynamic or localized content in element text nodes, never attributes.
- Run _validate_svg over rendered bytes in a unit test whenever the template changes.
- Add a CI step that regenerates and byte-compares both SVGs so drift is caught at PR time.
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
- generated SVG contains a forbidden element
- chart coordinates must be finite
- chart coordinates must be ordered
- unsupported SVG theme
- generated SVG must be strict UTF-8
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/7320e6203df77a07.
Report an issue: GitHub.