Anonimización que preserva el formato: por qué XXXX rompe su pipeline de IA
Sustituir 4532-1488-0343-6467 por XXXX-XXXX-XXXX-XXXX es la redacción más perezosa posible y rompe silenciosamente cada respuesta del LLM. Esto es lo que hay que hacer.
El problema con XXXX
Aquí tiene un número de tarjeta que pasa el dígito de Luhn: 4532-1488-0343-6467. Caviárdelo de la forma ingenua y mándelo a ChatGPT:
¿Es XXXX-XXXX-XXXX-XXXX un número de tarjeta válido?El modelo responderá algo como «no, es un marcador de posición, no un número real». Técnicamente correcto. En la práctica, inútil. La pregunta que usted quería responder era «¿es estructuralmente válida la tarjeta que mi usuario acaba de teclear?», y destruyó cada señal que el modelo necesitaba para responderla.
Pruebe ahora la reescritura preservando el formato:
¿Es 4532-1488-XXXX-6467 un número de tarjeta válido?El modelo puede razonar: el 4 inicial indica Visa, 16 dígitos coinciden con la longitud Visa, los 4 últimos quedan visibles para el front-end, los del medio están claramente redactados. Responderá a la pregunta real — sí, el patrón es coherente con una Visa real, la suma de control puede regenerarse y aquí tiene cómo mostrarla al cliente.
Esta es la brecha entre la redacción ingenua y la anonimización que preserva el formato. La primera es una casilla de cumplimiento que rompe el producto. La segunda es lo que Zeuslock entrega por defecto, porque la alternativa — que los usuarios desactiven el DLP porque ha vuelto idiota a su asistente de IA — es estrictamente peor para la seguridad que cualquier coste de filtración residual.
La redacción ingenua es destrucción de información
Si los ingenieros recurren a XXXX es porque parece seguro. Es el caviardado más fuerte posible: cada byte del secreto desaparece. El problema es que también destruye los metadatos. Tres elementos viajan con un valor real, y casi nunca quiere perder los tres:
- El formato. Una cadena de 16 dígitos con guiones es una tarjeta. Un IBAN empieza por dos letras. Un JWT contiene dos puntos. Un bearer token va en base64url. El formato es lo que permite al LLM saber de qué tipo de objeto se habla.
- La semántica dentro del formato. El
4inicial de una Visa, el prefijo de país de un IBAN, el prefijoAKIAde una clave AWS de largo plazo, elkidde una cabecera JWT — todos indican al modelo qué librería, qué proveedor, qué jurisdicción son relevantes. - Las colas no secretas. Los 4 últimos de la tarjeta, la longitud del local-part de un correo, los dígitos de control de un IBAN — el contexto que la IA necesita para dar una respuesta accionable.
Elimine los tres y ya no pide ayuda a la IA: le pide que adivine.
Caso 1: tarjetas de crédito
Tome 4532-1488-0343-6467. Luhn válido, 16 dígitos, el 4 inicial identifica Visa, el BIN 453214 apunta a un emisor concreto. Tres estrategias de caviardado, tres comportamientos LLM muy distintos.
| Estrategia | Cadena enviada al LLM | Lo que el LLM aún puede responder |
|---|---|---|
| Original (sin DLP) | 4532-1488-0343-6467 | Todo. Y: brecha PCI completa. |
| XXXX ingenuo | XXXX-XXXX-XXXX-XXXX | Casi nada. «Es un marcador de posición.» |
| Preservando el formato | 4532-1488-XXXX-6467 | Visa. Patrón de 16 dígitos. Plantilla Luhn válida. 4 últimos visibles. Emisor inferible. |
La fila del medio es la que producen la mayoría de los scripts DLP caseros, y también la que empuja al usuario a pegar el número real en su ChatGPT personal para obtener una respuesta de verdad. La tercera fila es la que mantiene productivos tanto al equipo de seguridad como al usuario.
Generar un reemplazo Luhn válido y preservando el formato en Python
A continuación, un pequeño generador. Dado un número de tarjeta real, conserva los seis primeros y los cuatro últimos dígitos, y reescribe el medio para que el resultado siga pasando Luhn. El original no se registra ni se persiste.
import secrets
def luhn_check_digit(digits: str) -> int:
total = 0
for i, ch in enumerate(reversed(digits)):
d = int(ch)
if i % 2 == 0:
d *= 2
if d > 9:
d -= 9
total += d
return (10 - (total % 10)) % 10
def format_preserve_card(original: str) -> str:
digits = ''.join(c for c in original if c.isdigit())
if len(digits) != 16:
raise ValueError('se esperaba una tarjeta de 16 dígitos')
bin6, last4 = digits[:6], digits[-4:]
# 5 dígitos aleatorios en medio + 1 dígito de Luhn calculado
middle = ''.join(str(secrets.randbelow(10)) for _ in range(5))
candidate = bin6 + middle + '0' + last4
check = luhn_check_digit(candidate[:-1] + last4[:-1])
fake = bin6 + middle + str(check) + last4
return f'{fake[:4]}-{fake[4:8]}-{fake[8:12]}-{fake[12:]}'
La salida es una Visa de aspecto real con 16 dígitos que ningún procesador cobrará jamás, porque estadísticamente casi seguro no está asignada. El LLM la trata como tarjeta, deduce correctamente el emisor y el usuario obtiene una respuesta utilizable.
Caso 2: IBAN
Un IBAN como FR76 3000 6000 0123 4567 8901 234 lleva cuatro capas de significado: país (FR), dígitos de control (76), código de banco/oficina (3000 6000), número de cuenta y una clave nacional. El XXXX ingenuo destruye las cuatro. Pregunte después al LLM «¿de qué país es esta cuenta?» y obtendrá una negativa.
Reescritura preservando el formato: FR76 XXXX XXXX XXXX XXXX XXXX 234. Se conservan el país y los dígitos de control mod-97. Un modelo aguas abajo responde con seguridad «Francia», y un generador puede reconstruir una suma de control plausible si su pipeline de pruebas lo necesita.
Compromiso: mantener FR76 filtra información geográfica. Para un flujo de soporte al cliente está bien y probablemente es lo deseado. Para un pipeline de RR. HH. interno que procesa empleados en varias jurisdicciones, puede que no. La preservación del formato es una decisión de política, no un valor por defecto universal. Zeuslock le permite ajustarlo por tipo de dato y por pipeline.
Caso 3: claves de acceso AWS
Una clave de acceso AWS de largo plazo se parece a AKIAIOSFODNN7EXAMPLE. El prefijo AKIA no es aleatorio: identifica el tipo de clave (ASIA para tokens STS temporales, AKIA para IAM de largo plazo). La longitud es fija en 20.
Caviardar a XXXX no le dice nada al LLM. Preservando el formato como AKIA**************** le dice «es una clave IAM de largo plazo, longitud 20, entropía secreta eliminada». El reemplazo no es una clave válida — AWS la rechazará al instante — pero ahora el modelo puede responder correctamente «rote la clave con aws iam create-access-key y luego revoque la antigua» en lugar de «no puedo ayudar con cadenas de marcador de posición».
Mismo principio para Stripe (sk_live_), OpenAI (sk-), GitHub (ghp_), Slack (xoxb-), GitLab (glpat-), npm (npm_). El prefijo es el tipo. Mantenga el prefijo, mate la entropía.
Caso 4: JWT
Un JWT tiene tres segmentos separados por puntos: header.payload.signature. La gracia de un JWT es precisamente que se puede decodificar la cabecera para descubrir el algoritmo sin verificar la firma. Si un desarrollador pega un JWT real en Claude con el prompt «¿por qué falla la validación de mi token?», el modelo necesita la estructura para dar consejo útil.
La redacción ingenua colapsa el JWT a XXXX. Al modelo no le queda nada que comentar. La versión preservando el formato conserva los puntos y las longitudes de segmento:
eyJhbGciOiJIUzI1NiJ9.XXXXXXXXXXXXXXXX.XXXXXXXXXXXXEl modelo ve tres segmentos, puede comentar la cabecera visible («alg: HS256, probablemente quiera RS256 para verificación asimétrica») y los bytes de firma reales no viajan. El desarrollador obtiene una respuesta real; el secreto se queda local.
Caso 5: correos electrónicos — el más difícil
El correo es el caso en el que la reescritura correcta depende de lo que esté intentando proteger. Dos opciones razonables para alice@acme.com:
| Reescritura | Lo que sobrevive | Cuándo es la correcta |
|---|---|---|
user@example.com | Solo la forma genérica. | El LLM necesita saber «esto es un correo». Identidad y empleador son ambos sensibles. Opción por defecto. |
XYxYx@acme.com | Dominio preservado. | Está depurando un problema de entregabilidad, un registro SPF, una regla de enrutado interno. El dominio es el contexto clave. |
alice@example.com | Local-part preservado. | Casi nunca la opción correcta. El local-part suele ser la identidad. |
La fila del medio merece pensarse. Mantener el dominio filtra el empleador del usuario al que pertenece el correo. Para una herramienta de soporte está bien — el agente sabe que es acme.com — pero para un pipeline de cribado de CV puede filtrar características protegidas. Misma regla que para los IBAN: la preservación del formato es una política, no un valor por defecto.
Cuándo preservar el formato es la opción equivocada
La razón de ser de la preservación del formato es que el formato lleva información semánticamente útil y no secreta. Hay categorías en las que el formato es el secreto. En esos casos, anonimice con más dureza, o bloquee.
- Identificadores tipo DNI/SSN. Los primeros tres dígitos de un SSN estadounidense codificaban antes el estado de emisión. El NIR francés codifica sexo, año, mes, departamento y comuna de nacimiento. «Preservar el formato» de un NIR significa filtrar demografía. La acción correcta es la redacción total o el bloqueo. La AEPD y la CNIL son explícitas sobre esto para datos de categoría especial bajo RGPD.
- IDs internos de cliente. Si su ID de cliente es
ACME-2024-018472y el año codifica la cohorte de onboarding, el formato filtra inteligencia de negocio. Trátelo como opaco. - IDs secuenciales en un espacio pequeño. Si solo existen 5000 empleados y el ID es secuencial, preservar el formato reduce la entropía del caviardado prácticamente a cero — el modelo (o un observador curioso) puede reidentificar a la persona por el contexto.
- Cualquier cosa para la que el regulador haya dicho explícitamente «no conserve identificadores seudónimos sin justificación». Si no puede señalar una finalidad legítima para mantener el prefijo, elimine el prefijo.
El compromiso que eligió Zeuslock, y por qué
Los valores por defecto de Zeuslock se inclinan hacia preservar la estructura. La razón es una observación conductual sencilla: cuando el DLP vuelve inútil al asistente de IA, el usuario no renuncia al asistente. Renuncia al DLP. Pega el prompt en un ChatGPT personal, en el móvil o en el navegador de la cafetería de al lado. La filtración ocurre igual y, además, ha perdido el rastro de auditoría.
Lo más difícil de construir un DLP para LLM no es la detección. Es hacer la experiencia protegida lo bastante buena para que el usuario no intente activamente esquivarla.
La anonimización que preserva el formato es la primitiva técnica que hace posible eso. El usuario sigue recibiendo respuestas útiles, los bytes secretos se quedan en el portátil y el operador dispone de un rastro de auditoría completo sobre qué detector se activó y qué reescritura se aplicó. Por tipo de dato, puede endurecer cualquier reescritura hasta el XXXX, o escalar a Bloquear — pero el valor por defecto sigue siendo estructurado-pero-falso, porque es el valor que sobrevive al contacto con usuarios reales.
Qué hacer a continuación
- Audite su caviardado actual. Si en algún punto de su stack está reemplazando los valores detectados por
[REDACTED],XXXXo cadena vacía antes de que lleguen a un LLM, está degradando la respuesta sin ganar nada en seguridad respecto a una reescritura estructurada. - Elija la política de preservación por tipo de dato. Tarjetas y JWT casi siempre se benefician. IBAN y correos requieren criterio. Los identificadores tipo NIR habitualmente deben caviardarse del todo.
- Muestre la reescritura al usuario antes del envío. La vista previa pre-envío de Zeuslock forma el hábito más rápido que cualquier programa de formación — consulte la guía del operador sobre configuración de políticas de detección para el despliegue recomendado Monitor → Anonimizar → Bloquear.
Protect your data from AI leaks
Try Zeuslock free — DLP for ChatGPT, Claude, Gemini and more.
Book a demo →