---
name: 365 Remediation Tool Reference
description: "365 remediation tool" = ACG's tiered ComputerGuru Graph/EXO app suite via the /remediation-tool skill; NOT CIPP, and NOT the deprecated fabb3421 single-app
type: feedback
---

When the user says "365 remediation tool" or "remediation tool", they mean ACG's direct Graph/Exchange tooling against customer tenants via the **`/remediation-tool` skill** (`.claude/skills/remediation-tool/`). This is NOT CIPP.

**App suite (current — tiered):** Security Investigator `bfbc12a4` (Graph read + EXO read), Exchange Operator `b43e7342` (EXO write), User Manager `64fac46b` (user/license/MFA/pw write), Tenant Admin `709e6eed` (high-priv directory), Defender Add-on `dbf8ad1a` (MDE-licensed tenants ONLY). Secrets in `msp-tools/computerguru-*.sops.yaml`. Client-credentials auth; tenant ID via OpenID discovery (or the `*.onmicrosoft.com` domain when the primary domain isn't verified). Use the lowest tier needed. Each app is consented per-tenant (URLs in `references/gotchas.md`); privileged ops also need directory roles assigned to the SP in that tenant (`onboard-tenant.sh`).

**DELETED — gone, do not reference:** `fabb3421` ("AI Remediation" / "Claude-MSP-Access", secret `msp-tools/claude-msp-access-graph-api.sops.yaml`). Removed from the azcomputerguru.com tenant **2026-06-14**; every token request now returns **AADSTS700016**. It previously had ~159 perms incl. Defender ATP (admin consent broke with AADSTS650052 on no-MDE tenants). Any skill still pointing at it is broken — repoint to the suite. (Original deprecation: 2026-05-27 Quantum onboarding.)

**ARCHITECTURE — two mail paths, kept SPLIT on purpose (decided 2026-06-21, Mike).** `/mailbox` (ACG own-mail) uses the single-tenant `1873b1b0` app; CLIENT mail send uses the suite's Exchange Operator `b43e7342`. They CANNOT be merged onto `/mailbox` because `1873b1b0` is single-tenant (azcomputerguru.com only) — it has no SP in client tenants. The reverse merge (everything on exchange-op, which IS multi-tenant + already has Mail.Send + consented in the ACG home tenant since 2026-06-05) was considered and rejected: it would put casual own-mail on a tenant-wide EXO-write remediation app (privilege creep) and lose `Contacts.ReadWrite` (exchange-op doesn't have it). Least-privilege wins — keep the split.

**MAIL.SEND ALREADY EXISTS IN THE SUITE — settled, NOT an open decision (do not re-raise).** The **Exchange Operator** tier (`exchange-op`, `b43e7342-5b4b-492f-890f-bb5a4f7f40e9`) holds Graph **`Mail.Send` + `Mail.ReadWrite` + `MailboxSettings.ReadWrite`**. The suite CAN send mail in any consented tenant via Graph `POST /users/<upn>/sendMail` (IR victim-notification). No separate app to provision, nothing "blocked", no pending click-through. Watch the token-audience gotcha below (line on Exchange-Online vs Graph audience). This replaced the deleted `fabb3421` for IR mail; `/mailbox` (ACG own-mail) separately uses the dedicated app `1873b1b0` (next paragraph).

**ACG OWN-mailbox reads/sends (`/mailbox`) — dedicated app `1873b1b0-3377-485c-a848-bae9b2f8f1f5`** ("ComputerGuru Mailbox", vault `msp-tools/computerguru-mailbox.sops.yaml`, Mail.ReadWrite + Mail.Send + Contacts.ReadWrite, azcomputerguru.com single-tenant). Token via `get-token.sh azcomputerguru.com mailbox` (a tier in get-token.sh; cert-preferred). This is what REPLACED fabb3421 for `/mailbox`. Its SP is **disabled when idle** → a token 401 "account is disabled" means enable the SP first. (`/mailbox` command doc repointed to it 2026-06-17 — it had been left on the dead fabb3421.)

**Why (original):** user clarified "remediation tool" != CIPP after a wrong CIPP navigation. **How to apply:** prefer the `/remediation-tool` skill — it wraps tenant resolution, token caching, breach check, sweep, gated remediation, and consent/onboarding URLs (`references/gotchas.md`, `graph-endpoints.md`, `checklist.md`).

