Saltearse al contenido

Anonimización

Los datos contables son especialmente sensibles: contienen IBANs de clientes y proveedores, NIFs, CIFs, emails corporativos, teléfonos y referencias de tarjetas. Celestín adopta un principio zero-knowledge: el servidor nunca recibe los valores originales. La sustitución ocurre en el SDK, en el proceso del cliente, antes de que el payload cruce la red.

El principio zero-knowledge

La arquitectura garantiza que ni siquiera Celestín puede reconstruir los datos sensibles de tus clientes. El servidor solo ve tokens opacos. Solo el cliente que posee la clave maestra puede realizar la operación inversa.

Esto tiene implicaciones importantes:

  • Si pierdes la CELESTIN_MASTER_KEY, los tokens son irrecuperables (incluso para soporte).
  • El matching semántico LLM funciona sobre los tokens porque el modelo evalúa estructura textual, no valores.
  • Los logs del servidor, las trazas de red y los backups de base de datos nunca contienen PII.

Pipeline de tres capas

Capa 1 — Regex determinístico

La primera pasada aplica expresiones regulares sobre cada campo de texto. Es la capa más rápida y tiene precisión total sobre los formatos definidos.

Cada coincidencia se sustituye por un token del formato <TIPO:BASE32> donde:

  • TIPO identifica el patrón detectado (ver tabla más abajo).
  • BASE32 es el HMAC-SHA256 del valor original, codificado en Base32 Crockford, calculado con la clave maestra y un salt por tenant.

El mismo valor original produce siempre el mismo token para un tenant dado, lo que permite al motor de matching detectar que dos apuntes mencionan el mismo IBAN aunque el token sea opaco para el servidor.

Capa 2 — NER (opcional)

Si se activa, un modelo NER ligero (ONNX, ejecutado localmente en el proceso del SDK) realiza una segunda pasada sobre el texto para detectar entidades que el regex no puede capturar: nombres de personas, empresas en texto libre, referencias geográficas.

Esta capa está desactivada por defecto porque añade ~80 ms de latencia y requiere descargar el modelo NER (~15 MB) en el primer uso. Se activa con anonymization: { ner: true } en la configuración del cliente.

Capa 3 — Validador de coherencia

Antes de enviar el payload, el validador hace una comprobación final:

  1. Ningún token ha quedado sin sustituir (el regex puede tener falsos negativos en formatos no estándar).
  2. El número de tokens detectados es coherente con el tipo de dato (un campo reference no debería tener más de 2-3 tokens).
  3. El salt_fingerprint se incluye en el metadata de la petición para trazabilidad de auditoría.

Si el validador detecta PII residual (un IBAN sin tokenizar en el payload final), la petición se rechaza en el cliente con error UnredactedPiiError. El dato nunca sale al servidor.

Tabla de patrones detectados

TipoPatrónEjemplo originalToken
IBAN_ESES\d{2}\s?\d{4}\s?\d{4}\s?\d{2}\s?\d{10}ES91 2100 0418 4502 0005 1332<IBAN_ES:A3BCF7>
NIFDNI español 8 dígitos + letra12345678Z<NIF:D4E2A1>
NIEExtranjero X/Y/Z + 7 dígitos + letraX1234567L<NIE:B9F3C8>
CIFSociedad española letra + 8 dígitosB27612951<CIF:E7A2D5>
EMAILRFC 5322 simplificadofactura@empresa.es<EMAIL:F1C4B2>
PHONE_ES+34 seguido de 9 dígitos+34 612 345 678<PHONE_ES:G8D1E3>
CARDLuhn-válido 13-19 dígitos4111 1111 1111 1111<CARD:H5F2A7>

La detección de tarjetas incluye validación Luhn para evitar falsos positivos (números de referencia que coincidan en longitud).

Esquema del token

<TIPO:HMAC_SHA256_BASE32_V2>

El campo de versión (v2) permite introducir esquemas futuros sin romper compatibilidad. El SDK siempre usa el esquema de la versión más reciente para tokenizar y acepta todos los esquemas al destokenizar.

El HMAC se calcula como:

token_value = Base32Crockford(HMAC-SHA256(key=HKDF(master_key, tenant_id, "anon-v2"), msg=original_value + tenant_salt))

salt_fingerprint

Cada petición incluye en su metadata el campo salt_fingerprint, que es un hash del salt de tenant sin revelar el salt en sí. Esto permite a los auditores verificar que dos peticiones usaron el mismo salt (y por tanto sus tokens son comparables) sin exponer el salt original.

{
"metadata": {
"salt_fingerprint": "sha256:a4b7c9...",
"anonymization_version": "v2",
"ner_enabled": false
}
}

SDK — redact() y deanonymize()

El SDK expone dos funciones de bajo nivel para casos avanzados:

import { redact, deanonymize } from "@celestin/sdk/anonymization";
// Tokenizar un texto arbitrario
const { redacted, tokenMap } = redact("Pago a ES91 2100 0418 4502 0005 1332", {
masterKey: process.env.CELESTIN_MASTER_KEY!,
tenantId: "sociedad-a-sl",
});
// redacted: "Pago a <IBAN_ES:A3BCF7>"
// tokenMap: { "<IBAN_ES:A3BCF7>": "ES91 2100 0418 4502 0005 1332" }
// Recuperar el texto original desde los tokens
const original = deanonymize(redacted, tokenMap);
// original: "Pago a ES91 2100 0418 4502 0005 1332"

tokenMap es un mapa local en memoria y nunca se persiste ni se envía al servidor. Es tu responsabilidad gestionarlo si necesitas destokenizar resultados más tarde.