{"record":{"id":"9c2515da863757d6","repo":"666ghj/MiroFish","slug":"generated-svg-contains-a-forbidden-element","errorCode":null,"errorMessage":"generated SVG contains a forbidden element","messagePattern":"generated SVG contains a forbidden element","errorType":"exception","errorClass":"StarHistoryError","httpStatus":null,"severity":"error","filePath":"scripts/star_history.py","lineNumber":1197,"sourceCode":"        \"baseFrequency\": {\"0.05\"},\n        \"scale\": {\"5\"},\n        \"fill-opacity\": {\"0.92\"},\n        \"stroke-linecap\": {\"round\"},\n        \"stroke-linejoin\": {\"round\"},\n        \"text-anchor\": {\"start\", \"middle\", \"end\"},\n        \"font-size\": {\"15\", \"16\", \"17\", \"20\"},\n        \"font-weight\": {\"700\"},\n        \"transform\": {\"rotate(-90 22 272)\"},\n    }\n    avatar_count = 0\n    watermark_count = 0\n    for element in root.iter():\n        if not isinstance(element.tag, str) or not element.tag.startswith(svg_namespace):\n            raise StarHistoryError(\"generated SVG contains a foreign namespace\")\n        local_name = element.tag[len(svg_namespace) :]\n        allowed_for_element = allowed_attributes.get(local_name)\n        if allowed_for_element is None:\n            raise StarHistoryError(\"generated SVG contains a forbidden element\")\n        if local_name == \"image\":\n            avatar_attributes = {\n                \"x\": \"316\",\n                \"y\": \"12\",\n                \"width\": \"22\",\n                \"height\": \"22\",\n                \"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","sourceCodeStart":1179,"sourceCodeEnd":1215,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/scripts/star_history.py#L1179-L1215","documentation":"Raised by `_validate_svg`'s element walk (scripts/star_history.py:1197) when an element's local name is not in the fixed allowlist `allowed_attributes` mapping (svg, title, desc, rect, path, text, image, ...). The chart is rendered from a reviewed template; any element outside that vocabulary means generated or injected content was never reviewed, so validation fails.","triggerScenarios":"Any SVG element whose local name is absent from the allowlist — e.g. `<script>`, `<foreignObject>`, `<use>`, `<style>`, `<animate>`, or legitimate-but-new elements introduced by fork code (e.g. adding `<circle>` for a new decoration) without extending the allowlist.","commonSituations":"Forks extending the chart with new shapes but forgetting to allowlist them; sanitizers/injectors that add `<style>` blocks; template edits that copy elements from other SVGs (defs/gradients/filters are not part of the reviewed set).","solutions":["If you intentionally added a new element type in a fork, add its local name plus its reviewed attributes to `allowed_attributes` and its constrained values to the exact-value table — keep the review discipline.","Remove `<style>`, `<script>`, filters, and gradients copied from external SVGs; the template expresses everything via reviewed attributes.","Run `_validate_svg(render_svg(...))` in CI after any template change so new elements fail the build instead of publishing.","Check for accidental pasted markup inside the template string (a stray `<g>` from a design tool is enough)."],"exampleFix":"# fork adding a reviewed <circle> decoration\n# before: template gains <circle cx=\"10\" .../> -> 'forbidden element'\n\n# after: extend the allowlist with reviewed attributes only\nallowed_attributes: dict[str, set[str]] = {\n    ...,\n    \"circle\": {\"cx\", \"cy\", \"r\", \"fill\", \"fill-opacity\"},\n}","handlingStrategy":"try-catch","validationCode":"ALLOWED_ELEMENTS = {\"svg\", \"title\", \"desc\", \"rect\", \"path\", \"text\", \"image\"}  # keep in sync with _validate_svg\n\ndef only_allowed_elements(payload: bytes) -> bool:\n    root = ET.fromstring(payload)\n    return all(el.tag.split('}')[-1] in ALLOWED_ELEMENTS for el in root.iter())","typeGuard":null,"tryCatchPattern":"try:\n    _validate_svg(payload)\nexcept StarHistoryError as exc:\n    if \"forbidden element\" in str(exc):\n        raise ValueError(\"template introduced an element outside the reviewed allowlist\") from exc\n    raise","preventionTips":["When forking, add new element names to allowed_attributes together with the template change in the same commit.","Forbid pasting markup from design tools (defs, filters, gradients, style) into the template.","Gate template changes with a CI check that renders and validates in one step."],"tags":["svg","allowlist","security","self-check","rendering"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}