NosotrosServiciosProyectosR&DBlogHerramientasEmpezarContacto

Un manifiesto de agente de Copilot es una frontera de seguridad. Aquí está el verificador.

Todas las propiedades de ámbito de un manifiesto de agente de Microsoft 365 Copilot son opcionales y, en seis casos, omitir una da el ámbito más amplio en lugar del más estrecho. Un validador de JSON Schema aprueba esos manifiestos, porque ninguno tiene nada inválido: falta algo que era opcional. Así que escribimos la comprobación que sí los detecta. Encuentra seis problemas en un manifiesto que pasa todas las demás pruebas.

pH7x Systems® · · 6 min de lectura

Un agente declarativo para Microsoft 365 Copilot no es código. Es un manifiesto JSON que le dice al modelo para qué sirve, qué puede leer y qué puede hacer. El modelo, la orquestación y el recorte por permisos los pone Copilot. Las fronteras las pone usted.

Casi todas las propiedades que definen esas fronteras son opcionales. En seis casos, dejar una fuera no restringe al agente. Le da el ámbito más amplio que existe.

La línea que lo concede todo

Un manifiesto necesita tres campos: name, description e instructions. Todo lo demás son capacidades, y esta es la que más pesa.

json
{
  "capabilities": [
    {
      "name": "OneDriveAndSharePoint",
      "items_by_url": [
        { "url": "https://tenant.sharepoint.com/sites/Tenders/Documents/Live" }
      ]
    }
  ]
}

Ahora borre el array items_by_url. La documentación es explícita: sin items_by_url ni items_by_sharepoint_ids, el agente pasa a poder acceder a todas las fuentes de OneDrive y SharePoint de la organización.

No a algunas. A todas. Y la versión equivocada no da ningún error: produce un agente que funciona, responde preguntas y fundamenta las respuestas en todos los sitios a los que tenga acceso la persona que habla con él.

Copilot sigue aplicando el recorte de seguridad de esa persona, así que el agente no lee nada que ella misma no pudiera abrir. Esa garantía es real, y escribimos sobre la misma frontera en el asistente de IA que solo lee lo que el usuario puede leer. Pero recortar no es delimitar. Quien tenga acceso amplio se queda con un agente de licitaciones respondiendo desde archivos de recursos humanos y actas del consejo, porque nada le dijo que no.

La misma regla aparece seis veces:

Capacidad Omitir esto Y el agente obtiene
OneDriveAndSharePoint items_by_url, items_by_sharepoint_ids Todas las fuentes de SharePoint y OneDrive de la organización
GraphConnectors connections Todos los conectores de Copilot de la organización
TeamsMessages urls Todos los canales, reuniones y conversaciones
Meetings items_by_id Todas las reuniones
WebSearch sites La web abierta
Email folders El buzón entero

Es una opción defendible: un agente que por defecto no leyera nada sería inútil recién instalado. Pero invierte el hábito de quien escribe configuración de seguridad, donde una regla que falta suele significar denegado. Aquí, el silencio significa sí.

La trampa que sobrevive a conocer el patrón

El esquema 1.8 añadió dos capacidades de escritura, EmailActions y MeetingActions. La primera cubre triaje, envío supervisado, eliminación, reglas de bandeja de entrada, respuesta automática y gestión de carpetas.

La documentación dice que EmailActions funciona con independencia de Email y que las restricciones de ámbito definidas en Email, incluidas folders, shared_mailbox y group_mailboxes, no se le aplican. Por eso este manifiesto no hace lo que se pretendía:

json
{
  "capabilities": [
    { "name": "Email", "folders": [ { "folder_id": "inbox" } ] },
    { "name": "EmailActions" }
  ]
}

La lectura queda limitada a la bandeja de entrada. Borrar, mover y crear reglas no queda limitado a nada. No hay manera de delimitar EmailActions: el objeto tiene exactamente una propiedad, su nombre. La decisión es binaria y hay que tomarla a propósito.

Ese manifiesto es obra de alguien que estaba teniendo cuidado. Tener cuidado en el sitio equivocado da un archivo que parece prudente y no lo es.

La comprobación que sí lo detecta

Un validador de JSON Schema aprueba todos los manifiestos equivocados de aquí atrás, porque ninguno tiene nada inválido. Falta algo que era opcional, que es distinto, y es lo único que aquí importa.

O sea, la revisión tiene que buscar ausencias, y eso es trabajo mecánico, lo que significa que no debería recaer en una persona leyendo con atención a las cinco de la tarde.

python
# Each capability, the properties that scope it, and what happens without them.
SCOPE = {
    "OneDriveAndSharePoint": (("items_by_url", "items_by_sharepoint_ids"),
                              "every SharePoint and OneDrive source"),
    "GraphConnectors": (("connections",), "every Copilot connector"),
    "TeamsMessages":   (("urls",),        "every channel, meeting and chat"),
    "Meetings":        (("items_by_id",), "every meeting"),
    "WebSearch":       (("sites",),       "the open web"),
    "Email":           (("folders", "shared_mailbox", "group_mailboxes"),
                        "the whole mailbox"),
}
# Write capabilities. There is no way to scope them: the choice is binary.
WRITES = {"EmailActions": "delete, move and create inbox rules",
          "MeetingActions": "book meetings and change the calendar"}

