Seguridad en interfaces React | Nicolás Garzón
TypeScript
Copiar < p> { comment. body} < / p> Esto reduce XSS accidental, pero no protege todos los sinks.
dangerouslySetInnerHTML exige contenido sanitizado con una política confiable.
TypeScript
Copiar < div dangerouslySetInnerHTML= { { __html: sanitizedHtml } } / > No intentes sanitizar con expresiones regulares.
Valida protocolos y destinos antes de usar datos externos en href, src o redirecciones.
Todo código y variable incluida en el bundle puede inspeccionarse. Las credenciales privadas pertenecen al servidor.
Ocultar un botón o una ruta no impide llamar una API. El backend valida identidad, rol y acceso al recurso.
Evita decisiones automáticas sobre almacenamiento sin analizar XSS, CSRF, expiración y arquitectura. Cookies seguras y sesiones requieren configuración del servidor.
Mantén paquetes actualizados y reduce librerías innecesarias. Un componente de terceros ejecuta código dentro de tu aplicación.
Confiar en validación del cliente.
Renderizar HTML sin sanitizar.
Incluir claves privadas en variables públicas.
Mostrar controles según rol y asumir que eso autoriza.
React ayuda a representar texto de forma segura, pero la seguridad real atraviesa cliente, servidor, datos y despliegue.
Texto
Copiar usuario / URL / storage / API
↓ entrada no confiable
validación y normalización
↓
UI React
↓ requests
servidor autoriza y persisteReact protege algunos sinks de texto, pero la seguridad depende de cada frontera.
TypeScript
Copiar < p> { userInput} < / p> React crea texto, no interpreta tags. Esta protección no aplica cuando el valor entra en otros contextos:
HTML explícito.
URL.
CSS generado.
srcdoc o embeds.
APIs de terceros.
Código ejecutable.
Escapar para HTML no valida una URL ni una consulta del servidor.
Texto
Copiar contenido no confiable
↓ parser/sanitizer mantenido
política de tags y atributos
↓
HTML sanitizado
↓ renderSanitiza lo más cerca posible del límite y evita volver a concatenar HTML después. Considera Trusted Types y Content Security Policy en aplicaciones de alto riesgo.
Scripts.
Event handler attributes.
Iframes arbitrarios.
URLs peligrosas.
Styles que exfiltren o oculten UI.
TypeScript
Copiar function safeExternalUrl ( value: string ) : URL | null {
try {
const url = new URL ( value) ;
return [ "https:" , "http:" ] . includes ( url. protocol) ? url : null ;
} catch {
return null ;
}
} La política depende del caso. Para imágenes quizá solo HTTPS y dominios permitidos. Para redirects internos, usa rutas relativas o allowlist; evita open redirects.
En enlaces externos con nueva pestaña, revisa rel="noopener noreferrer" según navegadores y política.
Autenticación: quién es.
Autorización: puede ejecutar esta operación sobre este recurso.
TypeScript
Copiar if ( ! canEditProject ( session. user, project) ) {
throw new ForbiddenError ( ) ;
} Ocultar un botón no autoriza. Cada loader, API, Action o Server Function valida ownership y rol.
No envíes un campo solo porque “no se muestra”. Props, RSC payloads, JSON, cache y source pueden inspeccionarse. Filtra en servidor el DTO permitido:
TypeScript
Copiar return {
id: user. id,
displayName: user. displayName,
} ; No serialices hashes, secretos, permisos internos completos o datos de otros tenants.
Cualquier variable incorporada al bundle público es visible. Los prefijos como VITE_ o NEXT_PUBLIC_ significan exposición, no cifrado. Secretos permanecen en runtime servidor.
No existe una recomendación universal de storage. Evalúa:
XSS.
CSRF.
Expiración y rotación.
Revocación.
SameSite.
HttpOnly/Secure.
Arquitectura API y dominios.
Una cookie HttpOnly reduce lectura desde JavaScript, pero necesita estrategia CSRF. localStorage evita envío automático, pero un XSS puede leerlo. Diseña con especialistas y threat model.
Para mutaciones con cookies:
Tokens anti-CSRF o verificación de origen.
SameSite adecuado.
Métodos correctos.
Reautenticación para acciones sensibles.
Idempotencia.
Validación servidor.
Framework Actions siguen siendo endpoints y requieren estas protecciones.
Aunque React escape texto, un atacante puede explotar:
Librería que usa innerHTML.
Markdown mal sanitizado.
URL javascript: en una integración.
DOM manipulation externa.
Script de terceros comprometido.
Dependencia vulnerable.
Inventaría todos los sinks, no solo JSX.
Revisa mantenedores y actividad.
Bloquea versiones con lockfile.
Audita vulnerabilidades.
Prueba updates.
Evita paquetes para funciones triviales.
Revisa install scripts.
Usa provenance cuando esté disponible.
Un audit automático no demuestra ausencia de vulnerabilidades.
Una plataforma puede añadir:
Content-Security-Policy.
Strict-Transport-Security.
X-Content-Type-Options.
Referrer-Policy.
Permissions-Policy.
Frame ancestors.
La configuración depende de assets, SSR y terceros. CSP con nonces/hashes puede requerir integración del framework.
Nunca confíes únicamente en businessId enviado por cliente. Deriva tenant permitido desde sesión y valida cada consulta:
TypeScript
Copiar repository. findOrder ( {
orderId,
businessId: session. businessId,
} ) ; Constraints y políticas de base de datos pueden añadir defensa en profundidad.
No muestres stack traces ni respuestas crudas. No registres:
Contraseñas.
Tokens.
Cookies.
Tarjetas.
Payloads personales completos.
Usa IDs de correlación y mensajes sanitizados.
Tamaño.
Tipo real, no solo extensión.
Nombre generado.
Ubicación aislada.
Antivirus cuando aplica.
Autorización de lectura.
No renderices SVG o HTML de usuarios bajo el mismo origen sin políticas estrictas.
Tests de autorización por rol y ownership.
Inputs maliciosos para HTML/URL.
CSRF en mutaciones.
Dependencias y headers en CI.
Revisión manual de flujos críticos.
DAST/SAST según riesgo.
Una prueba de componente no cubre seguridad servidor.
React escapa texto, no todos los contextos.
Cliente nunca es autoridad de permisos ni validación.
Filtra datos antes de enviarlos.
Sessions, CSRF y XSS requieren threat model conjunto.
Dependencias, headers, logging y despliegue forman parte de la seguridad.
¿Por qué validar una URL es distinto de escapar texto?
¿Qué diferencia existe entre esconder un botón y autorizar?
¿Por qué una variable pública no guarda secretos?
¿Qué riesgo tiene renderizar SVG subido por usuarios?
Ver respuestas
Porque una URL tiene protocolos y destinos con reglas propias.
El cliente solo cambia UX; el servidor decide acceso real.
Porque su valor termina en archivos o payloads inspeccionables.
Puede contener contenido activo o scripts según cómo se sirva.
Portals, modales y capas de interfaz aplica seguridad y accesibilidad a contenido que escapa de su contenedor DOM.