666ghj/MiroFish · critical · StarHistoryError
generated SVG contains an external resource
Error message
generated SVG contains an external resource
What it means
Raised by _validate_svg when an href attribute is present but the element is not an <image>, or its value is not one of the two pinned data URIs (OWNER_AVATAR_DATA_URI / WATERMARK_LOGO_DATA_URI). The published SVG must be fully self-contained: no external fetches, and the only embedded images are the byte-reviewed avatar and watermark.
Source
Thrown at scripts/star_history.py:1239
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:
raise StarHistoryError("generated SVG must contain both reviewed images")
try:
avatar = base64.b64decode(OWNER_AVATAR_BASE64, validate=True)
except ValueError as exc:
raise StarHistoryError("reviewed avatar data is invalid") from exc
if (
len(avatar) > MAX_INLINE_AVATAR_BYTES
or hashlib.sha256(avatar).hexdigest() != OWNER_AVATAR_SHA256
or _reviewed_avatar_dimensions(avatar) != OWNER_AVATAR_DIMENSIONSView on GitHub (pinned to b5b53acc57)
Solutions
- Locate every href in the generated SVG (grep 'href=' static/image/star-history-*.svg) and confirm only the two <image> elements carry it.
- If replacing the avatar or watermark image, update the whole constant set together: OWNER_AVATAR_BASE64, OWNER_AVATAR_SHA256, OWNER_AVATAR_DIMENSIONS (or WATERMARK_LOGO_*), since the data URI is derived from the base64 constant.
- Remove <a>/external-link wrappers; this design intentionally forbids links inside the published SVG.
- If you need a new embedded image, extend the avatar/watermark pattern: pinned data URI + count check + hash check, never a bare allowlist entry.
Example fix
# before: external link added to the chart <a href="https://github.com/owner/repo">...</a> # after: keep the SVG link-free; put links in the README that embeds the SVG # (no href elements other than the two reviewed <image> data URIs)
Defensive patterns
Strategy: validation
Validate before calling
def only_reviewed_hrefs(root) -> bool:
for el in root.iter():
href = el.get("href")
if href is not None:
if not el.tag.endswith("}image"):
return False
if href not in {OWNER_AVATAR_DATA_URI, WATERMARK_LOGO_DATA_URI}:
return False
return True Try / catch
try:
_validate_svg(svg_bytes)
except StarHistoryError as exc:
if "external resource" in str(exc):
# grep the SVG for href=/xlink:href and confirm only the two data URIs remain
... Prevention
- Embed images as pinned base64 data URIs; never reference URLs in the SVG.
- When swapping avatar/watermark assets, regenerate the DATA_URI constants from the same bytes as the SHA-256 pins.
- Exclude the generated SVGs from optimizers that externalize assets.
When it happens
Trigger: Adding any href (e.g. on <a>, <use>, <tspan>) other than on the two blessed <image> elements; changing the avatar/watermark image without regenerating the pinned DATA_URI constants; a template edit that swaps href for xlink:href-style external URLs; embedding a different logo PNG.
Common situations: Rebranding: someone replaces static assets or the owner avatar but only edits the SVG template, not OWNER_AVATAR_BASE64/SHA256 constants; adding a clickable link (<a href=...>) around the chart; pulling in an icon set that references external sprites.
Related errors
- generated SVG contains forbidden XML directives
- generated SVG contains a foreign namespace
- generated SVG contains a forbidden element
- generated SVG contains an unreviewed image
- generated SVG contains a forbidden attribute
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/5dfff3b6a85fd107.
Report an issue: GitHub.