Seguridad y aislamiento por workspace

La seguridad de AI Tasker no es una lista de etiquetas: es una respuesta a cuatro preguntas. Quién puede leer qué, con qué credencial, durante cuánto tiempo y con qué rastro. Esta guía describe el modelo tal como está implementado en el backend y contrastado con el contrato público del servicio el 21 de octubre de 2026: el aislamiento por workspace, el cifrado de las credenciales en reposo, la rotación de claves, el papel de la persona en las decisiones de privilegio, el feed de auditoría con doble redacción y las cuotas que cierran el perímetro. Cada código de error, cada cifra y cada nombre de variable de este texto sale del código del servicio, no de una descripción comercial.

Revisado · Publicado

El modelo de aislamiento por workspace

La unidad de aislamiento es el workspace, no la cuenta ni el agente. Una API key pertenece a un workspace: el que se resuelve en apiKey.workspaceId más, si los hay, los workspaces compartidos declarados en apiKey.workspaceIds. Todo lo que esa key alcanza está dentro de ese perímetro. No hay ninguna vía por la que una key pueda enumerar workspaces ajenos, y la comprobación ocurre en la capa de datos: cambiar de vista en el dashboard no amplía el acceso.

La credencial viaja en Authorization: Bearer atkr_… o en x-api-key: atkr_…, y el servidor no la compara con un valor guardado en claro: la busca por su hash sha256. De ahí se siguen dos comportamientos que conviene distinguir, porque son la diferencia entre "no tienes permiso" y "no existes".

  • 401 INVALID_API_KEY: la key está revocada (revokedAt) o el servidor no la reconoce. La petición se corta en la autenticación, antes de tocar datos de ninguna colección.
  • 403 API key workspace unavailable: la key es válida, pero el workspace al que apunta no se resuelve. La petición pasa la autenticación y se frena en la resolución del workspace.
  • 403 WORKSPACE_ARCHIVED: la key es válida y su workspace existe, pero está archivado por el ciclo de vida (7 días sin actividad). El cuerpo de la respuesta indica la ruta de recuperación: POST /agent/lifecycle/recovery-token, autenticada con la propia API key del agente.

Un 403 WORKSPACE_ARCHIVED no es un problema de credenciales: la key sigue siendo válida y el workspace sigue ahí. Es el estado del workspace el que bloquea el acceso normal, y el cuerpo de la respuesta dice cómo pedir el token de recuperación, que es de un solo uso y dura 900 s. La ventana de recuperación permanece abierta 7 días más desde el archivo.

La jerarquía de datos es la de siempre (workspace → project → task → subtask), pero lo que decide el acceso no es el nodo donde está el dato, sino la credencial que llega: un agente con una key de su workspace escribe en su workspace y en ninguno más, aunque conozca la forma de un identificador ajeno.

Lo que acota de verdad a un agente

Conviene separar lo que una API key declara de lo que el servidor hace. Hoy, lo que acota a un agente son cinco cosas: (1) el workspace al que pertenece su key, (2) las cuotas por workspace, (3) la revocación de la key, (4) el bloqueo del workspace archivado y (5) la autorización humana para ampliar límites o tomar la propiedad del workspace.

Los scopes existen y se guardan en la key: tasks:read, tasks:write, sprints:read, sprints:write y ai:generate. Se ven en el dashboard y en GET /api/apikeys, y por defecto una key creada por una persona lleva tasks:read, tasks:write y ai:generate, mientras que el alta de un agente autónomo lleva tasks:read, tasks:write y sprints:read. Pero no se aplican hoy en ninguna ruta: no hay middleware ni endpoint que los compruebe, y el servidor MCP no filtra las 40 tools por scope, así que tools/list devuelve las 40 con cualquier key válida. Un scope es la intención declarada de la credencial, no un muro.

Si buscas un límite técnico en lugar de una declaración de intenciones, el muro es el workspace más las cuotas. Los permisos y los scopes tienen su propia guía.

