Ein Copilot-Agentenmanifest ist eine Sicherheitsgrenze. Hier ist der Prüfer dafür.
Jede Eigenschaft, die in einem Microsoft 365 Copilot Agentenmanifest den Geltungsbereich setzt, ist optional, und in sechs Fällen gewährt das Weglassen den größten statt den kleinsten Bereich. Ein JSON-Schema-Validator lässt solche Manifeste durch, weil an keinem etwas ungültig ist: Es fehlt etwas Optionales. Also haben wir die Prüfung geschrieben, die sie findet. Sie findet sechs Probleme in einem Manifest, das jeden anderen Test besteht.
Ein deklarativer Agent für Microsoft 365 Copilot ist kein Code. Er ist ein JSON-Manifest, das dem Modell sagt, wozu er da ist, was er lesen und was er tun darf. Modell, Orchestrierung und Berechtigungsfilter kommen von Copilot. Die Grenzen kommen von Ihnen.
Fast jede Eigenschaft, die diese Grenzen setzt, ist optional. In sechs Fällen schränkt das Weglassen einer davon den Agenten nicht ein. Es gibt ihm den größtmöglichen Geltungsbereich.
Die Zeile, die alles gewährt
Ein Manifest braucht drei Felder: name, description und instructions. Alles Weitere sind Fähigkeiten, und diese wiegt am schwersten.
{
"capabilities": [
{
"name": "OneDriveAndSharePoint",
"items_by_url": [
{ "url": "https://tenant.sharepoint.com/sites/Tenders/Documents/Live" }
]
}
]
}
Löschen Sie nun das Array items_by_url. Die Dokumentation ist eindeutig: ohne items_by_url und ohne items_by_sharepoint_ids kann der Agent auf jede OneDrive- und SharePoint-Quelle der Organisation zugreifen.
Nicht auf einige. Auf alle. Und die falsche Fassung wirft keinen Fehler: Sie erzeugt einen Agenten, der funktioniert, Fragen beantwortet und seine Antworten auf jede Site stützt, auf die die Person im Chat Zugriff hat.
Copilot wendet weiterhin die Berechtigungen dieser Person an, der Agent liest also nichts, was sie nicht selbst öffnen könnte. Diese Garantie ist echt, und über dieselbe Grenze haben wir in der KI-Assistent, der nur liest, was der Nutzer lesen darf geschrieben. Aber Filtern ist kein Begrenzen. Wer breiten Zugriff hat, bekommt einen Ausschreibungsagenten, der aus Personalakten und Vorstandsprotokollen antwortet, weil ihm nichts das Gegenteil gesagt hat.
Dieselbe Regel taucht sechsmal auf:
| Fähigkeit | Das weglassen | Und der Agent bekommt |
|---|---|---|
OneDriveAndSharePoint |
items_by_url, items_by_sharepoint_ids |
Jede SharePoint- und OneDrive-Quelle der Organisation |
GraphConnectors |
connections |
Jeden Copilot-Connector der Organisation |
TeamsMessages |
urls |
Jeden Kanal, jede Besprechung und jeden Chat |
Meetings |
items_by_id |
Jede Besprechung |
WebSearch |
sites |
Das offene Web |
Email |
folders |
Das ganze Postfach |
Das ist vertretbar: Ein Agent, der standardmäßig nichts läse, wäre frisch installiert nutzlos. Aber es kehrt die Gewohnheit derer um, die Sicherheitskonfiguration schreiben, wo eine fehlende Regel üblicherweise verweigert bedeutet. Hier bedeutet Schweigen ja.
Die Falle, die auch überlebt, wenn man das Muster kennt
Schema 1.8 hat zwei Schreibfähigkeiten ergänzt, EmailActions und MeetingActions. Die erste umfasst Triage, überwachten Versand, Löschen, Posteingangsregeln, automatische Antwort und Ordnerverwaltung.
Die Dokumentation hält fest, dass EmailActions unabhängig von Email arbeitet und dass die auf Email gesetzten Einschränkungen, darunter folders, shared_mailbox und group_mailboxes, dafür nicht gelten. Dieses Manifest tut also nicht, was gemeint war:
{
"capabilities": [
{ "name": "Email", "folders": [ { "folder_id": "inbox" } ] },
{ "name": "EmailActions" }
]
}
Das Lesen bleibt auf den Posteingang begrenzt. Löschen, Verschieben und das Anlegen von Regeln überhaupt nicht. Es gibt keine Möglichkeit, EmailActions zu begrenzen: Das Objekt hat genau eine Eigenschaft, seinen Namen. Die Entscheidung ist binär und muss bewusst getroffen werden.
Dieses Manifest stammt von jemandem, der sorgfältig war. Sorgfalt an der falschen Stelle ergibt eine Datei, die umsichtig aussieht und es nicht ist.
Die Prüfung, die es findet
Ein JSON-Schema-Validator lässt jedes fehlerhafte Manifest von oben durch, weil an keinem etwas ungültig ist. Es fehlt etwas Optionales, was etwas anderes ist, und hier das Einzige, worauf es ankommt.
Die Prüfung muss also nach Abwesenheit suchen, und das ist mechanische Arbeit, was heißt, dass sie nicht bei einem Menschen liegen sollte, der um fünf Uhr nachmittags aufmerksam liest.
# 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
Ergänzen Sie zwanzig Zeilen, die eine Datei lesen und einen Exit-Code ungleich null zurückgeben, und lassen Sie das Ganze gegen ein Manifest laufen, das jedes andere Werkzeug durchwinkt:
$ 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
Begrenzen Sie denselben Agenten ordentlich, dann schweigt er und endet mit null. Genau das erlaubt ihm, eine Veröffentlichung zu stoppen, statt in einem Dokument zu liegen, das niemand öffnet.
Die Zeile TRAP rechtfertigt das ganze Skript. Sie schlägt nur an, wenn jemand Email begrenzt und danach EmailActions danebengesetzt hat, also genau bei dem Manifest, das ein sorgfältiger Mensch schreibt.
Wir haben es gegen ein realistisches Manifest für unseren eigenen Tenant laufen lassen, mit sauber begrenzter SharePoint-Site, und es hatte trotzdem etwas zu sagen: TeamsMessages war ohne urls-Array hinzugekommen, ein Agent für eine einzige Dokumentbibliothek konnte also jeden Kanal und jede Unterhaltung lesen, denen sein Nutzer angehört. Das hat niemand entschieden. Es war eine Zeile, die nie da war.
Was sie nicht findet
Das gehört klar gesagt, denn eine Prüfung, die ihre Abdeckung übertreibt, ist schlimmer als gar keine.
Sie liest eine Datei. Sie weiß nicht, ob die SharePoint-URL auf einen Ordner mit zwölf Dokumenten oder auf eine Site mit vierzigtausend zeigt, und dieser Unterschied wiegt schwerer als alles, was das Skript misst. Sie kann nicht beurteilen, ob die Anweisungen gut sind. Und sie sagt nichts darüber, ob die Menschen, die den Agenten nutzen, überhaupt Zugriff auf diesen Inhalt haben sollten, eine Berechtigungsfrage, die älter ist als der Agent und ihn überdauert.
Was sie abnimmt, ist der Teil der Prüfung, in dem ein Mensch am schlechtesten ist: zu bemerken, dass etwas nicht da ist.
Warum das zählt
Der Reiz eines deklarativen Agenten liegt darin, dass es keinen Code zu pflegen gibt. Ein Manifest, ein paar Wissensquellen und etwas wirklich Nützliches für ein Team, das nie eine eigens gebaute Anwendung bekommen hätte.
Das stimmt, und genau deshalb wiegt der Geltungsbereich hier schwerer als in einer Anwendung. Niemand prüft eine JSON-Datei so, wie er einen Pull Request prüft. Es gibt keine Testsuite. Es gibt keinen Compiler, der sagt, dass das ausgelassene Array den ganzen Tenant bedeutet.
Jetzt gibt es wenigstens ein Skript.


