- Merge duplicate DM memories into canonical feedback_dm_wrapping_commands_to_mike (points at the productized discord-dm skill; keeps UA/Cloudflare-1010 + 50109 gotchas); git rm the session-created feedback_dm_wrapped_command_lines duplicate. - feedback_365_remediation_tool: record that Exchange Operator HAS Graph Mail.Send/ Mail.ReadWrite (corrects an earlier "suite has no Mail.Send") + the EXO-vs-Graph token-audience gotcha + Get-MessageTraceV2 + fresh-onboard EXO 401 propagation. - Remove a duplicate MEMORY.md index line --apply-safe added from a false-orphan. - Log the memory-dream false-orphan/dup-index defect to errorlog for skill linting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.0 KiB
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).
DEPRECATED — do NOT consent to customer tenants: fabb3421 ("AI Remediation" / "Claude-MSP-Access", secret msp-tools/claude-msp-access-graph-api.sops.yaml). ~159 perms incl. Defender ATP, so admin consent breaks with AADSTS650052 on any tenant lacking an MDE license. It still works where already consented (e.g. ACG's own tenant — the /mailbox skill reads our own mailboxes with it), but new onboarding MUST use the tiered suite. (Corrected 2026-05-27 during Quantum onboarding — nearly consented the deprecated app to a no-MDE tenant.)
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