Descripción
Módulo: racks/utils/svg_sanitizer.py
Sanitizador de SVG con estrategia allowlist usando defusedxml.ElementTree, integrado en el flujo de importación de stencils. Elimina vectores de XSS en SVG subido por usuario, generado por conversión Visio, o manipulado en requests.
Funciones públicas
sanitize_svg(svg_text: str) → str
Recibe string SVG, devuelve versión limpia. Lanza ValueError si no es SVG válido.
Lógica:
- Parse defensivo con
defusedxml.ElementTree(bloquea billion-laughs, external entities) - Verifica raíz es
<svg> - Limpia atributos peligrosos (recursivo DFS)
- Re-serializa con namespaces registrados
Elementos permitidos (21 elementos):
- Geometría:
path,rect,circle,ellipse,line,polyline,polygon - Texto:
text,tspan,textPath - Contenedor:
g,defs,symbol,use - Gradientes/pattern:
linearGradient,radialGradient,stop,pattern - Clipping/masking:
clipPath,mask - Otros:
marker,image,switch,title,desc
Atributos bloqueados (eliminados sin excepción):
- Handlers:
on*(onload, onclick, etc.) - Valores con
javascript:scheme href/xlink:hrefexterno en<use>(solo#fragmentpermitido)hrefno-raster en<image>(solodata:image/*permitido)styleinline conurl(...)no-interno
sanitize_svg_file(path: str) → None
Limpia un fichero SVG in-place. Lanza ValueError si no es SVG válido.
Uso:
try:
sanitize_svg_file("/path/to/stencil.svg")
except ValueError as e:
# Fichero no es SVG válido
logger.error("Invalid SVG: %s", e)
Puntos de aplicación
- Subida directa de SVG (
POST /api/racks/visio/analyze): SVG de usuario entra crudo → sanitizar antes del preview - SVG generado por libvisio-ng (task
convert_visio_async): cada página convertida → sanitizar antes de quedar disponible - Gate final en confirm (
POST /api/racks/visio/confirm): second pass, cubre sesiones manipuladas
Defensa en profundidad: si la lógica aguas arriba fallara, confirm atrapa XSS residual.
Detalles de implementación
Localización de nombres de tags/atributos
- Soporta namespaces SVG (
http://www.w3.org/2000/svg) y XLink (http://www.w3.org/1999/xlink) - Tags con
{namespace}localnamese extraen correctamente
Bloqueo de referencias externas
Regex: url\s*\(\s*['\"]?\s*(?!#) — detecta url(http://...), url(/path), etc. pero permite url(#interno)
Recursos embebidos legítimos
<image href="data:image/png;base64,...">— permitido, frecuente en stencils de fabricante (Cisco, APC embeben rasters así)<use href="#local-symbol">— fragmentos internos OK
Testing
Cubierto en tests/api/test_visio_import_v2.py:
- Sanitización de SVG con múltiples vectores (script, foreignObject, handlers, javascript:, external URLs)
- Preservación de geometría y recursos legítimos (paths, text, embedded rasters)
- Validación de reformat (no-SVG, XML inválido rechazados con ValueError)
- Bloqueo de
<use href="http://..."> - Preservación de
<use href="#interno">
Véase también
- [[feature—racks—import-visio-v2]]
- [[entity—racks—service—convert-visio-async]]
- [[decision—20260713—visio-confirm-hardening]]
- [[concept—saas—security]]
Referenciado desde
- Decisión de seguridad: hardening del confirm Visio (s220) — 3 fixes
- Endpoint: POST /api/racks/visio/analyze — análisis y conversión de stencils Visio
- Endpoint: POST /api/racks/visio/confirm — persistencia de stencils importados
- Import Visio v2: motor asíncrono .vsdx/.vsd → SVG + sanitización
- Task Huey: Conversión asíncrona .vsdx/.vsd → SVG