Credenciales en reposo

Dos detalles sostienen la parte estática del modelo. El primero: la API key nunca se guarda en claro. Se busca por su hash sha256 (hashApiKey), de modo que ni el almacenamiento de la base de datos ni una lectura indebida de esa base devuelven una credencial utilizable. El segundo: cuando el alta de un agente guarda su credencial, esa credencial se cifra con AES-256-GCM (services/agentCredentialEncryption.js), con un IV aleatorio de 12 bytes y una clave de 32 bytes en hexadecimal tomada de la variable de entorno AGENT_CREDENTIAL_ENCRYPTION_KEY.

  • IV aleatorio de 12 bytes: cifrar dos veces el mismo texto produce dos ciphertexts distintos, así que no se pueden comparar credenciales cifradas para deducir si son iguales.
  • Clave de 32 bytes en hexadecimal: 64 caracteres que salen de la configuración del servicio, no de la base de datos, y que ningún endpoint devuelve.
  • Fallo cerrado: si AGENT_CREDENTIAL_ENCRYPTION_KEY no está configurada, el servicio no cifra en claro ni continúa sin cifrar: responde con el error AGENT_CREDENTIAL_ENCRYPTION_NOT_CONFIGURED.

La ausencia de la clave de cifrado es un fallo de configuración y el comportamiento por defecto es detenerse. Un servicio que guardara credenciales sin cifrar porque falta una variable de entorno sería un incidente; por eso el camino por defecto es cerrar el paso.

Dos lecturas autenticadas, con las dos formas de enviar la credencial. La key viaja en la cabecera y el workspace se resuelve solo a partir de ella.

curl https://api.ai-tasker.theboomer.dev/agent/lifecycle/status \
  -H "Authorization: Bearer atkr_…"

curl https://api.ai-tasker.theboomer.dev/api/apikeys \
  -H "x-api-key: atkr_…"

Rotación de claves

Una credencial que no caduca es una credencial que acaba filtrándose. La rotación está diseñada como una operación en dos manos: el agente pide un token de un solo uso y una persona con sesión de Clerk lo completa. El agente no rota su propia credencial, y esa asimetría es el punto: quien puede leer el workspace no puede decidir por su cuenta cuánto tiempo seguirá viva la credencial.

  1. Solicitud: POST /agent/key-rotation-token, autenticada con la API key del agente, emite un token de un solo uso con 900 s de validez. Ese token no rota nada todavía: solo abre la puerta a la confirmación.
  2. Vista previa: POST /api/agent-key-rotations/preview, con sesión de Clerk, muestra los datos del agente y del workspace implicado sin revelar la clave nueva.
  3. Confirmación: POST /api/agent-key-rotations/confirm, con sesión de Clerk, completa la rotación y entrega la API key nueva, que se muestra una sola vez.
  4. Rotación directa: POST /api/agent-key-rotations/{apiKeyId}/rotate, con cabecera Idempotency-Key, ejecuta el giro en un solo paso desde un cliente de persona y es idempotente.

La rotación cambia el hash de la key y su prefijo. La clave antigua deja de validar en el acto: la siguiente petición que la use recibe 401 INVALID_API_KEY, la misma respuesta que devuelve una clave nunca registrada. Es una rotación real, no un alias de la anterior.

La persona en el bucle

La mitad del modelo que no es código es el gobierno. Ampliar los límites de un agente, reclamar la propiedad de un workspace y aprobar una unidad de pago son acciones de persona, no de proceso. Ninguna de ellas se concede por una redirección: el alta autónoma de un agente llega con humanLinkRequired: true, es decir, el registro por sí solo no otorga ningún privilegio, y el vínculo con una persona se confirma en endpoints autenticados con Clerk.

  • Subir límites: la cuota por workspace la modifica una persona desde su sesión. Un agente que se topa con el techo no puede ampliárselo ni pedir que se lo amplíen.
  • Tomar la propiedad: POST /api/agent-links/preview y POST /api/agent-links/claim exigen sesión de Clerk. La vista previa no consume el token: permite revisar identidad, workspace y rol antes de confirmar el reclamo.
  • Aprobar el pago: la unidad facturable de 2,00 €/mes se aprueba cuando se acepta a una persona, se crea una API key de agente o se añade un workspace del owner. El consumo se contabiliza por operación y los reintentos por error de plataforma no se cobran.

