El navegador envía datos, nunca instrucciones
La tercera tarea es la que la gente se salta, tras haber hecho la parte de la credencial y darse por satisfecha.
Si el navegador envía el prompt, has conservado la clave y perdido todo lo demás. Las reglas del capítulo 3 desaparecen, la cláusula de alcance desaparece, y el usuario puede pedirle a tu presupuesto que le escriba una novela.
Así que el navegador envía parámetros, y el servidor compone el prompt:
$validated = $request->validate([
'language' => ['required', 'string', 'in:en,es,de,it,pt'],
'content' => ['required', 'array'],
]);
$translated = (new TranslateContentAction)(
token: $workspaceToken,
content: $content,
language: (string) $validated['language'],
);
El idioma de destino es una enumeración, no una cadena. No porque un idioma inesperado sea peligroso en sí, sino porque un campo de texto libre en un endpoint que compone prompts es una caja de texto apuntando a tu modelo. in:en,es,de,it,pt no cuesta nada y la cierra.
Esa es la forma general: todo valor que aporte el navegador debería ser un tipo acotado —un enum, un identificador que buscas, una clave de campo que reconoces, un entero con rango. El texto libre sólo es aceptable donde la función realmente es texto libre, y allí es contenido, interpolado en un prompt cuyas instrucciones escribiste tú.
Resuelve en el servidor la credencial que paga
La credencial que llega de verdad al proveedor se resuelve desde el contexto autenticado y nunca viaja hacia fuera:
$workspaceToken = $site->server?->workspace?->token;
if (! is_string($workspaceToken) || $workspaceToken === '') {
return new ErrorResponse('Workspace is not configured for AI.', Response::HTTP_SERVICE_UNAVAILABLE);
}
Esta es la credencial por cliente del capítulo 1 llegando donde importa. El navegador se autenticó como un sitio; el sitio resuelve a un espacio de trabajo; el espacio de trabajo tiene la credencial que se factura. La cadena corre en el servidor de punta a punta, y en ningún momento el navegador tiene nada que funcione en otro sitio.
El 503 también es deliberado. Un espacio de trabajo sin configurar no es culpa del llamante ni un problema de permisos: somos nosotros, temporalmente incapaces de servir. Devolver 401 ahí mandaría a un comerciante a buscar un problema de inicio de sesión que no existe.
Lo que el navegador sí puede hacer
Este capítulo tiene un título que exagera su propia regla, así que seamos precisos sobre la frontera.
Prohibido: que el navegador tenga una credencial del proveedor, o que componga las instrucciones que un modelo ejecuta a tu cuenta.
Perfectamente correcto: que el navegador haga una llamada HTTP larga y directa a tu endpoint, incluida una que emita en streaming. Hay buenas razones para hacer exactamente eso: un CDN o un proxy delante de tu aplicación puede imponer un tiempo de espera al primer byte más corto que lo que tarda el trabajo, y que el navegador llame a un endpoint que empieza a emitir de inmediato es a veces la única salida. El capítulo 16 cubre un caso en el que esa fue la respuesta correcta.
La distinción no es cuántos saltos hay. Es quién decide. Si las decisiones sobre credencial, coste, prompt e identidad se toman en tu servidor, la forma del transporte es una elección de ingeniería. Si alguna de esas cuatro se muda al cliente, ninguna cantidad de proxy las recupera.
De ahí salen dos patrones:
Las credenciales efímeras y acotadas están bien; las claves del proveedor no. Un token de corta vida que autoriza una operación estrecha contra tu API es un buen diseño. Una clave del proveedor con toda tu cuenta detrás no lo es, por corta que sea su vida.
Un relé no es una frontera. Un endpoint que acepta un cuerpo y lo reenvía al proveedor ha añadido un salto y ninguna seguridad. Si tu proxy no autentica, no mide y no compone, es una redirección con latencia extra.
Lo que sostiene este capítulo
- Cuatro cosas salen de tu control cuando el navegador llama al modelo directamente: la credencial, el gasto, el prompt y la identidad. Las dos últimas son las que se olvidan.
- La autenticación tiene que ocurrir donde están los hechos. Un servicio que no es dueño de la tabla no puede validar el token; darle uno sólo hace que el endpoint parezca protegido.
- Un backend-for-frontend tiene cuatro tareas: autenticar, medir, componer y llamar hacia fuera con una credencial que el cliente nunca ve. Haz tres y tienes un relé.
- Las credenciales van en cabeceras. Las URLs se filtran a registros, historial,
Referer, analítica y enlaces pegados. - Trata una caducidad ausente como inválida, no como ilimitada.
- Da a todo fallo de autenticación el mismo mensaje. Distinguirlos es un oráculo gratis para un atacante.
- Devuelve un tipo unión desde la guarda para que el camino de fallo no se pueda olvidar, y carga de golpe lo que el llamante necesitará después.
- El navegador envía parámetros acotados; el servidor compone el prompt. Un campo de texto libre en un endpoint que compone prompts es una caja de texto apuntando a tu modelo.
- La regla es sobre quién decide, no sobre cuántos saltos hay. Llamadas directas del navegador a tus propios endpoints están bien; al proveedor, no.