ZodOps

Anatomy · MCP · 7 min · Aug 25, 2026

Agent SecurityData Security

Sites.Read.All Is the Index

You connected the agent to SharePoint. You did not review the Graph permission the app was granted. Application Sites.Read.All is a tenant crawl, not a search box.

At a glance

Anatomy summary: system, stack, and the miss
SystemInternal agent with an MCP server in front of SharePoint and files
StackMCP, Microsoft Graph, Entra ID
The missApplication Sites.Read.All and Files.Read.All. The agent reads the tenant, not the site the user opened.

The short version: teams review what the model is allowed to say. They skip the Graph permission the MCP server uses to fetch. Application Sites.Read.All is not "the agent can search SharePoint." It is a tenant crawl. Prompt injection is how someone asks.

What you are looking at

An Entra app registration holds the grant. The MCP server is how the agent calls Graph. The launch file hands the server a client id, a tenant id, and a secret, then asks for https://graph.microsoft.com/.default. That scope means: every application permission an admin already consented on this app. The permission list is the door. The file is how the process knocks.

The config

This is a composite, not a dump from one tenant. I keep seeing the same pair in agent hosts and Graph connectors: a SharePoint MCP server with a client secret in env, and an Entra app that has application Sites.Read.All with admin consent.

mcp.json — the job that knocks
{
  "mcpServers": {
    "sharepoint": {
      "command": "npx",
      "args": ["-y", "@platform/mcp-graph"],
      "env": {
        "TENANT_ID": "••••••••-••••-••••-••••-••••••••••••",
        "CLIENT_ID": "••••••••-••••-••••-••••-••••••••••••",
        "CLIENT_SECRET": "••••••••••••••••••••••••••••••••",
        "GRAPH_SCOPE": "https://graph.microsoft.com/.default"
      }
    }
  }
}
prod-graph-mcp — annotated summary of the Entra API permissions. Not a portal export.
Microsoft Graph
  Sites.Read.All          Application
  Files.Read.All          Application
  Mail.Read               Application

Grant type: client credentials
Admin consent: granted 2026-03-12
Site grants: none — .All does not use them

Three lines that matter

Application permissions have no user on the token. Graph evaluates the app, not the person who opened the chat. Sites.Read.All plus Files.Read.All is read on every site collection and every file the tenant will hand to an app. Mail.Read next to them is the same pattern on the mailbox store.

  • Sites.Read.All as Application, with admin consent. That is the crawl. The agent can list and read sites the current user has never opened, including sites no one meant to put behind a chatbot.
  • .default and a client secret. The server is not doing delegated-on-behalf-of. It is the app. The secret in mcp.json is a long-lived copy of that principal — the same miss as AKIA in the env block.
  • The attacker is not a stranger on the internet. It is anyone who can put a document, a page, or a ticket in front of the agent — or anyone who can invoke this MCP server. Hidden text in a SharePoint page the agent was told to summarize is enough.

What it actually grants

Whatever those application permissions cover. Sharing does not apply. The app reads the support knowledge base the pilot needed, and also finance, HR, M&A, and every site nobody has opened since 2019.

  • A fooled agent can read anything this app can read, not anything the current user can read.
  • Files.Read.All is every SharePoint library and every user's OneDrive. The personal drives are the worse exposure. That is where the real files sit.
  • A Purview sensitivity label can still stop the app. Encrypted files need usage rights or a super-user grant. Labeling reaches app-only access. Sharing does not.
  • The secret and the consent outlive the engineer who clicked Grant admin consent in March.

Where these files sit

mcp.json is not Entra. It is the launch file. The API permission list is the Graph grant. A Sites.Selected site permission is the per-site grant that .All skips. SharePoint sharing is who can already see the files. None of those replace the others.

PieceWhere it livesWhat it decides
mcp.jsonLaptop or the agent hostHow the process starts, which tools it exposes, what sits in env
Entra API permissionsThe app registration, after admin consentWhich Graph APIs the app may call, and whether a user is on the token
Site permissionPOST /sites/{id}/permissions — needs Sites.FullControl.All on a separate admin app, or an admin running itWhich site collections the Sites.Selected app may actually touch
SharePoint sharingEach site, library, and fileWho can already see the content — including Everyone except external users