La excepción documentada es x402: una llamada MCP aislada puede liquidarse con créditos sin una cuenta humana previa. Es la vía de pago que no pasa por una confirmación de persona, y tampoco da acceso a un workspace, porque es una llamada suelta y no una credencial.

El feed de auditoría y la doble redacción

Cada petición autenticada deja una fila de uso. El registro ApiKeyUsage guarda apiKeyId, workspaceId, el método, la ruta y el canal — human, cli, llm o agent — de modo que se puede reconstruir quién tocó qué y desde dónde, sin depender de que la propia parte declare lo que hizo.

El feed de actividad aplica dos capas de redacción (services/activityRedaction.js): una por allowlist en el momento de escribir y otra profunda en el momento de leer. La primera decide qué se guarda; la segunda revisa lo guardado antes de devolverlo.

  • contraseñas y API keys completas;
  • cabeceras Authorization y cookies;
  • JWT y tokens OAuth o de sesión;
  • códigos OTP y de reset;
  • tokens de alta, vinculación y rotación;
  • secretos de webhook;
  • claves privadas;
  • ciphertext de credenciales;
  • firmas y payloads x402.

Lo que sobrevive a la redacción es deliberadamente lo no secreto: el identificador del evento, el apiKeyId, el actor (el userId o el canal), el correlationId, las fechas y las etiquetas de campo. Eso es lo que permite auditar el rastro sin reenviar el material sensible.

La redacción es fail-safe. Si falla (un getter que lanza, un ciclo, una estructura ilegible), el feed devuelve un esqueleto mínimo sin el payload crudo y con redactionFailed=true. El contenido nunca se escribe en logs: cuando la redacción no puede completarse, el sistema entrega menos información, no más.

Las cuotas, la última línea de defensa

Los límites se aplican por workspace. Se cuentan dos cosas distintas y conviene no confundirlas: el techo diario de requests autenticados y el cupo semántico mensual de operaciones.

Cuotas por workspace del plan gratuito, con el nombre con el que aparecen en el código.
LímiteValorQué cuenta
dailyAgentOps200 al díaRequests autenticados con API key, por MCP y por HTTP directo
Operaciones de agente100 al mesLecturas y mutaciones de una colección (AGENT_READ, AGENT_MUTATION, 1 unidad cada una)
Operaciones de IA alojada100 al mesPlanificar sprint, generar tareas y desglosar (bucket hosted_ai)
dailyHostedAiOps10 al díaLlamadas diarias a IA alojada
Webhooks3 por workspaceEndpoints de webhook registrados
webhooksPerMinute2 por minutoEntregas de webhook por minuto
Retención de actividad30 díasActividad conservada del workspace

La diferencia entre los dos techos es la clave para leer bien un error de cuota. dailyAgentOps cuenta requests: es el máximo diario de requests autenticados, y la nota oficial del producto es explícita en que no es el cupo semántico mensual. El cupo de 100 operaciones al mes cuenta operaciones semánticas. Y hay operaciones que consumen cero unidades: la entrega de webhook (WEBHOOK_DELIVERED), el reintento por 5xx o timeout (RETRY), la lectura humana en el dashboard (HUMAN_READ) y la llamada MCP pagada vía x402 (X402_MCP_PAID_TOOL). Un agente puede, por tanto, gastar requests y no gastar cuota semántica.

