Security

Sites.FullControl.All: the permission that lets a vendor rewrite your access plan

Case study: containing a Graph permission that opens up tenant-wide SharePoint governance.

GraphWarden Team·April 24, 2026·8 min read

The scenario

A publicly listed company, 2,000 employees, working on a strategic acquisition. The M&A committee has a dedicated SharePoint site, access restricted through a five-person security group. Documents are labeled Restricted-MA.

A document management ISV has been deployed tenant-wide for 18 months with Sites.FullControl.All, synchronizing retention policies between SharePoint and an external archival system. Standard setup.

A developer at the ISV gets compromised. The attacker uses the application token for three actions, in order. Add a guest user to the M&A site, created on the spot. Download the 14 GB of documents. Delete the guest user, then modify the site's audit columns to remove the add event.

Unified audit retains a tenant-level trace. The site itself shows nothing anymore. The security team only learns about the incident three weeks later, when the acquisition leaks in the press and the source needs to be traced back.

Why Microsoft doesn't block this scenario

Sites.FullControl.All does exactly what its name advertises: complete control over all sites. Modifying permissions is a legitimate operation for a document governance application. Microsoft cannot distinguish a legitimate modification from a malicious one without contextual signal.

The risk is not exfiltration, which Purview DLP would detect. The risk is pivot: using the governance permission to grant other permissions, bypass groups, erase traces. The blast radius of this permission is the entire SharePoint access plan for the tenant.

Microsoft offers a preventive mode via Sites.Selected, which forces the application to request per-site access explicitly. In practice, document governance ISVs do not adopt it because their use case covers the whole tenant by definition. The alternative that works is to selectively forbid risky operations at the proxy layer. Same reasoning as for Files.ReadWrite.All, transposed to governance endpoints.

What the incident looks like with a perimeter in place

With SharePoint calls routed through a governance proxy, the following rules apply. Baseline: default-deny. Any call not covered by an explicit allow is blocked.

Rule 1 — Read scoped to the application perimeter

- name: "Retention ISV — read scope"
  endpoint_pattern: "/sites/*/drive/*"
  http_methods: [GET]
  conditions:
    all_of:
      - caller_identity:
          application_id: "app-retention-isv"
      - object_attribute:
          path: "siteId"
          not_in_tag: ["sensitive:confidential", "sensitive:m-and-a"]
  action: allow

The retention ISV does not need to read M&A sites to do its job. Sites tagged sensitive in the directory are structurally out of reach, even with Full Control granted at the Graph level.

Rule 2 — No permission mutations

- name: "Retention ISV — no permission changes"
  endpoint_pattern: "/sites/*/permissions/**"
  http_methods: [POST, PATCH, DELETE]
  conditions:
    all_of:
      - caller_identity:
          application_id: "app-retention-isv"
  action: block

A document governance app needs to read permissions for its audit work. It does not need to modify them. Explicitly blocking mutations on /permissions removes the pivot mechanic without touching legitimate functionality.

Rule 3 — Protected audit columns

- name: "Retention ISV — no audit column mutations"
  endpoint_pattern: "/sites/*/lists/*/columns/*"
  http_methods: [PATCH, DELETE]
  conditions:
    any_of:
      - object_attribute:
          path: "displayName"
          matches_any: ["Audit*", "Modified*", "CreatedBy*", "Retention*"]
  action: block

Modifying or deleting traceability columns was the attacker's cleanup tool. The rule protects the audit lineage structurally: even the legitimate application cannot erase traces, by design.

Rule 4 — No guests added by the application

- name: "Retention ISV — no guest operations"
  endpoint_pattern: "/sites/*/permissions"
  http_methods: [POST]
  conditions:
    any_of:
      - request_body:
          path: "grantedToV2.user.@odata.type"
          equals: "#microsoft.graph.guestUser"
  action: block

Adding a guest user is a typical vector for granting oneself access to a restricted site. No retention application has a business reason to do that. The rule applies even to operations going through side endpoints: the condition is on the request body, not just the path.

Rule 5 — Operational window

- name: "Retention ISV — scheduled maintenance windows"
  endpoint_pattern: "/sites/*"
  http_methods: [POST, PATCH, DELETE, PUT]
  conditions:
    all_of:
      - time_window:
          timezone: "America/Toronto"
          days: ["Mon", "Tue", "Wed", "Thu"]
          hours: "20:00-23:00"
  action: allow

Retention operations follow an overnight weeknight maintenance cycle. A write at 3 AM on a Saturday is abnormal and blocked. The temporal signal covers the blind spots of the other rules.

Conditions on request_body and object_attribute give a granularity no Microsoft Graph permission exposes natively. See the documentation for the full grammar.

See the rule documentation →

What changes for the decision-maker

Internal pivot is the scenario security teams fear without being able to model it easily. The application is legitimate, the traffic is compliant, the data leaves through an authorized path. Classic detection tools (SIEM, DLP, unified audit) see the activity but do not qualify it as hostile.

With a governance layer, three shifts.

  1. Privilege drops to the strict minimum. Full Control stays granted, but the pivot-capable operations (permissions, audit columns, guests) become unreachable to the application.
  2. The audit lineage is untouchable. Audit columns are protected by rule, not by convention. No application, even compromised, can modify them.
  3. Incident response is scripted. Cutting off a suspect app does not require the vendor, Microsoft, or a redeployment: a global block rule by application_id stops the traffic.

The limits worth naming

The rules rely on site tagging (sensitive, m-and-a). That assumes an up-to-date inventory, maintained either manually by the compliance team or derived from Sensitivity Labels. This work is rarely done systematically, and it is often the first governance project that accompanies a deployment like GraphWarden.

Second limit: blocking all permission modifications by an application kills legitimate automation (for example, a self-service tool that creates a site and adds its owner). Granularity matters. The rules above target a specific retention ISV, not every application.

The takeaway

Sites.FullControl.All is the permission that concentrates the most privilege on the SharePoint surface. Its risk is not measured in exfiltrated gigabytes, but in the capacity to rewrite your access plan without anyone noticing in real time.

For an organization managing sensitive transactions (mergers and acquisitions, litigation, research, intellectual property), bounding this permission with declarative rules is less a technical option than an internal control any serious auditor will expect.

That is exactly why GraphWarden exists.

GraphWarden is built in Quebec, hosted in Canadian regions, and designed for environments where privacy law compliance and Microsoft Graph application control are non-negotiable requirements.