RFC 9839 y el Unicode problemático
(tbray.org)- RFC 9839 define con claridad los caracteres Unicode problemáticos que pueden aparecer en campos de texto durante el desarrollo de software
- Este RFC aborda los problemas causados por la falta de consistencia al procesar esos caracteres entre distintos lenguajes y bibliotecas
- En 9839 se proponen tres subconjuntos menos problemáticos que pueden usarse de forma opcional
- En comparación con el framework PRECIS existente, su aplicación es más fácil y simple
- También se publicó una biblioteca para Go de RFC 9839 para facilitar su uso en la práctica
Antecedentes y panorama general de RFC 9839
- Unicode se usa como estándar en casi todo el procesamiento de datos de texto
- Sin embargo, al diseñar estructuras de datos o protocolos reales, permitir todos los caracteres Unicode puede causar problemas
- Paul Hoffman y el autor presentaron un borrador individual ante la IETF para establecer criterios claros sobre problemas de Unicode que se repetían constantemente
- Tras dos años de discusiones, fue adoptado como estándar oficial y publicado como RFC 9839
- Este documento explica en detalle los tipos de caracteres problemáticos, por qué lo son (por razones técnicas y normativas), y tres subconjuntos entre los que el usuario puede elegir
Contenido principal de RFC 9839
- Es un documento de referencia obligatoria al diseñar campos de texto en entornos de software y redes
- RFC 9839 tiene 10 páginas, por lo que es relativamente conciso para un documento estándar de la IETF
- Está explicado de forma accesible, principalmente para desarrolladores de software e ingenieros de redes
Casos de caracteres Unicode problemáticos
- Por ejemplo, en el campo
usernamede JSON podría aparecer una cadena como la siguiente{ "username": "\u0000\u0089\uDEAD\uD9BF\uDFFF" } - Problemas de cada code point
U+0000: un carácter NULL sin significado que interfiere con el funcionamiento de algunos lenguajes de programaciónU+0089: un código de control C1 (CHARACTER TABULATION WITH JUSTIFICATION) cuyo comportamiento es complejo e inconsistenteU+DEAD: un carácter sustituto no emparejado, un problema derivado de las limitaciones de UTF-16. Genera datos poco deseables\uD9BF\uDFFF(U+7FFFFreal) : un Noncharacter, cuyo intercambio está prohibido por la norma
- Code points como estos provocan procesamiento inconsistente y errores inesperados dentro de estructuras de datos y protocolos
- RFC 9839 define oficialmente este tipo de caracteres problemáticos y establece con claridad qué categorías deben excluirse
Diseño y limitaciones de JSON
- No es responsabilidad de Doug Crockford, creador de JSON
- Se diseñó en una época en la que Unicode no estaba lo suficientemente maduro, por lo que no fue posible restringir estrictamente el conjunto de caracteres
- Como ya no es posible cambiar el estándar, se necesita un enfoque de exclusión empírica de caracteres problemáticos
Diferencias con el framework PRECIS de la IETF
- Incluso antes de RFC 9839 en 2025, la IETF ya ofrecía varios estándares como RFC 8264 (PRECIS Framework)
- Este framework trata en detalle cómo preparar, aplicar y comparar cadenas internacionalizadas
- Tiene 43 páginas y ofrece una explicación de fondo y soluciones muy completas
- PRECIS depende fuertemente de la versión de Unicode, y tiene la desventaja de ser complejo y difícil de aplicar
- RFC 9839 es conciso y está centrado en la practicidad, lo que facilita una adopción rápida al definir nuevos protocolos
Subconjuntos de RFC 9839 y ejemplos de uso
- 9839 presenta tres subconjuntos prácticos (
scalars,XML,assignables) - Cada subconjunto varía ligeramente en el rango de caracteres problemáticos que excluye
- A continuación, un resumen de cómo los principales formatos de datos y los subconjuntos de RFC 9839 manejan los caracteres problemáticos
- Algunos formatos como CBOR, TOML, XML, YAML excluyen parcialmente caracteres sustitutos o de control
- I-JSON excluye sustitutos y noncharacters
- JSON general y Protobufs no los excluyen
- XML y YAML solo excluyen parcialmente noncharacters/códigos de control por las características de su charset
- Nota: XML y YAML no excluyen noncharacters fuera del Basic Multilingual Pane
Biblioteca RFC 9839 para Go
- Se publicó una pequeña biblioteca de Go que permite validar caracteres según los tres subconjuntos de RFC 9839
- Ya fue suficientemente probada, aunque la optimización sigue en proceso
- Se agradecen pruebas y retroalimentación en entornos reales de trabajo
Importancia de RFC 9839 y proceso de elaboración
- RFC 9839 fue publicado oficialmente tras pasar por múltiples rondas de retroalimentación entre los coautores y más de 15 revisiones del borrador
- Gracias a la discusión y contribución de muchos expertos de la comunidad, evolucionó hasta convertirse en un documento mucho más sólido que la propuesta inicial
- Los colaboradores aparecen mencionados en la sección “Acknowledgements”
La experiencia de una presentación individual de RFC
- RFC 9839 se desarrolló como una presentación individual (individual submission)
- Implica más carga de trabajo y de procedimiento que el método tradicional a través de un Working Group
- Comparado con la experiencia de participar en un Working Group, el método tradicional resulta más eficiente y recomendable
1 comentarios
Opinión de Hacker News
Creo que está claro que ciertos caracteres causan problemas, pero me da la impresión de que el peor escenario es que los diseñadores de estructuras de datos o protocolos tiendan a no querer permitir arbitrariamente todo tipo de caracteres, incluso los correctamente escapados. Por ejemplo, creo que la validación de nombres de usuario debería manejarse en otra capa. Es decir, verificar que el nombre de usuario tenga menos de 60 caracteres, que no permita emoji ni caracteres zalgo, que no contenga bytes nulos, etc., y devolver un error apropiado desde la API. No quisiera que esto falle por ese tipo de problemas en la etapa de parseo de JSON en lugar de una validación previa. Desde luego, hay clases de caracteres claramente inapropiadas para un nombre de usuario. Pero si estoy enviando un archivo de texto que realmente usa tabulaciones y cosas así, esperaría que lo que pueda manejar el tipo
stringutf8 de mi lenguaje sea codificable. En particular, hay muchos usos para el byte nulo, y de hecho aparece con frecuencia en JSON. Aun así, si hay que usar solo un conjunto limitado de Unicode "normal", me parece mejor que exista un estándar a que cada quien se invente su miniestándar. En resumen, la idea en sí parece buena, pero no me convence mucho la lógica expuesta en la entrada del blogA estas alturas de 2025, creo que las únicas representaciones de cadenas en protocolos wire de bajo nivel que realmente se pueden defender son una de estas
Hablando en serio, preferiría que en archivos de texto plano no se usaran los caracteres C0 (excepto salto de línea y, a regañadientes, HT) ni los caracteres C1. Entiendo que alguien quiera guardar algo como marcado de color ANSI, pero en ese caso en realidad no es texto plano, sino algún tipo de formato de marcado de texto. Similar a Markdown, salvo que usa codificaciones del rango C0. No se puede decir que algo es texto plano solo porque se ve bonito con el comando
caty similares. Reconozco que hay muchos formatos de marcado codificados como texto plano por razones de interoperabilidadCreo que la idea misma de que lo peor es empezar a prohibir conjuntos arbitrarios de caracteres en estructuras de datos y protocolos está desconectada de la realidad. Lo verdaderamente peor es que defectos de software, como los parsers, terminen provocando una brecha de seguridad
Me pregunto si existe algún sistema que permita UTF-8 en nombres de usuario. Es obvio que cualquier identificador que se manipule o evalúe programáticamente (nombre de usuario de inicio de sesión, contraseña, etc.) debe ser ASCII. Ni ISO-8859-1 ni nada de eso: solo ASCII. Unicode no es adecuado para esos usos. Tal vez no importe si solo se muestra el nombre de usuario, pero como identificador para iniciar sesión en un sistema, las codificaciones no ASCII deberían prohibirse sin excepción. Ni siquiera el software del teclado puede garantizar siempre la consistencia visual de UTF-8 una vez que sales de ASCII, y según el sistema operativo y la configuración todo se vuelve más confuso. Tampoco hay garantía de que los binarios que queden y una IA que interprete Unicode en el futuro coincidan. Además, en cuanto a consistencia, ni el RFC 9839 ni el artículo dejan claro si casos como IVS o la normalización (NFC/NFD/NFKC/NFKD) están claramente dentro o fuera del alcance. Parece que falta por completo una sección de propósito. Solo hay menciones vagas, como eso de que existen "puntos de código no caracteres"
Me pregunto por qué habría que prohibir emoji en los nombres de usuario
Quisiera señalar que la IETF no esperó hasta 2025 para abordar Bad Unicode. Desde antes ya trataba ampliamente varios de estos problemas en RFC 8264: PRECIS Framework. También ayuda revisar RFC relacionados como 8265(enlace), 8266(enlace), etc. En general, cosas como contraseñas que pueden cambiar la dirección del texto o codificarse de forma distinta según el dispositivo de entrada no deberían usarse en nombres de usuario/contraseñas. Estos perfiles RFC permiten tratarlo de forma segura. Para estos fines, "failing closed" (bloquear de forma más estricta) es más seguro. Aunque aparezcan nuevos emoji, prefiero prohibirlos y ser conservador antes que permitirlos en nombres de usuario y afectar todas las páginas
Unicode claramente tiene partes "buenas", pero es decepcionante tener que saber que hay caracteres que deben excluirse de forma excepcional. Es el resultado de intentar abarcar de forma exhaustiva las maneras de escribir los idiomas y haberse vuelto demasiado complejo. Cansa tener que pensar siempre en qué caracteres requieren trato especial. Por eso trato las cadenas Unicode como una unidad de datos independiente. Las recibo, almaceno, renderizo y comparo por igualdad de datos, pero no intento interpretar su contenido. Incluso concatenarlas o manipularlas me da desconfianza
Unicode parece un abismo interminable de trivia y malas decisiones. Por ejemplo, los RFC relacionados advierten sobre caracteres de control ASCII obsoletos (por riesgo de confusión visual), pero no mencionan en absoluto los caracteres de cambio direccional con problemas graves de seguridad, como Explicit Directional Overrides
Como ejemplo simple, ya hay un problema si la primera cadena termina con un modificador de emoji huérfano y la segunda comienza con un emoji modificable. Mientras más casos complejos se suman, peor se vuelve el problema
La complejidad es grande, pero cosas como los surrogates y los códigos de control no están ahí para registrar idiomas, sino como resultado de diseños extraños preservados por compatibilidad histórica
Unicode es incómodo, pero me parece menos incómodo que otros estándares de codificación anteriores
Creo que la mayoría de los problemas se resuelven rechazando secuencias de bytes UTF-8 inválidas o devolviendo error en general. Por ejemplo, los surrogates son ilegales en UTF-8 por definición, así que un lenguaje que use utf-8 debería devolver error ante ese tipo de secuencias. Lo que realmente da problemas, en mi opinión, son ciertos "puntos de código" problemáticos (no imprimibles, etc.). Eso conviene tratarlo como un concepto claramente separado de las secuencias de bytes ilegales
Unicode ya define categorías para cada punto de código (General Category) para clasificar tipos de caracteres raros. Se puede consultar este artículo de Wikipedia. Por ejemplo, en Python
unicodedata.category(chr(0))devuelve "Cc" (control), yunicodedata.category(chr(0xdead))devuelve "Cs" (surrogate)Me parece excesivo excluir todos los "legacy control" no solo como literales sino también en cadenas escapadas (p. ej., "\u0027"). C1 casi no se usa, así que da igual, pero algunos caracteres C0 sí tienen ejemplos reales de uso. escape, EOF, NUL y otros todavía tienen usos claros
Creo que algunos caracteres C0 algo peculiares (como U+001E Record Separator) son muy útiles en flujos de datos. Tal vez puedas bloquearlos en documentos, pero para datos en streaming sí sirven
He visto el carácter form feed (U+000C) en código fuente de programas. Emacs lo soporta desde hace tiempo para navegación por páginas, así que a veces aparece incluido
No creo que Unicode sea bueno. Sea cual sea el conjunto de caracteres, al final cada aplicación tiene que decidir qué tipos de caracteres realmente va a usar (caracteres de control, gráficos, longitud máxima, etc.). Intentar incluir o excluir eso desde JSON y similares no sirve de mucho. Ya sea Unicode, ASCII u otro juego de caracteres, a veces puede ser útil ponerle nombre a cierto subconjunto (o superconjunto), pero no hay que engañarse pensando que es una buena elección para todo el mundo. RFC 9839 les pone nombre a algunos subconjuntos de Unicode, pero eso no garantiza que sean correctos para el servicio que yo haga. Mi conclusión es que incluso habría que considerar no usar Unicode en absoluto o no imponerlo
Estoy pensando si conviene controlar la entrada o envolverla en un tipo de dato que permita una salida segura para entradas no confiables (para web+log+debug)
Ojalá el estándar tuviera un límite para la cantidad de valores escalares Unicode que pueden entrar en una unidad gráfica. La última vez que lo vi (aunque fue hace años), el estándar no tenía ese límite; en cambio, solo recomendaba que las aplicaciones de streaming limitaran una unidad gráfica a 128 bytes. Si ese límite se definiera claramente en el estándar, la implementación sería mucho más sencilla y no impondría restricciones innecesarias
De hecho, ya me topé con casos donde un programa se rompía por asumir simplemente que "no hay caracteres de control" (form feed para separar páginas, el carácter escape para terminales, etc.). También se rompe la suposición de que "todo es UTF-8" (archivos de datos viejos, logs, etc.). Si no vas a hacer un procesamiento significativo como texto, lo mejor es pasar el contenido tal cual, como una secuencia de bytes, sin modificarlo. Pero por culpa de Microsoft Windows, a veces no queda otra que pasar una secuencia de
char16_t. UTF-16 es fundamentalmente distinto de UTF-8 en entrada y salida. Al convertir, hay que usar WTF-8 (UTF-16) y surrogate escape (UTF-8) respectivamente al pasar de datos externos a la forma interna. No se pueden mezclar ambos métodos.