### Directory Role Requirements (discovered 2026-04-01)

Graph API permissions alone are NOT sufficient for privileged operations. The service principal also needs Entra directory roles assigned per-tenant:

| Operation | Required Directory Role |
|-----------|----------------------|
| Password reset | User Administrator |
| Exchange transport rules, mailbox permissions | Exchange Administrator |

**Roles assigned so far:**
- Valleywide Plastering (5c53ae9f...): User Administrator
- Dataforth (7dfa3ce8...): User Administrator, Exchange Administrator
- azcomputerguru.com (ce61461e...): full set assigned 2026-06-05 — Sec-Inv + Exch-Op = Exchange Administrator; Tenant Admin = Conditional Access Administrator; User Manager = User Administrator + Authentication Administrator.

**For new tenants:** `onboard-tenant.sh <domain>` assigns the directory roles programmatically (Tenant Admin tier) — no manual portal step needed. The app cannot self-assign; the Tenant Admin SP does it.

**GOTCHA — pre-2026-04-20 tenants have NO directory roles.** The directory-role assignment block was added to `onboard-tenant.sh` in commit cd50117a on **2026-04-20**. Before that, "onboarding" only did app consent + Graph/EXO API permissions. So any tenant onboarded before that date has full app permissions but **zero directory role assignments** — Graph reads work, but **Exchange REST (quarantine, Get-Mailbox, message trace) and other privileged ops 401** until you re-run `onboard-tenant.sh`. This is NOT a removal/breach — the roles were simply never assigned, and with no Entra ID P2 there's no PIM to auto-expire anything. ACG's own tenant hit exactly this on 2026-06-05 (EOP quarantine check 401'd). **Re-run `onboard-tenant.sh` on any tenant onboarded before 2026-04-20** — Valleywide, Dataforth, Cascades are prime candidates to verify proactively. Confirm actual state with `roleManagement/directory/roleAssignments?$filter=principalId%20eq%20'<sp-oid>'&$expand=roleDefinition` (tenant-admin token; classic endpoint, no P2 needed — the PIM `roleAssignmentSchedules` endpoints return `AadPremiumLicenseRequired` without P2).

**BUG (fixed 2026-06-05):** `onboard-tenant.sh role_assigned()` had an unencoded space in its `$filter` (`principalId eq '...'`), so the query always failed → function always returned false → script always printed "MISSING -> ASSIGNING" and leaned on the conflict-tolerant POST for idempotency (assignment still worked, but PRESENT/MISSING reporting was meaningless). Fixed to `%20`. The old TODO blaming PIM was a misdiagnosis.

### Exchange Online REST API

For Exchange cmdlets (Get-TransportRule, Add-MailboxPermission, Get-MessageTraceV2, Get-DkimSigningConfig, New-Mailbox, etc.), use scope `https://outlook.office365.com/.default` and POST to `https://outlook.office365.com/adminapi/beta/$TENANT_ID/InvokeCommand` with `{"CmdletInput":{"CmdletName":"...", "Parameters":{...}}}`. (`Get-MessageTrace` is deprecated → use `Get-MessageTraceV2`.) Fresh-onboard EXO app-only auth can 401 for ~15-60 min until the Exchange Admin role + `Exchange.ManageAsApp` replicate, even though Graph already works — wait and retry, it's propagation not misconfig.

### Sending mail + the token-audience gotcha (corrected 2026-06-15)

Exchange Operator (`b43e7342`) ALSO holds **Graph** `Mail.Send` + `Mail.ReadWrite` + `MailboxSettings.ReadWrite` — so the suite CAN send as any mailbox in a consented tenant via Graph `POST /users/<upn>/sendMail`. (This corrects an earlier "the suite has no Mail.Send" conclusion — that came from reading the wrong token.) `get-token.sh exchange-op` returns an **Exchange-Online**-audience token (outlook.office365.com) whose `roles` claim does NOT list Graph scopes; to call Graph you must mint a **Graph**-audience token with the app's client creds (`scope=https://graph.microsoft.com/.default`). **Never conclude a permission is missing from a wrong-audience token — check the app's actual Graph app-role assignments first.** [[reference_cloudflare_access]]
