Add AI to Microsoft 365 the right way — least-privilege, auditable, and security-first.
Enterprises want AI on their Microsoft 365 data — email triage, document summarization, meeting intelligence, compliance review. Most bolt it on insecurely:
- App registrations with Global Administrator or over-scoped
Mail.ReadWritewhenMail.Readis all that's needed - Client secrets stored in
.envfiles, CI/CD variables, or — worse — committed to source control - No Conditional Access policy gating the service principal
- No audit log review, no alerting on token issuance, no lifecycle management
- A breach of the AI layer becomes a breach of the entire M365 estate
This repo exists because the common tutorials skip all of that.
- Registering an Entra ID app with exactly the permissions the workload needs — nothing more
- Storing credentials in Azure Key Vault, never in code or environment variables
- Calling Microsoft Graph from Python and PowerShell with managed identity or certificate auth where possible; client secret only as a documented fallback
- Building AI reasoning on top of Graph responses in a way that keeps the LLM call outside the trust boundary of your M365 tenant
- Logging every Graph call and AI decision to a durable, tamper-evident store
flowchart TD
subgraph User / Automation
U[User or Scheduled Job]
end
subgraph AI Layer ["AI Reasoning Layer (outside M365 trust boundary)"]
LLM[LLM / Orchestration\ne.g. Azure OpenAI, Claude API]
end
subgraph Auth ["Auth & Secrets Boundary"]
AKV[Azure Key Vault\ncertificate or client secret]
ENTRA[Entra ID\nApp Registration\nleast-privilege scopes]
end
subgraph Graph ["Microsoft Graph API"]
GW[/api/v1.0/me/messages\n/api/v1.0/sites\n/api/v1.0/users/]
end
subgraph M365 ["Microsoft 365 Data"]
EX[Exchange Online\nMail / Calendar]
SP[SharePoint / OneDrive]
TEA[Teams]
end
U -->|trigger| LLM
LLM -->|fetch credential at runtime| AKV
AKV -->|scoped token| ENTRA
ENTRA -->|OAuth 2.0 access token| GW
GW --> EX
GW --> SP
GW --> TEA
GW -->|structured payload only\nno raw credentials| LLM
LLM -->|response + audit record| U
The LLM never holds a token or touches M365 directly. Credentials flow only at runtime from Key Vault through Entra — never through the AI layer.
- Least privilege by design — each example lists the exact Graph permission(s) required and justifies why broader scopes are refused
- Secrets never in code — all examples pull credentials from Azure Key Vault or use Workload Identity / Managed Identity;
.envfiles are absent and.gitignore-blocked - Certificate-based auth preferred — client certificates over client secrets where the runtime supports it; secret rotation documented for every fallback
- Conditional Access enforced — app registrations are paired with a CA policy restricting token issuance to known IPs / compliant devices
- Full audit trail — every Graph call is logged (caller identity, endpoint, timestamp, response code) to Azure Monitor / Log Analytics; AI decisions are written to a separate, append-only log
- Token scope validation — runtime assertion that the acquired token contains only the expected scopes before any Graph call is made
- No persistent token storage — access tokens are acquired per-execution and discarded; no caching to disk or shared memory
secure-ai-microsoft365/
├── README.md # This file
├── docs/
│ ├── architecture.md # Detailed component walkthrough and trust-boundary diagram
│ └── security.md # Threat model, permission rationale, Key Vault setup, CA policy
├── examples/
│ ├── email_triage_assistant.py # Python: fetch unread mail via Graph, classify with LLM, write audit log
│ └── Register-LeastPrivilegeApp.ps1 # PowerShell: create Entra app registration with scoped permissions only
└── .gitignore # Blocks .env, *.pfx, *.pem, secrets.*, local.settings.json
- Security engineers evaluating AI integrations and needing a reference implementation that doesn't cut corners
- IT architects at enterprises planning Microsoft 365 Copilot extensions or custom Graph-backed assistants
- Hiring managers looking at a candidate's GitHub — this is what a security-first M365 engineer ships
- Cloud administrators who inherit an over-permissioned app registration and need a model for what "done right" looks like
- Developers new to the Microsoft identity platform who want a correct starting point instead of the insecure quick-start
- Azure tenant with Entra ID (any tier)
- Azure Key Vault (or Managed Identity if running in Azure)
- Python 3.11+ / PowerShell 7.4+
- Microsoft Graph permissions approved by a tenant administrator (see
docs/security.md)
No tenant ID, client ID, or secret is ever committed here. All placeholders follow the pattern <TENANT_ID>, <CLIENT_ID>, contoso.onmicrosoft.com.
This repo is a living reference — new secure patterns get added as they're built:
- SharePoint / OneDrive document RAG (Graph search + grounded LLM answers, sensitivity-label aware)
- Teams bot pattern (proactive notifications + bounded actions, no standing tenant access)
- Power Automate + AI flows (low-code orchestration behind the same least-privilege boundary)
- Microsoft 365 Copilot extension / declarative agent (governed plugin pattern)
- Sentinel / Defender security automation (AI-assisted alert triage)
- Meeting & calendar intelligence (summaries / action items, app-only + scoped)
- A DLP-aware "AI gateway" that strips or blocks regulated data (CUI / PII) before any external LLM call
Suggestions and PRs welcome.
MIT — see LICENSE. Use freely; attribution appreciated.
Built by a USAF Security Forces veteran, Security+ / DoD 8570 IAT II certified — on the principle that AI over enterprise data should be least-privilege and auditable by default, not bolted on and hoped for.