El orden de comprobación también es fijo: primero la cuota de agente (reserveAgentQuota, con su contador requests) y después el techo diario de requests del workspace. Por eso los dos límites se consultan siempre en ese orden, y por eso el catálogo de operaciones que publica la API, con el nombre de cada operación, es la forma fiable de saber qué se está cobrando.

Los límites se aplican por workspace, no por persona ni por API key. En el plan gratuito son 1 workspace, 1 humano y 2 API keys: dos credenciales de agente es el máximo por workspace en ese plan.

Webhooks: firma, sin reintento y secretos

Los webhooks son el canal de salida del sistema hacia terceros y, por eso mismo, el punto donde un dato puede escaparse a un tercero. El cuerpo se firma con HMAC-SHA256 usando el secreto del webhook y se entrega en la cabecera x-ai-tasker-signature: sha256=<firma>, junto a x-ai-tasker-event y x-ai-tasker-event-id, que es el UUID del evento; el campo id del cuerpo repite ese mismo UUID, de modo que el receptor puede correlacionar sin interpretar la cabecera.

  • Timeout de 10 s por entrega y attempts fijado en 1: no hay reintento automático y el reintento lo decide quien recibe.
  • Historial: GET /api/webhooks/:id/deliveries devuelve las 100 últimas entregas con su estado (pending, delivered, failed), su responseCode y su error.
  • Eventos: task.created, task.updated, task.moved y task.archived; el endpoint de prueba dispara webhook.test.
  • El secreto se redacta: los secretos de webhook están en la lista de lo que el feed de auditoría elimina al escribir y al leer.

Control, qué protege y cómo falla

Los controles del modelo, qué protege cada uno y cómo se manifiesta el fallo cuando se activa.
ControlQué protegeCómo falla
Resolución por workspaceQue una key no alcance datos de otros workspaces403 API key workspace unavailable cuando el workspace no resuelve
Revocación de la keyQue una credencial robada deje de servir401 INVALID_API_KEY desde el momento de la revocación
Ciclo de vida del workspaceQue un workspace inactivo no siga accesible403 WORKSPACE_ARCHIVED, con la ruta de recuperación en el cuerpo
Cifrado en reposoQue la credencial guardada no sea legibleError AGENT_CREDENTIAL_ENCRYPTION_NOT_CONFIGURED si falta la clave
Rotación de clavesQue una credencial no viva indefinidamenteLa key antigua recibe 401 INVALID_API_KEY tras el giro
Redacción del feedQue el rastro no arrastre secretosEsqueleto mínimo con redactionFailed=true si la redacción falla
Cuotas por workspaceQue un agente consuma sin límiteSe agota el contador de requests o el cupo de operaciones
Autorización humanaQue ningún proceso se amplíe a sí mismoLa operación exige sesión de Clerk y no se concede por redirección

Checklist para endurecer un workspace de agente

  • Una API key por agente, sin compartirla entre procesos ni entre entornos.
  • Un workspace por agente, para que el aislamiento sea también el límite de cuota.
  • Cuotas revisadas y ajustadas al trabajo real del agente, no dejadas en su valor por defecto.
  • Rotación programada y hecha con la clave vigente, no solo cuando hay un incidente.
  • Clave revocada en cuanto el agente termina su trabajo, y no "por si acaso".
  • Workspace archivado tras la inactividad, dejando correr la ventana de recuperación antes de purgar.
  • Feed de auditoría revisado: qué pidió cada key, con qué canal y desde qué ruta.
  • Secretos de webhook rotados y guardados fuera del código que los usa.

Cómo verificar el modelo en tu propio entorno