"We connected SharePoint" names the mechanism. It does not name the crawl. If the app has application Sites.Read.All, the agent is not searching a site. It is reading the tenant.

The tighter version

Take .All off the app. Use Sites.Selected. An admin — not this app — then grants read on the one site. That call needs Sites.FullControl.All. Take the secret out of the file. Do not let the model search or send freely.

Orange marks what changed from the permission list above.

prod-graph-mcp — annotated summary. Application. One scope.
Microsoft Graph
  Sites.Selected          Application

Grant type: client credentials
Admin consent: granted
Site grants: required — Sites.Selected allows nothing until you add them
Mail.Read: removed
Files.Read.All: removed
POST /sites/{support-kb}/permissions — the one site this app may read. Not called by prod-graph-mcp.
{
  "roles": ["read"],
  "grantedToIdentities": [
    {
      "application": {
        "id": "••••••••-••••-••••-••••-••••••••••••",
        "displayName": "prod-graph-mcp"
      }
    }
  ]
}

That call is not something this app can make. POST /sites/{id}/permissions needs Sites.FullControl.All on a separate admin app, or an admin running it. Granting a site is a deliberate admin act. That is the point.

Orange marks what changed from the launch file above.

mcp.json — no secret, site pinned. Auth is not in this file.
{
  "mcpServers": {
    "sharepoint": {
      "command": "npx",
      "args": ["@platform/mcp-graph@1.4.2"],
      "env": {
        "TENANT_ID": "••••••••-••••-••••-••••-••••••••••••",
        "CLIENT_ID": "••••••••-••••-••••-••••-••••••••••••",
        "GRAPH_SCOPE": "https://graph.microsoft.com/.default",
        "SITE_ID": "••••.sharepoint.com,••••••••-••••-••••-••••-••••••••••••,••••••••-••••-••••-••••-••••••••••••"
      }
    }
  }
}

This file no longer authenticates the server. On Azure, the process uses a managed identity or a federated credential — the Entra equivalent of the workload role in The Key in the Env Block. That does not work on a laptop. For a local server, use a certificate credential, ideally non-exportable and stored in a key store, or move the server onto a host that can use a managed identity. A missing CLIENT_SECRET is not a complete fix if nothing replaced it.

  • Sites.Selected with no site grant is no SharePoint access. That is the point. You add the site on purpose, with roles: ["read"]. Finance, HR, and the 2019 dump are not in the crawl. If the job is one library or one file, Lists.SelectedOperations.Selected and Files.SelectedOperations.Selected go tighter than a whole site. Same rule: consent grants nothing until you add the resource permission.
  • Pin the package. npx -y @platform/mcp-graph fetches whatever latest is today.
  • SITE_ID in env is hygiene. It only holds if the server refuses other sites. Graph enforces Sites.Selected. A prompt that says "only the support site" is not a control.
  • If a job really needs mail, do not put application Mail.Read on the tenant. Exchange Online RBAC for Applications can limit an app to a set of mailboxes. Removal is still the default.
  • Delegated, on-behalf-of the current user, is the right shape for an interactive agent. It is not a fix for oversharing. Application permissions ignore sharing. A delegated token inherits it: if the site is shared to Everyone except external users, the user can see it, so the agent can too. Fix the sharing. Then pick delegated.

How to tell you are looking at this

  • The Entra app has application Sites.Read.All or Files.Read.All, with admin consent, and no site-permission grants.
  • mcp.json or the connector has CLIENT_SECRET and GRAPH_SCOPE of .default.
  • You cannot name which site the agent is allowed to read, or who is allowed to add a new one.
  • The same app is used by more than one agent, and every call looks like this app ID. Look in the Microsoft Graph activity logs and the unified audit log's SharePoint events. Both record the app ID.
  • A SharePoint search for "Everyone except external users" still returns sites the chatbot was never meant to see.

The takeaway

Do not approve the agent until you can approve the permission list and the site grant. The model will do what the tools allow. The tools will do what this app can read. Sites.Read.All is the index. Start there.