typing.LiteralString representa cadenas consideradas literales o derivadas únicamente de otras cadenas literales. Ayuda a APIs sensibles a distinguir texto definido por desarrolladores de contenido dinámico recibido de usuarios, archivos, redes, configuración o bases de datos. Esta distinción puede reducir riesgos de inyección en SQL, comandos, templates y formatos, pero no sustituye parametrización, escaping, validación, autorización ni privilegios mínimos.
Esta guía explica inferencia, operaciones con strings, parámetros SQL, identificadores dinámicos, shell, templates, builders, cast, límites de taint analysis, stubs, compatibilidad, pruebas y seguridad por capas.
El problema de strings dinámicas
def ejecutar_sql(query: str) -> None:
...
nombre = input("Nombre: ")
ejecutar_sql(f"SELECT * FROM usuarios WHERE nombre = '{nombre}'")La firma acepta cualquier string. El analizador no distingue una consulta fija de una construida con entrada externa. El ejemplo de runtime es vulnerable a inyección.
Declarar LiteralString
from typing import LiteralString
def ejecutar_sql(query: LiteralString) -> None:
...
ejecutar_sql("SELECT * FROM usuarios")Una cadena escrita directamente en el código es compatible. Una variable general de tipo str normalmente no lo es.
La entrada externa debe rechazarse
query: str = input("SQL: ")
ejecutar_sql(query) # error estático esperadoEl analizador informa que una string arbitraria no cumple el contrato confiable. Esto crea una barrera útil en revisión y CI.
LiteralString es subtipo de str
Una LiteralString puede usarse donde se acepta str. Lo contrario no es válido. Mostrar, registrar y procesar texto normalmente sigue funcionando.
Concatenar literales
prefijo: LiteralString = "SELECT id, nombre "
sufijo: LiteralString = "FROM usuarios"
query = prefijo + sufijo
ejecutar_sql(query)Las operaciones que combinan solo LiteralStrings pueden conservar la confianza. La inferencia exacta depende del analizador.
f-strings
tabla: LiteralString = "usuarios"
query = f"SELECT * FROM {tabla}"Una f-string puede seguir siendo LiteralString cuando todas las partes interpoladas son confiables. Si alguna expresión es solo str, la confianza debería perderse.
No interpoles datos SQL
Incluso con LiteralString, separa estructura y valores:
def consultar(sql: LiteralString, parametros: tuple[object, ...]) -> None:
...
nombre = input("Nombre: ")
consultar(
"SELECT * FROM usuarios WHERE nombre = ?",
(nombre,),
)La consulta es literal y el dato externo se envía por separado. El driver maneja encoding y escaping. La parametrización es la defensa principal.
Nombres de tabla dinámicos
Los drivers suelen parametrizar valores, no identificadores. Selecciona identificadores desde un conjunto cerrado:
from typing import Literal
Tabla = Literal["usuarios", "pedidos"]
def nombre_tabla(tabla: Tabla) -> LiteralString:
if tabla == "usuarios":
return "usuarios"
return "pedidos"No uses cast sobre entrada arbitraria. Valida con allowlists y devuelve literales conocidos.
Comandos de shell
def ejecutar_comando(comando: LiteralString) -> None:
...El tipo puede desalentar comandos construidos con entrada externa, pero el diseño más seguro evita shell=True y pasa una lista:
subprocess.run(["git", "show", revision], check=True)LiteralString no vuelve seguro el parsing del shell.
Templates HTML
def renderizar(template: LiteralString, contexto: dict[str, object]) -> str:
...El renderer puede exigir un template controlado por desarrolladores y recibir datos dinámicos por separado. El engine todavía debe aplicar escaping contextual para HTML, atributos, JavaScript, CSS y URLs.
Formatos de logging
def logear(formato: LiteralString, *args: object) -> None:
...
logear("usuario %s entró", nombre)Mantener el formato literal evita placeholders controlados externamente. Los valores siguen separados.
Internacionalización
Texto traducido desde catálogos es str, aunque el catálogo pertenezca al proyecto. No lo fuerces a LiteralString solo para satisfacer una API. La traducción necesita otro modelo de confianza.
Archivos de configuración
Texto leído de YAML, JSON o variables de entorno es dinámico. No se vuelve literal porque el archivo esté en el repositorio. Deploys y operadores pueden modificarlo.
Funciones que preservan confianza
def agregar_limite(query: LiteralString) -> LiteralString:
return query + " LIMIT 100"Una función puede recibir y devolver LiteralString si añade únicamente contenido literal. Si incorpora un str arbitrario, el retorno debería ser str.
Builders seguros
def ordenar_por_nombre(query: LiteralString) -> LiteralString:
return query + " ORDER BY nombre"Para opciones dinámicas, mapea enums o Literals a fragmentos fijos. Evita fragmentos libres.
cast no sanitiza
from typing import cast
entrada = input("SQL: ")
query = cast(LiteralString, entrada)El cast solo silencia el analizador. No valida, escapa ni cambia el valor. Aplicarlo a entrada no confiable destruye la garantía.
Validar no crea automáticamente LiteralString
Después de una regex o allowlist, el analizador puede seguir viendo str. Prefiere funciones que seleccionen literales conocidos o devuelvan una unión Literal cerrada. Evita un helper genérico “confía en esta string”.
Literal frente a LiteralString
Literal["asc", "desc"] describe valores exactos, compatibles con LiteralString. Literal es mejor para opciones cerradas. LiteralString sirve cuando la API acepta texto construido exclusivamente desde literales confiables.
TypeGuard no prueba el origen
TypeGuard no puede determinar de forma general que una string arbitraria provino del código fuente. El origen es una propiedad del flujo estático, no del contenido observable.
Bibliotecas externas
Un retorno anotado como str pierde confianza aunque la implementación actual devuelva constantes. Una biblioteca puede prometer LiteralString solo si conserva realmente el origen en todo su contrato.
Archivos stub
Bibliotecas de base de datos, logging y templates pueden usar LiteralString en .pyi. Prueba la experiencia con mypy y pyright. Una anotación demasiado restrictiva puede bloquear usos legítimos.
No es taint tracking completo
LiteralString es una aproximación simple. No rastrea fuentes, sanitizadores, contextos, codificaciones, drivers o flujos entre procesos como un sistema completo de taint analysis.
Seguridad por capas
Úsalo como capa adicional. Parámetros SQL, arrays de argumentos, escaping contextual, allowlists, validación, autorización, privilegios mínimos y pruebas siguen siendo necesarios.
Compatibilidad
Usa typing_extensions.LiteralString en versiones anteriores. La precisión depende también del analizador. Mantén herramientas actualizadas y añade pruebas de tipado al CI.
Errores comunes
- Hacer cast de entrada externa: no existe sanitización.
- Interpolar valores SQL: usa parámetros del driver.
- Suponer que el shell es seguro: evita shell=True.
- Tratar configuración como literal: es dato dinámico.
- Confundirlo con taint analysis completo: el modelo es limitado.
- Confiar solo en el tipo: runtime necesita defensas.
Ejemplo completo: repositorio SQL
from typing import Literal, LiteralString
Orden = Literal["nombre", "creado_en"]
def columna_orden(orden: Orden) -> LiteralString:
if orden == "nombre":
return "nombre"
return "creado_en"
def listar_usuarios(conexion, termino: str, orden: Orden):
columna = columna_orden(orden)
query: LiteralString
if columna == "nombre":
query = (
"SELECT id, nombre FROM usuarios "
"WHERE nombre LIKE ? ORDER BY nombre"
)
else:
query = (
"SELECT id, nombre FROM usuarios "
"WHERE nombre LIKE ? ORDER BY creado_en"
)
cursor = conexion.execute(query, (f"%{termino}%",))
return cursor.fetchall()Los valores de búsqueda están parametrizados. La columna se elige mediante una unión Literal y cada consulta final es un literal de código. La entrada externa nunca se convierte en estructura SQL.
Pruebas estáticas
from typing import assert_type
assert_type(columna_orden("nombre"), LiteralString)
texto: str = input()
# ejecutar_sql(texto) debería fallarIncluye casos aceptados y rechazados para proteger la frontera cuando cambien stubs y analizadores.
Cuándo usarlo
Usa LiteralString en APIs donde la estructura textual debe estar definida por el desarrollador: consultas, templates, formatos y expresiones. No lo uses en funciones normales que procesan texto arbitrario.
Conclusión
LiteralString declara que una API espera texto originado en literales confiables. Ayuda a bloquear strings dinámicas en posiciones sensibles e incentiva separar estructura y datos.
La documentación oficial de LiteralString en Python explica la inferencia. Úsalo como defensa adicional, nunca como sustituto de consultas parametrizadas, escaping, allowlists, validación, autorización y privilegios mínimos.







