{"record":{"id":"6e65556f59657648","repo":"666ghj/MiroFish","slug":"generated-svg-contains-a-forbidden-attribute","errorCode":null,"errorMessage":"generated SVG contains a forbidden attribute","messagePattern":"generated SVG contains a forbidden attribute","errorType":"exception","errorClass":"StarHistoryError","httpStatus":null,"severity":"error","filePath":"scripts/star_history.py","lineNumber":1222,"sourceCode":"                \"href\": OWNER_AVATAR_DATA_URI,\n                \"clip-path\": \"url(#clip-circle-title)\",\n            }\n            watermark_attributes = {\n                \"x\": \"635\",\n                \"y\": \"508.333\",\n                \"width\": \"20\",\n                \"height\": \"20\",\n                \"href\": WATERMARK_LOGO_DATA_URI,\n            }\n            if element.attrib == avatar_attributes:\n                avatar_count += 1\n            elif element.attrib == watermark_attributes:\n                watermark_count += 1\n            else:\n                raise StarHistoryError(\"generated SVG contains an unreviewed image\")\n        for raw_name, value in element.attrib.items():\n            if raw_name.startswith(\"{\") or raw_name not in allowed_for_element:\n                raise StarHistoryError(\"generated SVG contains a forbidden attribute\")\n            if (\n                not value.isascii()\n                or \"\\\\\" in value\n                or \"/*\" in value\n                or \"*/\" in value\n                or any(ord(character) < 0x20 for character in value)\n            ):\n                raise StarHistoryError(\"generated SVG contains an unsafe attribute value\")\n            exact_values = exact_attribute_values.get(raw_name)\n            if exact_values is not None and value not in exact_values:\n                raise StarHistoryError(\"generated SVG contains an unsafe attribute value\")\n            if raw_name == \"href\":\n                if local_name != \"image\" or value not in {\n                    OWNER_AVATAR_DATA_URI,\n                    WATERMARK_LOGO_DATA_URI,\n                }:\n                    raise StarHistoryError(\"generated SVG contains an external resource\")\n            lowered = value.lower().replace(\" \", \"\")","sourceCodeStart":1204,"sourceCodeEnd":1240,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/scripts/star_history.py#L1204-L1240","documentation":"Raised by `_validate_svg`'s attribute loop (scripts/star_history.py:1222) when an attribute name is namespaced (starts with `{`, e.g. `{http://www.w3.org/1999/xlink}href`) or is not in the per-element allowlist in `allowed_attributes`. The published SVG permits only reviewed, plain-named attributes on each element type — xlink-prefixed or novel attributes are rejected.","triggerScenarios":"Serializing with ElementTree while elements carry namespaced attributes (classic `{xlink}href` when code sets `\"{http://www.w3.org/1999/xlink}href\"`), fork code adding unlisted attributes like `opacity`, `class`, `id`, or `filter`, or attributes valid on one element used on another (e.g. `font-size` on `<rect>`).","commonSituations":"Forks porting markup that uses SVG 1.1 `xlink:href`; adding CSS hooks (`class`) for downstream styling; editors that attach `id`/`data-name` attributes to every node; using `href` on an element whose allowlist set lacks it.","solutions":["Use the SVG2 plain attribute names the template already uses — `href`, not `{xlink}href`.","In forks, add any new attribute to the target element's set in `allowed_attributes` (and to `exact_attribute_values` if it must be pinned), after review.","Avoid editor round-trips: Inkscape adds `id`/`data-*` everywhere and this check will fail.","Print `element.attrib` for the failing element (catch StarHistoryError around `_validate_svg` in a debug harness) to see exactly which name was rejected."],"exampleFix":"# before: namespaced attribute serializes as {ns}href\nlink.set(\"{http://www.w3.org/1999/xlink}href\", uri)\n\n# after: plain SVG2 attribute, already allowlisted\nlink.set(\"href\", uri)","handlingStrategy":"try-catch","validationCode":"def plain_attribute_names(payload: bytes) -> bool:\n    root = ET.fromstring(payload)\n    return not any(name.startswith(\"{\") for el in root.iter() for name in el.attrib)","typeGuard":null,"tryCatchPattern":"try:\n    _validate_svg(payload)\nexcept StarHistoryError as exc:\n    if \"forbidden attribute\" in str(exc):\n        raise ValueError(\"attribute is namespaced or not allowlisted for this element\") from exc\n    raise","preventionTips":["Use plain SVG2 attribute names (href, not xlink:href) in all fork code.","Extend the per-element allowlist (and exact-value pinning) whenever you add an attribute in a fork.","Skip editor round-trips — tools that inject id/class/data-* attributes will fail this check.","Debug by printing element.attrib for the failing element to identify the rejected name."],"tags":["svg","attributes","allowlist","security","self-check","xlink"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}