La API para aplicaciones
Latet protege lo que un agente envía a un modelo. Pero los programas también envían texto sensible a otros sitios: sus registros a Datadog, Splunk o Elastic, sus documentos al índice de una aplicación RAG. La API para aplicaciones permite a un programa pedir a un servidor de Latet, con el token de una clave de API, que:
- analice un texto (
scan): qué contiene de sensible, y dónde; - lo enmascare (
mask): el mismo texto con valores ficticios realistas en lugar de sus valores; - lo restaure (
unmask): los valores reales de nuevo en lugar de los ficticios; - olvide valores (
forget): los quite de la caja fuerte de la clave, cuando una persona pide que sus datos desaparezcan (olvidar valores).
Es una función del servidor de Latet: las claves de API y sus tokens solo existen allí, y un equipo
sin servidor no tiene nada de ella. Para probarla en tu propio equipo, arranca en él un servidor
Community con el perfil de servidor de compose.yaml (operación del servidor).
Lo que aporta frente a una regla de ocultación:
- El mismo valor ficticio cada vez. Un valor recibe el mismo valor ficticio en cada llamada, en cada instancia del servidor, mientras la caja fuerte de la clave lo conserve. En un servicio de registros, los gráficos y las búsquedas siguen funcionando, y se puede seguir a un cliente durante días de registros sin que nadie vea quién es. En un índice RAG, una persona sigue siendo la misma persona de un documento a otro.
- Una sola caja fuerte para todo el flujo. Los documentos y las preguntas enmascarados con la
misma clave comparten sus valores ficticios, así que un modelo razona sobre valores ficticios
coherentes, y
unmaskvuelve a poner los valores reales en su respuesta. - Reversible, bajo control. Restaurar es un permiso propio, solo alcanza los valores ficticios de la clave, y cada restauración a través de la API para aplicaciones queda escrita en el registro de administración del servidor.
Dos páginas recorren los dos usos para los que se hizo: los registros y el RAG. La
referencia HTTP da el contrato para programas en cualquier lenguaje, y la
descripción OpenAPI de la API permite generar un cliente a partir de
ella. Los programas en Python pueden usar el paquete
latet-client, que funciona con LangChain, y los flujos de
n8n los nodos de Latet para n8n.
Una primera llamada
Sección titulada «Una primera llamada»Un programa envía una lista de textos y recibe una lista, en el mismo orden:
printf 'Authorization: Bearer %s' "$(cat shop.key)" | curl -sS -H @- \ -H "Content-Type: application/json" \ -d '{"texts": ["Reembolsa el pedido de mario.rossi@acme.it a IT60X0542811101000000123456, por favor."]}' \ https://latet.acme.lan/_latet/apps/v1/mask{"results": [{"text": "Reembolsa el pedido de tommaso.gallo@margie-6e5a.example a IT17F3945471087320356678487, por favor.", "replacements": [{"type": "EMAIL", "class": "pii", "start": 23, "end": 56}, {"type": "IBAN", "class": "pii", "start": 59, "end": 86}], "counts": {"EMAIL": 1, "IBAN": 1}}]}shop.key contiene el primer token de la clave, el que escribe keys add
(más abajo).
printf, que la propia shell ejecuta, pasa la cabecera a curl por su entrada estándar (-H @-): así
el token no aparece en la línea de órdenes de curl, donde otros usuarios del equipo podrían leerlo.
Los valores ficticios son los de la clave: otra clave, u otro servidor, da otros. Con un token que
puede restaurar, el mismo texto enviado a /_latet/apps/v1/unmask vuelve como era, con
"restored": 2.
Claves de API y tokens
Sección titulada «Claves de API y tokens»Una clave de API es un espacio en el servidor: sus reglas, su caja fuerte de valores ficticios, su historial. Puede tener varios tokens, los secretos que actúan en ese espacio, y cada token tiene sus propios permisos:
| Permiso | Lo que puede hacer el token |
|---|---|
scan |
Decir qué contiene un texto y dónde; no cambia nada. |
mask |
Sustituir los valores de un texto por los valores ficticios de la clave. Un token que puede enmascarar también puede analizar. |
unmask |
Volver a poner los valores reales en lugar de los valores ficticios de la clave. |
forget |
Quitar valores de la caja fuerte de la clave (olvidar valores). Ningún otro permiso lo incluye. |
Da a cada programa solo lo que necesita. Una aplicación que envía sus registros necesita mask; la
herramienta de soporte que lee una línea copiada del servicio de registros necesita unmask; una
aplicación RAG que enmascara sus documentos y restaura la respuesta del modelo necesita los dos.
latet server --server https://latet.acme.lan keys add "Shop" --out shop.keylatet server --server https://latet.acme.lan keys add-token "Shop" shipper --mask --out shipper.tokenlatet server --server https://latet.acme.lan keys add-token "Shop" support --unmask --out support.tokenlatet server --server https://latet.acme.lan keys tokens "Shop"El primer token de una clave, el que crea keys add, tiene scan, mask y unmask, igual que el
token de cada clave creada antes de que los tokens tuvieran permisos: esas claves funcionan como antes.
El primer token de la clave tiene además una dirección para modelos, la
puerta de enlace, y a través de ella
sirve a los agentes de la clave: keys tokens lo señala con serves agents (has an address), para que
no se revoque en lugar de un token añadido con los mismos permisos. Un token creado con keys add-token no tiene dirección, tenga los
permisos que tenga, --mask --unmask incluidos: la puerta de enlace y el bridge del plugin lo
rechazan (403), y solo llama a la API para aplicaciones.
Cada token se detiene por separado: keys tokens "Shop" --revoke t_4kxq2m7zap revoca uno por el id
que muestra keys tokens, en pocos segundos en cada instancia. keys rotate "Shop" --token t_4kxq2m7zap --out shipper.token sustituye ese token por uno nuevo, con un secreto nuevo y un id
nuevo, el mismo nombre y los mismos permisos; una clave con un solo token no necesita --token. Cada
cambio queda en el registro de administración (token.added, token.revoked, key.rotated), con los
ids de los tokens, nunca un secreto.
La API para aplicaciones solo acepta tokens de claves de API: el token del equipo de una persona se rechaza (403), igual que una solicitud que hace una página web. Las personas usan su panel.
Prosa y datos
Sección titulada «Prosa y datos»scan y mask aceptan un kind para los textos de una llamada:
prose(el predeterminado): texto escrito por personas, como mensajes, tickets y documentos. Se enmascara como la puerta de enlace enmascara tus propios mensajes.data: registros, fichas, la salida de un programa. Se enmascaran como la puerta de enlace enmascara la salida de una herramienta.
La diferencia está en lo que es probable: en el perfil balanced, los nombres solo se buscan en la
prosa (con el modelo de nombres local
instalado en el servidor), y una serie de dígitos solo cuenta como número de teléfono en la prosa,
mientras que en un registro es mucho más a menudo un id. Con phone_regions: [IT] en las reglas de la
clave, el número en Llámame mañana al 3471234567. recibe un valor ficticio como prosa y se queda
como está como datos. Un documento JSON enviado como texto se enmascara como texto, conservando su
formato, así que las posiciones de la respuesta son posiciones en él.
Qué lee un enmascaramiento
Sección titulada «Qué lee un enmascaramiento»scan y mask usan los ajustes de la clave tal como están cuando llega la llamada: las reglas
propias de la clave sobre los ajustes compartidos del servidor, como hace la puerta de enlace con las
solicitudes de la clave. No hay reglas solo para la API. Se configuran con latet server config --key
y latet server rules --key
(ajustes compartidos y reglas de las claves de API).
- El revisor local, cuando las reglas de la clave indican uno, lee cada texto de una llamada
maskantes de la respuesta, diga lo que digallm_review.inlinepara la puerta de enlace: una aplicación no tiene un momento posterior. Dispone dellm_review.budget_mssi está configurado, y si no de 30 segundos. Un texto cuya revisión falla siguellm_review.on_failure: constop(el predeterminado) la respuesta esblocked, consendes el texto tal como lo enmascararon las reglas. Así que, con un revisor caído, cada texto se bloquea hasta que vuelva a responder. scanno ejecuta el revisor local. El revisor enseña a la caja fuerte lo que encuentra, y el análisis no cambia nada. En una clave con revisor,scanpuede informar de menos de lo quemasksustituye.- El Revisor en directo nunca retiene el texto de una aplicación. Una aplicación no puede esperar a una persona: un texto se enmascara solo con las reglas y el revisor local, aunque las reglas de la clave activen el Revisor en directo.
- La protección contra volcados se aplica a los textos
datacomo a la salida de una herramienta: uno que contiene más valores personales de los que permitedump_guardrecibeblocked.
Un texto bloqueado vuelve como {"blocked": "<motivo>"} sin texto, y el motivo no nombra ningún
valor. Cuando algo funcionó de forma reducida en la llamada (un detector que no pudo leer un texto,
una revisión que no terminó), la respuesta lo dice en "degraded", con los nombres de lo afectado,
como ner o llm-review.
Enmascarado dos veces, restaurado dos veces
Sección titulada «Enmascarado dos veces, restaurado dos veces»mask toma por real cada valor que encuentra, también los propios valores ficticios de la clave:
nadie puede saber por una cadena si es un valor ficticio o un valor real que se le parece. Un texto
enmascarado dos veces contiene valores ficticios de valores ficticios, y una restauración lo devuelve
un paso atrás:
| Paso | Texto |
|---|---|
| Original | Escribe a mario.rossi@acme.it |
| Enmascarado | Escribe a tommaso.gallo@margie-6e5a.example |
| Enmascarado de nuevo | Escribe a simone.desantis@humongous-0e54.example |
| Restaurado una vez | Escribe a tommaso.gallo@margie-6e5a.example |
Enmascara un texto una sola vez, a partir de su original. La puerta de enlace sigue la misma regla: un texto enmascarado con la API y luego enviado a un modelo a través de la puerta de enlace de Latet se vuelve a enmascarar allí, y la respuesta del modelo vuelve con los valores ficticios de la API. Por eso una aplicación RAG envía el texto enmascarado directamente al modelo y restaura la respuesta con la API (RAG).
Quién puede saber qué
Sección titulada «Quién puede saber qué»Restaurar da los valores reales de los valores ficticios de la clave, sea cual sea la llamada o la
conversación de la clave que los creó, también los del año pasado. No alcanza los valores ficticios de
otra clave ni los de una persona: el texto que no es un valor ficticio de la clave se queda como está.
El nombre de una persona solo vuelve entero: una parte suya sola (un nombre de pila) puede ser una
palabra corriente, o el nombre de otra persona, y se queda como está. Cada restauración se escribe en el
registro de administración antes de enviar su respuesta (apps.unmasked, con el token, cuántos textos y
cuántos valores; nunca un texto ni un valor), y un registro que no puede escribirla rechaza la llamada
(503). El primer token de la clave, que puede analizar, enmascarar y restaurar, también sirve a agentes
a través de la puerta de enlace: sus respuestas se restauran como las de cualquier agente, sin una
entrada del registro de administración por cada restauración. Un token creado con keys add-token
nunca lo hace, así que cada una de sus restauraciones es una entrada apps.unmasked. Da unmask a las
herramientas de las
personas que pueden ver los datos reales, y lee el registro con
latet server log --action apps.unmasked.
Enmascarar también revela algo. Con un token que puede enmascarar y acceso a los registros
enmascarados, alguien puede confirmar una sospecha: enmascarar mario.rossi@acme.it y buscar su valor
ficticio en los registros. Si está, Mario Rossi es cliente. Por eso da mask solo a los programas que
lo necesitan, guarda el token donde guardan sus secretos, y trata los registros enmascarados como
datos seudonimizados, no anónimos.
Analizar también revela algo. No crea valores ficticios ni cambia nada, pero informa de los
valores que la caja fuerte de la clave ya conoce, dondequiera que aparezcan. Un token que solo puede
analizar puede, por tanto, confirmar que un valor que ningún detector encontraría por sí solo (un
nombre en un texto data, un valor que una regla de la clave encontró antes de que la quitaran) es
uno de los de la clave. Da scan con el mismo cuidado que mask.
Olvidar también revela algo. Su respuesta dice cuántos de los valores guardaba la caja fuerte de la
clave, así que un token que puede olvidar puede confirmar que un valor es de la clave, como uno que
puede analizar. Da forget solo al proceso que atiende las peticiones de borrado; cada olvido queda en
el registro de administración (apps.forgotten, con el token y las cifras, nunca un valor). Ninguno
de los tokens que crea keys add puede olvidar: forget se da a un token propio
(keys add-token "Shop" erasure --forget).
La caja fuerte de una clave
Sección titulada «La caja fuerte de una clave»La caja fuerte de una clave conserva cada valor que ha enmascarado, con su valor ficticio, mientras la
clave exista: una restauración funciona con una línea de registro del año pasado, y un cliente conserva
el mismo valor ficticio durante años. Los valores no caducan: un valor sale de la caja fuerte cuando se
olvida (olvidar valores), y todos se van con la clave, ya que latet server keys remove
borra su espacio, y con él su caja fuerte. Un valor encontrado una vez se enmascara desde entonces
dondequiera que aparezca, incluso donde ningún detector lo volvería a encontrar: la dirección de un
cliente que una regla de la clave encontró una vez, y que luego se quitó, sigue enmascarada.
Un espacio de registros puede contener millones de valores, así que una instancia no guarda en
memoria la caja fuerte de una clave. Guarda los valores que la clave usó por última vez
(key_working_values, 5.000 por defecto, unos 10 MiB por clave) y encuentra cualquier otro en
PostgreSQL cuando los textos de una llamada lo necesitan, con una sola búsqueda para todos, mediante
un índice de las palabras de los valores y de los valores ficticios. Ese índice contiene hashes
calculados con una clave secreta, nunca una palabra en claro.
Secretos pegados a otras letras. En PostgreSQL, un secreto escrito como una sola serie de letras
y dígitos, sin espacios ni puntuación, se encuentra por su palabra, y por sus seis primeros y seis
últimos caracteres cuando otras letras o dígitos están pegados a uno de sus lados. Pegado por ambos
lados (xhunter2y), o pegado de cualquier modo cuando la palabra de su borde tiene seis caracteres
o menos, ninguna palabra del texto lo encuentra. Por eso una instancia guarda en memoria para
siempre los secretos cortos de una clave, los de una sola serie de 16 caracteres o menos o con una
palabra así de corta en un borde: los key_held_secrets más recientes (1.000 si los ajustes del
servidor no dicen otra cosa), que se encuentran estén pegados como estén. Uno más antiguo se
encuentra donde nada está pegado a él, o donde un lado está libre y tiene siete caracteres o más;
uno más largo, dondequiera que un lado esté libre. Sube key_held_secrets para una clave que ve
muchos secretos cortos, como un token de sesión en cada línea de registro: cada uno que se guarda
ocupa unos 2,2 KB en cada instancia, y cada texto que la clave enmascara o restaura se recorre para
buscarlo, lo que hace sus llamadas un poco más lentas. Medida como abajo con un token de sesión
propio en cada línea (un valor más que guardar en cada una), una clave nueva con 1.000 de esos
secretos guardados enmascaró 73 líneas por segundo y restauró 1.935, frente a 75 y 2.103 sin ninguno
guardado. Un valor ficticio se encuentra pegado como su secreto: conserva los signos del secreto en
su sitio, así que un valor ficticio pegado de modo que ninguna de sus palabras quede suelta es el de
un secreto guardado en memoria, y se encuentra mientras el secreto se guarda. Como en todo Latet, un
valor se vuelve a reconocer a partir de cuatro caracteres, el nombre de una persona a partir de tres
(valores conocidos).
Secretos cortos. Un secreto de menos de doce caracteres (password=1234, un token corto) recibe
un valor ficticio más largo de los mismos tipos, con sus signos en su sitio: quince dígitos para un
número (con su signo y su punto decimal, para que un número JSON siga siendo un número), doce
dígitos hexadecimales para un valor hexadecimal, si no doce letras y dígitos. Un valor ficticio tan
corto como su valor aparecería por casualidad en los identificadores y los números de otras líneas
de registro, y una restauración cambiaría líneas que nunca contuvieron el secreto.
Un valor ficticio corto que la caja fuerte de una clave dio antes de Latet 0.10.2 (de ocho
caracteres o menos) se queda, como todo valor ficticio. unmask, que lee cada valor ficticio de la
caja fuerte de la clave, solo lo sustituye por el valor donde está como palabra completa, sin letra,
dígito ni guion bajo al lado. El coste: donde el valor de un secreto así se enmascaró dentro de una
palabra o un número más largo (postgres en postgresql://, 1234 en order_id=9912343),
unmask deja ahí su valor ficticio. Una conversación del primer token de la clave, a través del
gateway o del plugin, lo restaura como antes, también dentro de una palabra, porque solo restaura
los valores ficticios que se le mostraron. Mientras una instancia del servidor siga ejecutando la
0.10.0, da valores ficticios cortos a los nuevos secretos cortos, y también esos se quedan:
actualiza cada instancia.
Qué conserva una llamada que falla. Una llamada mask que falla mientras enmascara (un 507, una
confirmación que no llega a PostgreSQL) no conserva ninguno de sus valores nuevos cuando ocupaba para
ellos una de las transacciones de la instancia, tres a la vez con el database_pool predeterminado: el
espacio de la clave los olvida. Si esas estaban ocupadas, la llamada conservó cada valor nuevo sobre la
marcha, y esos se quedan en la caja fuerte de la clave. Un fallo después del enmascaramiento, en la revisión local o al preparar la
respuesta, llega después de la confirmación, así que los valores nuevos de la llamada también se
quedan. Volver a enmascarar los textos es seguro en cualquier caso: un valor que la caja fuerte
conservó tiene su valor ficticio, y uno que olvidó recibe otra vez uno. Un agente de la misma clave
que usó mientras tanto un valor olvidado a través de la puerta de enlace ya envió su valor ficticio,
que ninguna restauración posterior devolverá.
Límites
Sección titulada «Límites»- La caja fuerte. La caja fuerte de una clave admite los
vault_limitvalores del servidor (200.000 si los ajustes del servidor no dicen otra cosa), o un límite propio:latet server keys limit "Shop" 5000000(de 1.000 a 100.000.000;defaultle devuelve el del servidor). Una caja fuerte llena rechaza con 507 la llamadamaskque añadiría un valor: no vuelve nada de la llamada, y nada sale en claro. El estado del servidor lista las identidades al 90 % de su límite, y la métricalatet_vault_near_limitlas cuenta, así que sube un límite antes de alcanzarlo. Cada instancia cuenta los valores nuevos de una llamada antes de confirmarlos: dos instancias, o una llamada y un agente de la misma clave, pueden superar el límite en los valores nuevos de una llamada cada uno antes de que se rechace el siguiente valor. - Una llamada admite como mucho 1.000 textos y el
apps_request_mbdel servidor (8 MiB si sus ajustes no dicen otra cosa, nunca más que sumax_request_mb): una llamada espera en memoria al hilo de la clave, ylatet-clientdivide una lista más larga en llamadas de 8 MiB como mucho. Los textos cuyas palabras nombran más de 100.000 valores o valores ficticios de la caja fuerte se rechazan con 422, sin enmascarar nada: envía menos textos por llamada. - Llamadas simultáneas. Una clave tiene como mucho
requests_in_flightsolicitudes de aplicaciones en una instancia (16 si los ajustes no dicen otra cosa); por encima, 429 conRetry-After.
Velocidad
Sección titulada «Velocidad»Las llamadas de una clave se ejecutan una tras otra en una instancia, en el hilo de su espacio; las llamadas de claves distintas, y las que llegan a instancias distintas, se ejecutan a la vez. Para ir más rápido con una clave, pon más instancias detrás del equilibrador de carga.
Medido en una instancia (Intel i9-10850K, 64 GB, Windows 10, PostgreSQL en Docker Desktop en el mismo
equipo), con líneas de registro de unos 200 caracteres, cada una con una dirección de correo y una
dirección IPv4 pública, la mitad de las cuales nombra a un cliente ya visto, 500 líneas por llamada
como data; y con documentos de unos 20 KB de prosa con diez valores cada uno, uno por llamada:
| La caja fuerte de la clave | Enmascarar, líneas por segundo | Restaurar, líneas por segundo | Un documento, enmascarar / restaurar |
|---|---|---|---|
| Nueva, aprende sus valores sobre la marcha | 189 | 2.691 | 77 / 28 ms |
| 200.000 valores | 440 | 1.691 | 87 / 30 ms |
| 1.000.000 de valores | 410 | 1.498 | 95 / 32 ms |
El enmascaramiento está limitado por los detectores: en un hilo, con todos los valores en memoria y
sin base de datos, el motor enmascara unas 900 líneas como estas por segundo. Una clave nueva además
escribe cada valor nuevo en PostgreSQL. Una instancia mantiene como mucho database_pool conexiones
a PostgreSQL (10 si los ajustes del servidor no dicen otra cosa) para el trabajo de todas las
identidades, y deja que tres décimas partes de ellas (tres de diez) las ocupen llamadas que conservan
sus valores nuevos en una transacción cada una; las demás conservan cada valor según llega, más
despacio. Sube database_pool para una instancia que enmascara para muchas claves nuevas a la vez, y
con él el max_connections de PostgreSQL
(operación del servidor).
scripts/bench_apps.py, en el repositorio, mide un servidor tuyo de la misma manera.
Actualizar un servidor
Sección titulada «Actualizar un servidor»Tras una actualización que cambia cómo se indexan las palabras de una caja fuerte, la primera instancia que construye el espacio de una clave las vuelve a crear antes de responder a la primera llamada de la clave: unos 40 segundos para 200.000 valores y cuatro minutos y medio para 1.000.000 en el equipo indicado arriba. Una llamada que llega mientras tanto espera, y una instancia construye cuatro espacios a la vez, así que unas pocas claves grandes retrasan las primeras llamadas de las demás claves en ella. El registro de cambios dice cuándo una versión lo hace; entonces envía a cada clave grande una llamada, con un tiempo de espera suficiente, antes de que sus programas vuelvan a arrancar.
Mientras instancias de dos versiones se ejecutan a la vez, un valor que escribe una instancia más antigua lo encuentran las más nuevas mientras lo tienen en memoria, pero no en PostgreSQL hasta que el espacio de la clave se vuelve a construir. Cuando la API para aplicaciones está en uso, detén cada instancia más antigua antes de que arranquen las nuevas, aunque las tablas no cambien (actualizar).
© 2026 Emanuele Casabona · NIF-IVA IT02293040990 · Política de privacidad · Aviso legal · latet.ai