Nada de esto hay que darlo por bueno: se comprueba con cuatro lecturas y una rotación de prueba. Enumerar las claves declaradas, leer el estado del ciclo de vida, revisar el feed de actividad y comprobar qué devuelve una llamada con una credencial revocada.

  1. GET /api/apikeys con una key válida: devuelve las claves declaradas del workspace, con sus scopes y su estado. Es el inventario de lo que hay que rotar.
  2. GET /agent/lifecycle/status con la API key del agente: devuelve los plazos del ciclo (aviso, archivo, recuperación) y el estado vigente.
  3. Revisa el feed de actividad: cada fila debe traer apiKeyId, workspaceId, método, ruta y canal, y ningún secreto. Si aparece una cabecera Authorization o un token, la redacción no está haciendo su trabajo.
  4. Rota una clave de prueba y usa la antigua: la respuesta debe ser 401 INVALID_API_KEY y no un error genérico.
  5. Comprueba un workspace archivado: la respuesta debe ser 403 WORKSPACE_ARCHIVED y el cuerpo debe indicar la ruta de recuperación.

Endurecer el workspace de un agente en cuatro pasos

El recorrido mínimo para dejar un workspace de agente con credencial propia, rotación programada, cuota revisada y rastro auditable. Cada paso es un endpoint real del contrato público del servicio.

  1. Crea una credencial propia para el agente

    Da de alta el agente con POST /agent/register (cabecera Idempotency-Key) y quédate con la clave que devuelve, con prefijo atkr_. Esa es la credencial que viajará en Authorization: Bearer atkr_…, y todo lo demás de esta guía se apoya en ella.

  2. Comprueba el perímetro con dos lecturas

    Con GET /api/apikeys mira las claves declaradas del workspace y con GET /agent/lifecycle/status los plazos del ciclo de vida del agente. Si hay más de una credencial para un solo agente, este es el momento de revocarla.

  3. Fija la rotación en el calendario

    Pide el token con POST /agent/key-rotation-token (900 s, un solo uso) y completa el giro con POST /api/agent-key-rotations/confirm bajo sesión de Clerk, o con POST /api/agent-key-rotations/{apiKeyId}/rotate y cabecera Idempotency-Key. Guarda la clave nueva: se muestra una sola vez, y la antigua responde 401 INVALID_API_KEY.

  4. Revisa el rastro y las cuotas

    Baja el feed de actividad y comprueba que cada fila trae apiKeyId, workspaceId, método, ruta y canal, sin secretos. Contrasta el volumen con los contadores del workspace: 200 requests autenticados al día (dailyAgentOps), 100 operaciones de agente al mes y 100 de IA alojada al mes.

Preguntas frecuentes

¿Puede un agente ver los datos de otro workspace?

No. Una API key resuelve un solo perímetro: el workspace de apiKey.workspaceId más los workspaces compartidos declarados en apiKey.workspaceIds. Lo que no está en esa lista no es accesible, y si el workspace no resuelve la respuesta es 403 API key workspace unavailable en lugar de una lista vacía. Los scopes de la key son la intención declarada y hoy no se comprueban en ninguna ruta, así que un scope no retira ninguna tool: el límite técnico es el workspace más las cuotas.

¿Qué pasa si la clave de cifrado de credenciales no está configurada?

El servicio falla cerrado con el error AGENT_CREDENTIAL_ENCRYPTION_NOT_CONFIGURED. No hay modo degradado que guarde la credencial del agente sin cifrar: la ausencia de AGENT_CREDENTIAL_ENCRYPTION_KEY corta la operación en lugar de continuar con un almacén en claro.

¿Cómo sé si un agente ha rebasado un límite?

Hay dos contadores distintos y conviene saber cuál se ha agotado. dailyAgentOps es el máximo de requests autenticados al día por workspace (200), y el cupo semántico mensual es de 100 operaciones de agente y 100 de IA alojada. Cada petición autenticada deja además una fila de uso con su método, su ruta y su canal, de modo que el feed muestra el volumen real y a qué corresponde.

Fuentes

Cifras verificadas contra la API en vivo y las páginas oficiales de cada producto el 21 de octubre de 2026. Los precios de terceros pueden cambiar: contrasta con la página oficial antes de publicar.

Guías relacionadas

Esta guía existe para ser indexada y citada. No está en el menú a propósito.