def check(manifest: dict) -> list[tuple[str, str]]:
    found = []
    caps = {c.get("name"): c for c in manifest.get("capabilities", [])
            if isinstance(c, dict)}

    for name, cap in caps.items():
        if name in SCOPE:
            props, everything = SCOPE[name]
            if not any(cap.get(p) for p in props):
                found.append(("OPEN", f"{name} without {' or '.join(props)}:"
                                      f" reads {everything}"))
        if name in WRITES:
            found.append(("WRITES", f"{name} can {WRITES[name]}"))

    # Email is scoped, EmailActions is not, and the file gives no sign of it.
    if "EmailActions" in caps and any(caps.get("Email", {}).get(p)
                                      for p in SCOPE["Email"][0]):
        found.append(("TRAP", "Email is scoped but EmailActions ignores that limit"))

    if not manifest.get("behavior_overrides", {}).get(
            "special_instructions", {}).get("discourage_model_knowledge"):
        found.append(("INVENTS", "answers from model knowledge when sources are silent"))
    if not manifest.get("disclaimer", {}).get("text"):
        found.append(("NO NOTICE", "the reader is not told where answers come from"))
    return found

Añádale veinte líneas que leen un archivo y devuelven un código de salida distinto de cero, y ejecútelo contra un manifiesto que todas las demás herramientas aprueban:

bash
$ python check_agent.py tender-desk.json
Tender Desk  schema v1.8
  OPEN      OneDriveAndSharePoint without items_by_url or items_by_sharepoint_ids:
            reads every SharePoint and OneDrive source
  WRITES    EmailActions can delete, move and create inbox rules
  OPEN      WebSearch without sites: reads the open web
  TRAP      Email is scoped but EmailActions ignores that limit
  INVENTS   answers from model knowledge when sources are silent
  NO NOTICE the reader is not told where answers come from

6 to review
$ echo $?
1

Delimite bien ese mismo agente y se calla y sale con cero. Eso es lo que le permite frenar una publicación, en vez de quedarse en un documento que nadie abre.

La línea TRAP es la que justifica el script entero. Solo salta cuando alguien ha delimitado Email y después ha puesto EmailActions al lado, que es precisamente el manifiesto que escribe una persona cuidadosa.

Lo ejecutamos contra un manifiesto realista para nuestro propio tenant, con el sitio de SharePoint bien delimitado, y aun así tuvo algo que decir: TeamsMessages había entrado sin un array urls, así que un agente pensado para una biblioteca de documentos podía leer todos los canales y conversaciones a los que pertenece el usuario. Aquello no lo decidió nadie. Fue una línea que nunca estuvo.

Lo que no detecta

Conviene decirlo con claridad, porque una comprobación que exagera lo que cubre es peor que ninguna.

Lee un archivo. No sabe si la URL de SharePoint apunta a una carpeta con doce documentos o a un sitio con cuarenta mil, y esa diferencia pesa más que todo lo que el script mide. No puede decir si las instrucciones son buenas. Y no dice nada sobre si las personas que usan el agente deberían siquiera tener acceso a ese contenido, que es una cuestión de permisos anterior al agente y que le sobrevive.

Lo que se ahorra es la parte de la revisión en la que una persona es peor: reparar en algo que no está.

Por qué esto importa

El atractivo de un agente declarativo es que no hay código que mantener. Un manifiesto, unas cuantas fuentes de conocimiento, y algo genuinamente útil para un equipo que nunca tendría una aplicación hecha a medida.

Es cierto, y es también por eso que el ámbito pesa más aquí que en una aplicación. Nadie revisa un archivo JSON como revisa un pull request. No hay batería de pruebas. No hay compilador que avise de que el array que quedó fuera significa el tenant entero.

Ahora hay, al menos, un script.

Seguir leyendo

Desarrollo y automatización

SPFx antes de la versión 1.0: tres web parts nuestras en los samples oficiales de Microsoft

La developer preview de SharePoint Framework salió en agosto de 2016. La versión 1.0 llegó en febrero de 2017. Nuestra primera contribución al repositorio oficial de samples de Microsoft 365 es de octubre de 2016, cinco meses antes de que hubiera una 1.0 sobre la que construir. Tres de nuestras web parts están hoy en ese repositorio, y esto es lo que hace cada una.

·5 min de lectura
Desarrollo y automatización

Seguimos midiendo a los ingenieros por la parte que se automatizó.

En 2025 DORA tiró su propio marcador. Los niveles low, medium, high y elite, que la industria citó durante una década, dieron paso a siete arquetipos de equipo sobre ocho medidas. Eso no es una nota metodológica. Es el marco de medición más usado del software admitiendo que los instrumentos ya no leen aquello que decide si un sistema sobrevive.

·10 min de lectura
IA y agentes

Un agente es una integración. Cien son un problema de gobernanza.

Los agentes obtuvieron identidad de producción antes de tener una forma estable de registrar lo que hicieron. Microsoft Entra Agent ID ya está disponible, con patrocinadores y fechas de caducidad. Las convenciones de OpenTelemetry para las trazas de los agentes siguen siendo experimentales. Esa diferencia no es un detalle: decide qué se puede poner en producción este año, y convierte una cuestión de IA en una cuestión de gobernanza.

·11 min de lectura