Departure Watch
Metadata-only review of a departed employee's mailbox. An Exchange management scope was measured on 2026-08-08 not to bound the application — see How access is scoped before relying on it. A dedicated-app ApplicationAccessPolicy was measured on 2026-08-17 to bind. Last updated 2026-08-22
Setup-TATERDepartureWatch.ps1 provisions and proves an access boundary. It does not create a case, and a provisioned mailbox is not a monitored one. Monitoring begins only after an organization records a policy attestation, a named human authorizes the specific case with a written justification, and the case is activated — and none of that is possible until TATER Security sets the platform kill switch, which is currently off. See what is live and what is not.
What this is
Departure Watch is designed to read message metadata only from a named departed employee's mailbox — subject lines, timestamps, participant addresses, folder, and a link that opens the original message in Outlook. It never reads message bodies, previews, attachment contents, or attachment file names.
Every case is bound to a named window with an explicit end date, after which collection stops, and to a named list of recipients recorded with a written reason for why those people. Recipients must be addresses this organization already knows — an organization member or a People record — and an address that is neither is refused by name at case creation rather than silently dropped. A briefing lists a named individual's client correspondence; a mistyped address that happens to be deliverable would send it to a stranger.
The window is bounded at both ends, and the start bound is the one worth understanding. windowStart is not a label — it is what TATER asks Microsoft Graph for, as a receivedDateTime ge filter — so it decides how far back into the mailbox the feature actually reaches. It may begin at most 90 days before the subject's last day (or before today, if the People record carries no last day), and a case may span at most 180 days in total. Both are refused at case creation and re-checked at activation against the current People record, because a last day corrected by HR after the draft was written would otherwise widen the lookback silently. The 90-day figure is deliberately the same as the maximum metadata retention: collecting deeper history than the platform will keep is incoherent, so the two move together.
Access is fenced at the Microsoft 365 layer, not by TATER's own good behaviour: Exchange Online is told which mailboxes the application may touch, and TATER verifies that grant rather than creating it.
What is live today, and what is not
This is the most important section on the page, and it changes as the feature ships — check its date against your release rather than assuming. The left-hand column is what exists today. The right-hand column is what still stands between a provisioned mailbox and a monitored one.
| Live today | Not yet available |
|---|---|
The Exchange Online permission configuration: a scoping security group, a management scope filtered on that group, and a read-only Application Mail.Read role assignment. Setup-TATERDepartureWatch.ps1 creates and verifies all three. Note the word configuration — a management scope was measured on 2026-08-08 not to act as a boundary for app-only Graph mail (ADO #1873). |
Any user interface. Departure Watch does not appear anywhere in the TATER app. Every operation below is reachable only over the REST API. |
A positive and a negative Test-ServicePrincipalAuthorization, with membership-cache delay reported as NOT RUN rather than allowed to look like a pass. These are not evidence that the boundary holds. On 2026-08-08 all three of Test-ServicePrincipalAuthorization, Test-ApplicationAccessPolicy and an Entra grant audit passed for a tenant whose application could nonetheless read an out-of-scope mailbox. Only the empirical read settles it. |
Any MCP tool. No agent-facing tool exposes this feature, and the authorization and activation paths are deliberately excluded from MCP — an agent must not be able to authorize surveillance of a person. |
An audit of the application's Entra application-permission grants on both Microsoft Graph and Office 365 Exchange Online: confirming the required Graph mail-read grant is present, and flagging anything beyond what a read-only feature needs — write and send grants, and every mailbox-reaching role on the Exchange resource, full_access_as_app included. |
The platform kill switch. DEPARTURE_WATCH_ENABLED is not set on the API, and the compliance gate denies unless it is exactly '1'. Until TATER Security sets it, every timer below runs, evaluates the gate, and correctly refuses. Nothing an administrator does in the product clears this. |
The REST API. Ten /api/departure-watch/* routes: the org policy attestation and its revocation, per-case authorization, case create and read, activate, deactivate, and a gate-status endpoint that returns the block codes rather than a bare boolean. |
Any case that has not cleared the gate. A case needs an attested org policy, a per-case human authorization with a real business justification, a departed or departing subject, and a provisioned Exchange scope. Missing any one of those, activation is refused and the reasons are returned. |
| Collection, briefing and retention on a schedule. Collection every 15 minutes, briefing generation daily at 06:20 UTC, retention enforcement daily at 07:10 UTC. All three evaluate the gate per case before doing anything, so an attestation that expires mid-window stops collection on the next tick rather than at the end of it. | — |
| Briefing delivery to the case's recorded recipient list, which is enforced server-side from the case and cannot be influenced by report content or by a caller. A partial delivery is recorded as partial; a re-run does not re-send. | — |
Provision the Exchange boundary first, before anything else. Activation verifies this provisioning and deliberately refuses to create it: TATER cannot add a mailbox to its own scoping group, because an application that can widen its own scope is not fenced by it. A case whose mailbox has not been provisioned is refused with exchange-scope-not-provisioned, and no amount of configuration inside the product will clear that. The negative test and the Entra grant audit are the parts most often skipped, and they are the parts that make the boundary evidence rather than assertion.
The complete HTTP surface
Fourteen routes, listed in full because a partial list is how an approved scope and a built scope come apart. Eleven live in departureWatch.ts; the last three live in the timer files, so anyone auditing this feature by reading the routes file alone would miss them — and they are the most operationally potent of the set.
| Route | Role | What it does |
|---|---|---|
GET /departure-watch/gate | Auditor | Org-level gate status and block codes. |
GET /departure-watch/attestation | Auditor | Read the policy attestation. |
POST /departure-watch/attestation | Admin, org member only | Record it. Identity is server-owned. |
POST /departure-watch/attestation/revoke | Admin | Revoke it. Blocks every case at the next tick. |
GET /departure-watch/cases | Auditor | List cases. Audited — this discloses every monitored person. |
GET /departure-watch/cases/{id} | Auditor | Case detail, gate, authorization. Audited. |
POST /departure-watch/cases | Admin | Create or edit a draft. Never starts reading. |
POST /departure-watch/cases/{id}/authorization | Admin, org member only | The per-case human authorization. |
POST /departure-watch/cases/{id}/activate | Admin, org member only | Begins reading the mailbox. Gated four ways. |
POST /departure-watch/cases/{id}/deactivate | Admin | Stops reading. Deliberately the cheapest privilege here. |
POST /departure-watch/cases/{id}/descope | Admin, org member only | Attest that the mailbox left the Exchange group. |
POST /departure-watch/collect/run | OrgAdmin | Forces a mailbox read cycle outside the schedule. |
POST /departure-watch/reports/run | OrgAdmin | Generates a briefing and emails it to the recipient list, on demand. |
POST /departure-watch/retention/sweep | OrgAdmin | Runs the retention sweep, which can delete collected evidence. |
“Org member only” means the caller must hold a real membership in the organization — a TATER SuperAdmin who merely selected it in the org picker is refused. A managing service provider with a genuine membership is not: an MSP-run offboarding is a real scenario, and the person authorizing it legitimately works for the MSP. What the check excludes is the platform vendor, who has no relationship to the firm and whose name on a monitoring authorization would be worse than no record at all.
There are no MCP tools for any of this, and there will not be. An automated caller must not be able to authorize surveillance of a person.
Before you begin — the legal gate
This feature reads an identified person's mailbox. Treat the legal question as the first step of the project, not the last.
The platform is designed to refuse collection without a recorded policy attestation for the organization: a statement that the firm has appropriate notice and policy in place for this monitoring, in the jurisdictions listed, as reviewed by named counsel. That attestation should not be recorded until someone with authority has answered the questions in the notice and counsel brief:
- Departure Watch — employee-monitoring notice, attestation, and counsel brief. It lives in the TATER repository at
Docs/DEPARTURE_WATCH_NOTICE_TEMPLATE.md. It is deliberately not linked and not published — ask TATER Security for the current copy, and read the warning below before you send it anywhere.
It covers whether existing acceptable-use policy already reaches automated recurring review of a departed employee's account, whether notice obligations attach before monitoring begins, which states' rules apply, third-party content from clients and plan participants who never agreed to firm policy, retention and litigation hold, the scope of the recipient list, and whether transmitting message metadata to an AI provider is acceptable under client confidentiality obligations.
It is a drafting aid for counsel, not legal advice, and it is stale in both directions, which is why it needs re-measuring rather than editing. It was written while the application half did not exist, so its status annex reports no REST endpoints, no scheduler, no delivery path, and no way to record an attestation — all four of which now ship. Sending it as-is would understate the feature to the very reviewer whose job is to assess it.
It is also wrong in the older direction on narrower points, and those are the ones that would mislead a reviewer toward comfort: it describes attachment count and total size as read (they are not obtainable) and inbox rules as read once per run (nothing fetches them). Counsel answering questions about behaviour the software does not have, while unaware of behaviour it does, is the failure this warning exists to prevent. Ask TATER Security for the revised copy rather than working from an older one.
One point in it has changed direction and must not be repeated from an older reading of this page. Until recently the agent-authored mode had no code behind it, and this page said so — no message metadata left the tenant for any AI provider, because nothing was capable of sending it. That is no longer unconditionally true. A case in agent report mode can now have a summary written by an external routine, and such a routine reads the collected metadata — subject lines, counterparties, timestamps — and submits it to whichever model it runs on in order to write that summary. Nothing inside TATER does this: no TATER timer, endpoint or MCP tool sends mail metadata to a model, and cases in the default raw mode are unaffected. But a firm running the agent mode is operating a transmission of client-correspondence metadata to a third-party AI provider, under its own agreements with that provider, and counsel should be told so explicitly rather than reassured from this page's earlier wording.
Two points that should be stated plainly to whoever signs the attestation:
- TATER records the attestation. It does not verify or endorse it. The platform serves firms in different states with different obligations and is not positioned to determine any firm's compliance. The attestation is a statement by the firm, stored and audit-logged by the platform.
- Expiry stops collection. The attestation carries an expiry date and the gate treats a lapsed attestation as a block, not as a reminder banner.
Per-case, the design additionally requires a human authorizer, a business justification of at least 40 characters, and an explicit acknowledgement of the folders, window, and retention limits. Machine principals — API keys and service identities — are refused as authorizers by an explicit machine-principal check in the compliance gate (departureWatchGate.ts).
Be precise about what that check is, because the difference matters to anyone relying on it: it enumerates the known machine-identity shapes and rejects them. It does not require positive proof of an interactive Entra sign-in, because no such signal exists on the platform's user record today — the gate's own header states that residual gap rather than papering over it, and notes that an auth path arriving unlabelled would not be structurally caught. "Interactively authenticated" is the design intent, not a property the code can currently verify.
The attestation and the per-case authorization each have a write path now, but there is no screen and no MCP tool for either — they are reachable only over the REST API. The absence of an agent-facing tool is deliberate and permanent, not a gap waiting to be filled: an automated caller must not be able to authorize surveillance of a person.
On both paths, and on revocation, the identity is taken from the authenticated session and refused by name if supplied in the request body. A machine principal is rejected on all three — including an MCP key that carries a genuine human's UPN, because a key is not a person sitting at a keyboard. Answering the counsel questions is still the first step; the difference now is that you can store the answer, and storing it is what unblocks activation.
What is read, and what is never read
The reader requests exactly one projection from Microsoft Graph. This is it, verbatim:
id, internetMessageId, conversationId, subject,
receivedDateTime, sentDateTime,
from, sender, replyTo, toRecipients, ccRecipients,
hasAttachments, parentFolderId, webLink
webLink is a URL that opens the original message in Outlook on the web. It is metadata, not content, and it is what lets a briefing point at a message instead of copying one. Opening the link requires the reader's own Exchange access to that mailbox; if they do not have it, the link does not work for them. There is exactly one copy of the correspondence and it stays in Exchange.
The fields that are unreachable by construction
Five keys are forbidden outright:
body, bodyPreview, uniqueBody, attachments, contentBytes
These are not filtered out after retrieval. They are rejected at two independent layers:
- On the request. The projection is validated before the first read of every folder pass. A forbidden key in the
$selectthrows rather than being stripped. - On the response. Each message is checked for the presence of a forbidden key. If one appears, the message is rejected with a reason naming the field — and the caller escalates that rejection to fail the entire folder pass rather than skipping the message, on the grounds that dropping messages would understate activity, which is the dangerous direction.
Both boundaries are covered by unit tests that run as a gated step of the API deployment pipeline, so a widened projection fails the build rather than shipping quietly.
Why this shape matters for a benefits and audit practice. The mailbox holds plan-participant data. Retrieving message bodies and then redacting them means the data has already left the mailbox and, in any AI-summarized design, would already have left the tenant — and no downstream redaction can undo an egress that has happened. Building so the content is never retrieved is a materially stronger position than building so it is retrieved and filtered.
What is genuinely not obtainable
Some things an admin might expect are not missing by choice; they cannot be had under this boundary.
| Not available | Why |
|---|---|
| Attachment count and attachment total size | Microsoft Graph's v1.0 message resource exposes neither. Obtaining either requires expanding or fetching the attachments collection, which is on the forbidden list. In production these values are always absent — and they are represented as absent, never as zero, because "no attachments" and "we cannot know" are different facts. |
| Message or body size | Graph exposes no size property. Deriving one would mean selecting the body of every message. |
| Attachment file names | Covered by the same ban on the attachments collection. |
| Display names of participants | Only the email address half of each participant is kept. |
| Direction, for archive folders | Folder location decides direction for Inbox and Sent Items. An archive holds both, so direction is inferred from the recipient lists and reported as unknown when it cannot be decided. That value is deliberate and must not be collapsed into a guess. |
Three of the six detectors cannot produce a finding in the current build, and all three say so on the briefing rather than contributing to a clean result. The field carrying them is required on the report type, so a pass that omits it does not compile:
- large-attachment-external — attachment size is not obtainable under the metadata boundary, so the check has no input at all. Reported unavailable whenever no outbound message carries a known size, including on a cycle with no outbound mail: the reason it cannot run is structural and has nothing to do with how much mail the cycle happened to see.
- send-from-dormant-mailbox — needs to know whether the owner's sign-in is blocked and the mailbox has no delegates. Neither is recorded on the case, and neither is derivable from mail metadata.
- forwarding-rule-present — inbox rules are never fetched anywhere in the platform, so there is nothing to inspect. This is the one to read carefully: a forwarding rule on a blocked account keeps exfiltrating with no sign-in and leaves nothing in Sent Items, so no other check on the page substitutes for it. It is disclosed as un-run, but disclosed is not the same as covered.
The remaining three — personal-webmail sends, cross-client domain reuse, and competitor-domain contact — do work. Competitor-domain contact reports itself unavailable when no competitor domains are configured for the case, which is the default.
How access is scoped
Everything in this section describes what the Exchange configuration is intended to do. On 2026-08-08 it was tested empirically for the first time, and it did not do it. An app-only Graph read of a mailbox outside the watch group returned 200 where a 403 [RAOP] was expected — meaning the application could read every mailbox in that tenant.
Nothing was misconfigured. The application held exactly one mail-reaching Graph permission, exactly one Exchange role assignment scoped to the watch group, a well-formed scope reporting IsValid: True, a group containing only the intended mailbox, and no full_access_as_app. There was no defect to correct — the premise was wrong.
This also reverses the reasoning in the note below about the email-intake feature. Its [RAOP] 403 was read as proof that scoping works; the consistent explanation is that RBAC for Applications denies an app with no role assignment, and once any assignment exists the Graph permission's tenant-wide reach applies. Adding a scoped assignment did not scope that feature — it unblocked it.
Do not treat the steps below as establishing confinement. Provision them, then run the empirical read against both an in-scope and an out-of-scope mailbox, and do not enable monitoring unless the out-of-scope one returns 403. If it returns 200, the cheapest containment is removing the mail permission from the application — not an Application Access Policy, which scopes the whole app and has broken unrelated features.
The tenant enforces Exchange Online RBAC for Applications in default-deny mode. An application with a Graph token still cannot read a mailbox unless Exchange has been told it may. The chain has three links, and each is meaningless without the others:
A security group holds the watched mailboxes
Default name sg_tater_departure_watch. This group is the fence. Everything downstream is defined relative to its membership, so the question "which mailboxes can this application read?" has exactly one answer: the ones in this group.
A management scope is filtered on that group's distinguished name
Default name TATER-DepartureWatch, with the filter MemberOfGroup -eq '<group DN>'. The filter takes the group's distinguished name — not its SMTP address, alias, or GUID. Getting that wrong produces a filter that matches nothing, which fences everyone out and looks exactly like a propagation delay.
A read-only role assignment binds the application to that scope
Application Mail.Read, scoped to the management scope. Not Mail.ReadWrite, not Mail.Send. Departure Watch reads metadata; it has no reason to be able to modify or send anything, and an assignment that could would outlive the watch window.
Direct members only
MemberOfGroup matches direct members. Nesting a group inside the watch group adds nobody to the scope. To watch ten mailboxes, ten mailbox objects go in this group.
Propagation is 30 minutes to 2 hours
Exchange evaluates MemberOfGroup against a cached view of membership. A mailbox added minutes ago will legitimately test out of scope for 30 minutes to 2 hours. That is expected and does not mean the scope is broken. The setup script tracks whether the current run changed anything and reports this state as NOT RUN rather than as a pass or a failure — see verifying.
Revocation happens in Microsoft 365, not in TATER
Because access is evaluated from group membership, removing the mailbox from the watch group removes it from the scope. The revocation is enforced by Exchange, not by TATER choosing to stop asking. That is the property that makes the window meaningful: when a case ends, take the mailbox out of the group. Expect the same membership-cache timescale to apply to the removal, and confirm it with the negative test rather than assuming it was instant.
These are two different facts and it is worth being blunt about the gap between them. Completed means TATER stopped reading — the gate blocks and the case stops being polled. It says nothing about whether the application can still read that mailbox, because that depends entirely on group membership, and nothing in TATER removes a mailbox from the group. That is deliberate: an application that can widen its own scope is not fenced by it, so TATER can verify the group and never write to it.
The consequence, left untracked, compounds quietly. A firm six departures into two years has six former employees' mailboxes still readable by the application, while every case in the register reads completed — a compliance record asserting the opposite of the access state.
So each case now carries an Exchange access state alongside its status:
| Exchange access | Meaning |
|---|---|
in-scope | Monitoring is or was live. Set at activation, when the readability probe has just confirmed the mailbox is in the group. |
awaiting-descope | Action outstanding. TATER has stopped; a human must remove the mailbox from the group. Set on deactivation and on the automatic completion when a window expires — which is the one nobody watches, and therefore the one that gets forgotten. |
descoped | A named person confirmed the removal, with a timestamp. |
Record the confirmation with POST /api/departure-watch/cases/{id}/descope. TATER does not verify it — it cannot read the group any more than it can write to it — so this is your attestation, and the audit entry says exactly that rather than implying a check was made. Do the negative test first if you want evidence rather than recollection.
There is one more permission in the chain, and it is easy to get backwards. The application also needs a Microsoft Graph Mail.Read application permission, admin-consented. That grant is what makes the client-credentials token carry mail scope at all. Without it nothing is readable: Graph rejects the call with a 403 before Exchange RBAC is ever consulted, so the group, the scope and the role assignment above accomplish nothing on their own.
The two permissions answer different questions, and both must be answered:
- The Graph application permission decides whether the token may ask about mail at all.
- The Exchange management scope decides which mailboxes it may reach — on a tenant that enforces RBAC for Applications.
An earlier version of this page, and of the setup script, said the opposite: that an Entra mail grant unions with the Exchange scope and therefore defeats it. That was wrong. The sibling email-intake feature has shipped exactly this combination since 2026-06-05 — tenant-wide Graph mail grants alongside a management scope bound to a single mailbox — correctly scoped to that one mailbox the entire time. The [RAOP] 403 that the Exchange setup exists to remediate is itself the proof: the tenant-wide Graph grant was already consented, and every read still failed until the scoped Exchange role assignment landed. A Graph grant that overrode the Exchange scope would have made that failure impossible.
The Exchange scope narrows which mailboxes an application can reach only where the tenant enforces RBAC for Applications. Where it does not, there is nothing narrowing anything, and the Graph grant is effectively tenant-wide. Nothing in the configuration tells you which of those two worlds you are in — and Test-ServicePrincipalAuthorization cannot tell you either, because it reports the stored RBAC configuration, not enforced state.
Only one thing settles it: an empirical negative read. Take an app-only token for this application and ask Graph for a mailbox that is not in the watch group.
| Result of the negative read | What it means |
|---|---|
403 whose body carries the [RAOP] fingerprint | Bounded. Exchange refused on this application's behalf. This is the result you want. |
200 | Unbounded — the real danger. The application can read a mailbox outside the watch group. Treat as an active exposure. |
403 with no [RAOP] fingerprint | No Graph mail grant, or consent is missing. Nothing can be read at all — a broken feature, not a bounded one. |
The setup script cannot perform this read. It signs in as an administrator with a delegated token, and the question is what the application's own token can reach — which needs that application's client credentials, which the script does not have and will not handle. So it reports boundedness as NOT RUN and prints the exact commands. It never reports PASS on it. See auditing the grants for the commands.
Auditing the Entra grants
The setup script performs this audit automatically, using read-only Graph scopes. It inspects two resources, because mailbox data can be reached from either one and they are separate objects in Entra. They play opposite roles for this feature — one holds the permission it needs, the other holds nothing it needs:
| Resource | Resource app id | What the audit expects to find |
|---|---|---|
| Microsoft Graph | 00000003-0000-0000-c000-000000000000 | Required: exactly one mail-read role — Mail.Read (or Mail.ReadBasic.All / MailboxItem.Read.All). Flagged: Mail.ReadWrite, Mail.Send, MailboxSettings.ReadWrite — more than a metadata reader needs. |
| Office 365 Exchange Online | 00000002-0000-0ff1-ce00-000000000000 | Nothing. The reader speaks Graph only, so every mailbox-reaching role here is surplus: full_access_as_app, EWS.*, IMAP.*, POP.*, and this resource's own Mail.* roles. |
The audit records three separate checks, because these are three different questions and one verdict cannot answer them all:
| Check | PASS when | FAIL when |
|---|---|---|
| Graph mail-read grant present (REQUIRED) | A Graph mail-read role is granted and consented. | None is. The feature cannot read any mailbox — not an exposure, but a broken feature. |
| Entra grants are least privilege | Nothing beyond read on either resource. | Any write, send, or Exchange-resource mailbox grant. Least privilege applies regardless of scoping: a write grant on a read-only feature is a real finding however well fenced. |
| Entra grant boundedness (empirical read) | Never, from this script. It always reports NOT RUN. | — |
Mail.Read grant
It is the permission the feature requires. Removing it does not tighten the boundary — it stops all collection, silently, and the failure looks like a scoping problem rather than a missing grant. If you are working through findings, remove the excess grants and leave the read grant alone.
full_access_as_app on the Office 365 Exchange Online resource grants the application full access to every mailbox in the tenant. Nothing in this feature needs it, and whether RBAC for Applications bounds it on your tenant is exactly what the empirical negative read settles — Test-ServicePrincipalAuthorization cannot tell you, because it reports the Exchange RBAC assignment rather than Entra grants.
On this deployment it is the likeliest real-world gap, not a theoretical one. Departure Watch reuses a shared application registration that already carries Exchange.ManageAsApp on this very resource along with dozens of Graph roles, granted over time for compliance scanning. A mailbox-reaching role added to that pile for some other integration is invisible to every Exchange-side check on this page.
Performing the empirical negative read
This is the step the script cannot do for you, and the only one that distinguishes an application bounded by RBAC for Applications from one that is genuinely tenant-wide. Run it where the application's client secret is available — the setup script never handles one.
$body = @{
client_id = '<application (client) id>'
client_secret = '<client secret>'
scope = 'https://graph.microsoft.com/.default'
grant_type = 'client_credentials'
}
$tok = (Invoke-RestMethod -Method POST -Body $body `
-Uri "https://login.microsoftonline.com/<tenant id>/oauth2/v2.0/token").access_token
# NEGATIVE READ - a mailbox that is NOT in the watch group.
try {
$r = Invoke-WebRequest -Method GET -Headers @{ Authorization = "Bearer $tok" } `
-Uri "https://graph.microsoft.com/v1.0/users/<mailbox NOT in the group>/mailFolders/inbox"
"UNBOUNDED - HTTP $($r.StatusCode). This app can read outside the watch group."
} catch {
$code = $_.Exception.Response.StatusCode.value__
"HTTP $code : $($_.ErrorDetails.Message)" # expect 403 containing [RAOP]
}
Then run the same request against a mailbox that is in the watch group, and expect 200. A 403 on both proves nothing about boundedness — that is what a missing grant looks like, not what a fence does.
To inspect the grants by hand:
# The enterprise application (service principal) object id
az ad sp show --id <application (client) id> --query id -o tsv
# Its application-permission grants, across every resource
az rest --method GET --uri "https://graph.microsoft.com/v1.0/servicePrincipals/<that id>/appRoleAssignments"
Each assignment carries a resourceDisplayName and an appRoleId GUID — not a role name. Pick out every assignment whose resource is one of the two above, then resolve the GUID against that resource's own appRoles collection to learn what it actually grants:
# Role names published by Office 365 Exchange Online
az rest --method GET --uri "https://graph.microsoft.com/v1.0/servicePrincipals(appId='00000002-0000-0ff1-ce00-000000000000')" `
--query "appRoles[].{id:id, value:value}"
An assignment you cannot resolve to a role name is not a clean result. Treat it as unproven and resolve it before concluding the application is bounded.
This is a shared application registration — the same per-tenant application used for Graph compliance scanning, and on many tenants for email intake, which legitimately needs Mail.ReadWrite and Mail.Send. A grant that is excess for Departure Watch may be load-bearing for something else, and removing it breaks that silently. Enumerate what is configured against this application first, then remove:
Remove-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId <enterprise application object id> `
-AppRoleAssignmentId <assignment id>
The setup script reports and explains the grant, prints this exact command for each one it finds, and does not remove anything. It holds read-only Graph scopes and cannot perform the removal even if asked — a human decides.
Running the setup script
Get the script
The script is published at a public URL — no TATER repository access required. This is the path to hand to a client or tenant admin. From a PowerShell 7 session on a workstation that can sign in to the target tenant:
irm https://www.tatersecurity.com/Setup-TATERDepartureWatch.ps1 -OutFile .\Setup-TATERDepartureWatch.ps1
The script contains no secrets. It provisions and verifies objects in the tenant the runner signs into, and prints its results to that runner's console. Alternatives:
- Direct download in a browser: https://www.tatersecurity.com/Setup-TATERDepartureWatch.ps1
- From the repository root, if you have TATER-Security access:
Setup-TATERDepartureWatch.ps1
Read it before you run it. It is a script that grants an application read access to a person's mailbox, and it is written to be read.
Prerequisites
- PowerShell 7. The Microsoft Graph PowerShell modules no longer support Windows PowerShell 5.1.
- Modules:
ExchangeOnlineManagementandMicrosoft.Graph.Applications. The script installs them for the current user if they are missing. - An existing application. The script does not create an app registration. Departure Watch reuses the organization's existing per-tenant credentials application — the same one already used for Graph compliance scanning — so that one consent decision covers one identity instead of two. You pass its application (client) id.
- An enterprise application for it in this tenant. If the service principal does not exist, the script stops and tells you how to create it rather than creating it itself: a script that provisions the identity it then grants mailbox read access to is not a control.
- A Microsoft Graph
Mail.Readapplication permission on that app, admin-consented. This is required and the script does not grant it — it audits for it and FAILs if it is missing. Without it the client-credentials token carries no mail scope, Graph rejects every read before Exchange RBAC is consulted, and everything else on this page accomplishes nothing. GrantMail.Readonly; notMail.ReadWrite, notMail.Send. - A mailbox that is not in the watch group, licensed and real, to use as the negative test subject.
Required roles
- Exchange Administrator — for the management scope and the role assignment.
- Global Reader or Application Administrator — to read the application's Entra permission grants for the audit. Only read-only Graph scopes are requested (
Application.Read.All,Directory.Read.All). - Or Global Administrator, which has both.
Parameters
| Parameter | Required | Default | What it does |
|---|---|---|---|
-AppId | Yes | — | The application (client) id of the existing per-tenant credentials application. Not an app registration object id, not a service principal object id. |
-Mailbox | No | none | A mailbox to add to the watch group. Omitting it provisions the fence before anyone is inside it, which is a legitimate first step — the positive test then reports NOT RUN rather than inventing a subject. |
-GroupName | No | sg_tater_departure_watch | The security group holding the watched mailboxes. |
-GroupPrimarySmtpAddress | No | derived from the group name plus the tenant's default accepted domain | Pass explicitly in a multi-vanity-domain tenant where the default accepted domain is not the one you want on this object. |
-ScopeName | No | TATER-DepartureWatch | The Exchange management scope name. One scope serves the whole group. |
-NegativeTestMailbox | No, but strongly encouraged | none | A mailbox that must not be readable. This is the Exchange-side evidence that the scope bounds the application rather than merely permitting it. It reports the stored RBAC configuration, not enforced state, so it is strong evidence and not proof — the empirical negative read is the proof, and no script parameter can produce it. Omitting this is reported as NOT RUN, never as a pass. |
-GroupManagedBy | No | none | Who may manage the watch group. This is boundary-relevant: group membership is the surveillance scope, so whoever can add a mailbox to this group can place someone under monitoring. The group is created with MemberJoinRestriction and MemberDepartRestriction set to Closed regardless; the script reads all three back and FAILs if the gate is open, including on a pre-existing group it did not create. |
-ServicePrincipalObjectId | No | resolved from Graph | Supply the enterprise application object id by hand if you cannot sign in to Graph from this session. Provisioning proceeds, but both Entra grant checks (12 and 13) then report NOT RUN — including the one that would tell you whether the required mail-read grant exists at all. |
-VerifyOnly | No | off | Run every check and change nothing in your tenant. Anything missing is reported as a failure rather than created. One exception, on your own machine rather than in the tenant: a missing ExchangeOnlineManagement or Microsoft.Graph.Applications module is still installed for the current user, because a run that cannot import its own modules cannot inspect anything. |
-UseBrowserAuth | No | off (device code) | Use interactive browser sign-in for Graph instead of device code. |
-WhatIf / -Confirm | No | — | Standard PowerShell support. Granting an application read access to a person's mailbox should be previewable before it happens. |
The command
.\Setup-TATERDepartureWatch.ps1 -AppId <application (client) id> `
-Mailbox departing.manager@contoso.com `
-NegativeTestMailbox unrelated.person@contoso.com
Preview it first if you prefer:
.\Setup-TATERDepartureWatch.ps1 -AppId <application (client) id> `
-Mailbox departing.manager@contoso.com -WhatIf
The script is idempotent. Re-running changes nothing that already matches and re-runs every verification check.
What it does, in order
- Verifies or installs the two required modules.
- Connects to Microsoft Graph with read-only scopes (device code by default).
- Resolves the enterprise application object id for your
-AppIdand prints it, labelled as such. Stops if none exists. - Connects to Exchange Online.
- Runs
Enable-OrganizationCustomizationif the organization is dehydrated, polling up to five minutes. - Finds or creates the watch group.
- Optionally adds
-Mailboxto it, idempotently. - Resolves the group's distinguished name and finds or creates the management scope.
- Registers the Exchange service principal and assigns
Application Mail.Read, scoped. - Runs the positive authorization test.
- Runs the negative authorization test.
- Audits the application's Entra grants on both Microsoft Graph and Office 365 Exchange Online: confirms the required Graph mail-read grant is present, and flags anything beyond read.
- Prints the commands for the empirical negative read it cannot perform itself, and records that check as NOT RUN.
- Prints a results table, then a block stating plainly that nothing is being monitored, and exits with a code that reflects the worst result in the table.
An application with tenant-wide mailbox read passes every positive check in this script, identically to a correctly-fenced one. If you skip -NegativeTestMailbox, boundedness has not been demonstrated — the script says so in the strongest terms it has and records the check as NOT RUN. Run it against any licensed mailbox that is not in the watch group:
Connect-ExchangeOnline
Test-ServicePrincipalAuthorization -Identity <application (client) id> `
-Resource someone@yourdomain.com # expect InScope False
Understand what that buys you. It is configuration evidence: the cmdlet reports the stored Exchange RBAC configuration, not enforced state. Together with a clean least-privilege audit it means the boundary is configured as bounded — which is worth having, and is not the same as observing the application fail to read something. That observation is the empirical negative read, and the script cannot make it for you.
What correct output looks like
The script ends with a results table. Every check resolves to exactly one of PASS, FAIL, or NOT RUN; no code path lets a skipped check render as a pass, and a check that never got recorded at all is materialised as NOT RUN so it cannot quietly shrink the table. The fourteen checks, in the order they are recorded, are:
- Enterprise application resolved
- Exchange organization customization
- Watch group present
- Watch group membership is gated
- Mailbox added to watch group
- Management scope bound to the group
- Exchange service principal registered
- Role '
Application Mail.Read' scoped to '<your scope name>' - App's other Exchange role assignments
- POSITIVE test (access works)
- NEGATIVE test (access is BOUNDED)
- Graph mail-read grant present (REQUIRED)
- Entra grants are least privilege
- Entra grant boundedness (empirical read)
These are the check names verbatim, so they can be searched for or quoted in a support request. Only check 8 carries values that vary per run — it interpolates the role and your -ScopeName.
Checks 12–14 replaced a single check named "Entra mailbox-access grant audit" in earlier versions. That check failed on the presence of a Graph mail grant, which is the permission the feature requires; if you are comparing against an older run or an older copy of this page, that verdict was inverted.
Check 14 is always NOT RUN. Boundedness needs an app-only read the script cannot perform with an administrator's delegated sign-in, so it reports the check unproven and prints the commands rather than certifying something it did not observe. The best a script-only run produces is thirteen PASS and one NOT RUN, which exits 1.
Do not treat that 1 as a gate to route around. It is an accurate statement that one thing remains unproven, and the fix is to run the negative read, not to re-run the script.
The table is followed by the tenant, application id and name, enterprise application object id, group name, scope name, and the assigned role, then by a block headed EXCHANGE SIDE ONLY — nothing is being monitored.
If a future version adds a check, a run that is one short will still look like the clean run you remember. Read the status column and the exit code rather than the total — a PASS count is only meaningful against the number of checks that particular version emits, which the table itself states. This page said "twelve" until the grant audit was split into three.
A NOT RUN is not one thing — read which check it is on
A first run commonly comes back with several NOT RUNs, and the ones an admin is most likely to see mean completely different things. Do not treat them as the same result.
| NOT RUN on | What explains it | What to do |
|---|---|---|
| POSITIVE test (check 10) | The membership cache, legitimately. This run changed something, and Exchange evaluates MemberOfGroup against a view that is 30 minutes to 2 hours behind. This one is benign and expected. |
Wait, then re-run with -VerifyOnly. If it is still NOT RUN or FAIL after a run that changed nothing, propagation no longer explains it. |
| NEGATIVE test (check 11) | Two different causes, and they need opposite responses. (a) You omitted -NegativeTestMailbox, or the subject you gave has no mailbox or resolves to a member of the watch group — the check never happened. (b) The negative subject came back out-of-scope but the POSITIVE test did not pass either. Everything reporting out-of-scope is exactly what an assignment that has not propagated looks like, so a False here proves nothing about boundedness — only that nothing is readable yet. |
Treat boundedness as unproven either way. For (a), re-run with a real mailbox outside the group; waiting changes nothing. For (b), wait for propagation and re-run with -VerifyOnly once the positive test passes — the script prints that exact command. |
| Graph mail-read grant (check 12) or least privilege (check 13) | Graph sign-in did not happen, a resource service principal could not be resolved, the role map came back empty, an app role id did not map to a known role, or a mail-shaped Graph role could not be classified. The enumeration did not complete; it did not come back clean. | Re-run with a working Graph sign-in, as Global Reader or higher. An unrun audit is not a clean audit. Note that check 12 reporting FAIL is a different and more urgent thing: it means no Graph mail grant exists and the feature cannot read anything at all. |
| Entra grant boundedness (check 14) | Always. Not a fault, not something to fix by re-running. The script holds an administrator's delegated sign-in; the question is what the application's own app-only token can reach. | Perform the empirical negative read. It is the only step that separates an application bounded by RBAC for Applications from one that is genuinely tenant-wide. |
So a given PASS count is not a single expected shape. Thirteen PASS with only check 14 NOT RUN is the clean result. Twelve PASS with the POSITIVE test also pending propagation is a normal first run. Twelve PASS with the NEGATIVE test NOT RUN means the Exchange scope has not been shown to bound anything — the script prints that conclusion explicitly rather than leaving the count to be read as a near-pass. This is why the exit code and the status column, not the count, are what to judge a run by.
Exit codes
The script sets a meaningful exit code so a scheduled or piped run can be judged without reading the table:
| Exit code | Meaning |
|---|---|
0 | Every check PASSED. Not reachable from a script-only run — check 14 is always NOT RUN. Do not gate on -eq 0; you will never see it. |
1 | Nothing FAILED, but at least one check is NOT RUN. If check 14 is the only NOT RUN, the script says so explicitly and this is the clean result. If anything else is also NOT RUN — a missing negative test, an incomplete grant enumeration — then a 1 is not a pass with a caveat. Read the table. |
2 | At least one check FAILED. Two different kinds of bad arrive here: an active exposure (failed negative test, a role assignment outside the scope, a grant beyond least privilege) and a broken feature (no Graph mail-read grant, so nothing can be read at all). Both need action; they need opposite actions. |
In PowerShell, read it from $LASTEXITCODE after the call. Gate automation on -le 1, and treat the empirical negative read as a separate, deliberate step rather than something a script can discharge.
The Exchange Online and Microsoft Graph sessions are deliberately left open when the script finishes, so that every remediation command it printed can be pasted directly into the same session. Disconnect when you are done:
Disconnect-ExchangeOnline -Confirm:$false
Disconnect-MgGraph
Verifying it worked
Come back in 30 minutes to 2 hours and re-run in verify mode. This changes nothing in your tenant and re-runs every check:
.\Setup-TATERDepartureWatch.ps1 -AppId <application (client) id> `
-NegativeTestMailbox unrelated.person@contoso.com -VerifyOnly
Telling "not propagated yet" from "misconfigured"
This distinction is implemented in the script rather than left to the reader, and it is worth understanding because the two states look identical in the raw cmdlet output.
| What you see | Did this run change anything? | Reading |
|---|---|---|
Positive test returns InScope False | Yes — the group, scope, membership, or assignment was created or changed | NOT RUN. Inconclusive. The membership cache is 30 minutes to 2 hours. A False result right now is expected and does not mean the scope is broken. Re-check with -VerifyOnly. |
Positive test returns InScope False | No — e.g. a -VerifyOnly re-run | FAIL. Propagation does not explain this. Confirm the mailbox is a direct member of the watch group, and that the role assignment's CustomResourceScope is your scope name. |
Negative test returns InScope False, and the positive test passed | Either | PASS. This is the result you want: one mailbox in, one mailbox out. |
Negative test returns InScope False, but the positive test did not pass | Either | NOT RUN. Everything reporting out-of-scope is exactly what an assignment that has not propagated at all looks like. It shows nothing is readable right now; it does not demonstrate boundedness. Re-run once the positive test passes. |
Negative test returns InScope True | Either | FAIL — treat as an active exposure. The application can read a mailbox outside the watch group. See below. |
| Negative test subject has no mailbox, or is a member of the group | Either | NOT RUN. A non-existent mailbox is out of scope for every application, and a group member is in scope by design. Neither result proves anything. Pick a real, licensed mailbox outside the group. |
If the negative test reports InScope True
There is no effective scoping. Do not proceed on the assumption that Departure Watch is bounded. The likely causes, in the order worth checking:
- A second management role assignment on the same application with no
CustomResourceScope, or one scoped to something far wider. - A mailbox-reaching grant on the Office 365 Exchange Online resource —
full_access_as_app,EWS.*,IMAP.*,POP.*. Nothing in this feature needs any of them. - The tenant does not enforce RBAC for Applications at all. In that case no Exchange management scope bounds anything, and the Graph mail grant is effectively tenant-wide. The empirical negative read is what tells you this.
Mail.Read grant is not the cause
It is tempting to reach for it, because it is tenant-wide in Entra and it is the most visible mail permission on the application. But it is the grant the feature requires, and it is present on correctly-fenced deployments too. Removing it will make the negative test keep failing while collection silently stops working.
You do not have to hunt for cause 1 by hand — the script checks it. App's other Exchange role assignments (check 9) enumerates every assignment on the application and FAILs on any mailbox-data role scoped outside your management scope. Run the cmdlet below only to see the detail, or if that check itself reported NOT RUN:
Get-ManagementRoleAssignment -App <application (client) id> |
Format-List Name, Role, CustomResourceScope, RecipientWriteScope
Doing it by hand if the automation fails
This is the sequence the script performs, and the sequence it prints if its Exchange RBAC step throws. Run it as an Exchange Administrator, then re-run the script — it is idempotent and will verify what you did.
Connect-ExchangeOnline
# One-time; skip if the organization is already customized
Enable-OrganizationCustomization
# The group's DISTINGUISHED NAME is what the scope filter takes
$dn = (Get-Group -Identity 'sg_tater_departure_watch').DistinguishedName
New-ServicePrincipal -AppId <application (client) id> `
-ObjectId <ENTERPRISE APPLICATION object id> -DisplayName '<app display name>'
New-ManagementScope -Name 'TATER-DepartureWatch' `
-RecipientRestrictionFilter "MemberOfGroup -eq '$dn'"
New-ManagementRoleAssignment -App <application (client) id> `
-Role 'Application Mail.Read' -CustomResourceScope 'TATER-DepartureWatch'
Then both tests, which are not optional:
# POSITIVE — a mailbox that IS in the group. Expect InScope True.
Test-ServicePrincipalAuthorization -Identity <application (client) id> `
-Resource departing.manager@contoso.com
# NEGATIVE — a mailbox that is NOT in the group. Expect InScope False.
Test-ServicePrincipalAuthorization -Identity <application (client) id> `
-Resource unrelated.person@contoso.com
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
A mailbox read returns 403 with [RAOP] : Blocked by tenant configured AppOnly AccessPolicy settings |
The tenant enforces Exchange RBAC for Applications in default-deny mode and this application has no management-role assignment covering that mailbox. This is a configuration problem — retrying will never make it succeed. | Run the setup script for that mailbox, or the manual sequence. Allow up to 30 minutes to propagate. Note that Test-ApplicationAccessPolicy returning Granted does not prove this is configured — that cmdlet checks the older, independent application access policy mechanism. |
| The script reports "NO GRAPH MAIL-READ GRANT — this application cannot read ANY mailbox" | The application has no Microsoft Graph mail-read application permission, or it was never admin-consented. The client-credentials token therefore carries no mail scope and Graph rejects every read before Exchange RBAC is consulted. The Exchange group, scope and role assignment are all irrelevant in this state. | Grant Mail.Read as an application permission (not delegated) on Microsoft Graph and admin-consent it — the script prints the exact az ad app permission commands. Grant only Mail.Read; Mail.ReadWrite and Mail.Send exceed what this read-only feature needs and will be flagged by check 13. |
A mailbox read returns 403 with no [RAOP] in the body |
Distinct from the RAOP case above. This is the missing-Graph-grant signature: the token was rejected for lack of mail scope, not by Exchange RBAC. | Check 12 in the results table. If it is FAIL, grant and consent Mail.Read as above. Do not go looking for an Exchange scoping problem — there is not one. |
New-ManagementRoleAssignment fails with "Couldn't find a service principal" |
Either New-ServicePrincipal was never run for this application, or it was run with the app registration object id instead of the enterprise application object id. The wrong id is accepted at registration time and only fails here, pointing at the wrong step. |
Get the enterprise application object id with az ad sp show --id <appId> --query id -o tsv, or let the script resolve and print it. Re-run New-ServicePrincipal -AppId <appId> -ObjectId <that id>. |
| A cmdlet says to "run the command Enable-OrganizationCustomization" | The Exchange organization is dehydrated. Custom scopes and role assignments require a customized organization; fresh tenants have not done this. | Run Enable-OrganizationCustomization once and wait for provisioning. The script does this automatically and polls up to five minutes; if it is still provisioning, wait 10–15 minutes and re-run. |
The negative test returns InScope True |
The scope is not effective. Most likely a second unscoped management role assignment, a mailbox-reaching grant on the Office 365 Exchange Online resource such as full_access_as_app, or a tenant that does not enforce RBAC for Applications at all. Not the Graph Mail.Read grant, which is required and present on working deployments. |
Enumerate the application's Exchange role assignments and audit the Entra grants, then run the empirical negative read. Treat this as an active exposure until resolved — do not let anything read the mailbox on the assumption it is bounded. |
InScope False for a mailbox you just added to the group |
Exchange caches the MemberOfGroup evaluation. |
Wait 30 minutes to 2 hours and re-test. This is not a fault. If it persists after a run that changed nothing, confirm the mailbox is a direct member — nested groups do not expand. |
| The script stops: "No enterprise application (service principal) exists in this tenant" | Either the value passed to -AppId is wrong (the script detects and reports the case where it is actually an app registration object id), or the application is registered in another tenant and has never been consented into this one. |
Correct the client id, or create the service principal deliberately: az ad sp create --id <appId> or New-MgServicePrincipal -AppId <appId>. The script will not create it for you — provisioning the identity it then grants mailbox access to is a decision for an administrator. |
| The script reports the management scope exists but does not match the expected filter | A scope by that name already exists and its filter does not name your watch group. If a role assignment is bound to it, the application's access is fenced by whatever that filter actually selects — which may be far wider. | Nothing is changed automatically. Either inspect it, confirm nothing else relies on it, and repoint it with Set-ManagementScope; or leave it alone and re-run with a different -ScopeName. |
Test-ServicePrincipalAuthorization is not available |
The Exchange Online module in this session is too old. Neither test can be performed without it. | Update-Module ExchangeOnlineManagement -Force, then re-run. |
Connect-MgGraph fails with a "Method not found" or Microsoft.Identity.Client error |
An MSAL / Graph SDK version mismatch in the session, from older Microsoft.Graph modules loaded alongside newer dependencies. | Close the window, open a fresh PowerShell 7 session, Update-Module Microsoft.Graph -Force, and re-run. If device code itself is the problem, re-run with -UseBrowserAuth. As a last resort, pass -ServicePrincipalObjectId — but the Entra grant audit will then report NOT RUN, and an unrun audit is not a clean audit. |
Retention, and what leaves the tenant
What leaves, and where it goes
Running the setup script transmits nothing to TATER. It reads Entra and Exchange configuration from your own tenant using your own administrator sign-in, writes the group, scope, and role assignment, and reads no mailbox content. Provisioning is entirely a tenant-side operation.
Once a case is active, collected metadata is stored by TATER — not only in your Microsoft 365 tenant. Message metadata, security signals, and generated briefings are written to Cosmos DB in TATER Security's Azure subscription, partitioned by tenant. That is a real transfer of data about a named individual's correspondence to a vendor, and it should be stated plainly to whoever signs the attestation rather than left to be inferred. What is not transferred is message content: no body, no preview, no attachment contents, no attachment file names — those are unreachable by construction, as described above.
TATER itself sends nothing to an AI model provider in either briefing mode. No timer, endpoint or MCP tool in TATER submits mail metadata to a model, and a case in the default raw mode involves no model at all.
A case in agent mode is different, and the difference is yours rather than TATER's. That mode is written to be driven by an external routine you run: it reads the collected metadata — subject lines, counterparties, timestamps — through TATER's read tools, submits it to whichever model that routine uses in order to write the summary, and posts the resulting text back. So the transmission is real, it carries client-correspondence metadata about a named individual, and it happens under your agreement with that model provider, not TATER's. Whoever signs the attestation should be told this explicitly if you intend to run agent mode; leaving it to be inferred from "TATER sends nothing" would be true about TATER and misleading about the firm.
Designed retention limits
The case model carries per-case retention settings with hard maximums that cannot be raised:
| Data | Default | Maximum |
|---|---|---|
| Message metadata | 30 days | 90 days |
| Security signals | 180 days | 365 days |
| Generated briefings | 365 days | 365 days |
A value above the maximum is silently clamped to the maximum rather than rejected, so asking for 36,500 days quietly yields the ceiling. Read back what was stored rather than assuming the request was honoured. The same applies to the recipient list (25) and the client, competitor, and personal-webmail domain lists.
These limits are now enforced by a daily retention sweep (07:10 UTC) across the three stores — metadata, signals, and briefings. Two behaviours are worth knowing because both invert a reasonable guess:
- An unset retention means the platform ceiling, not "keep forever." A case that never specified a retention is swept at the maximum, and so is an orphaned row whose case no longer exists. The dangerous reading — absent means unbounded — is the one the sweep deliberately does not take.
- The destructive step is off by default. Deletion runs only when the app setting
DEPARTURE_WATCH_PURGE_ENFORCEis exactlytrue. Until it is set, the sweep still runs and reports what it would delete, but deletes nothing. If your firm's answer on retention is a commitment rather than an aspiration, confirm with TATER Security that this setting is on — a limit that is computed and reported is not a limit that is applied.
Deletions are capped at 200 rows per store per run, so a large backlog drains over several nights rather than in one spike.
The optional AI-summarised briefing mode
The design has two briefing modes on each case:
raw— deterministic. Threads, tiers, and signals are rendered as-is. Nothing is sent to a model provider. This is also the fallback whenever the other mode fails.agent— a routine reads the staged evidence and writes the narrative. In this mode, message metadata and derived fields (subject, dates, direction, counterparty domain, days silent) would be sent to the AI model provider to compose the text. No message body would be sent, because none is ever retrieved.
A case created without an explicit reportMode is an agent-authored case. The deterministic mode is the one that must be selected deliberately — it is not what you get by leaving the field alone. This matters because whether message metadata may reach an AI provider is one of the questions the counsel brief asks the firm to answer: if the answer is no, reportMode has to be set to raw on every case, and that has to be checked rather than assumed.
In both modes the design has TATER perform delivery and enforce the recipient list. The model authors content; it never gains the ability to choose an audience. The monitored mailbox is attacker-influenced by construction — anyone can email the person under watch — so its contents are treated as untrusted input to whatever summarises them.
The mode is declared on the case model and the briefing renderer has a branch that discloses a fallback, but there is no code anywhere in the platform that sends Departure Watch data to a model provider — verified by search across the feature. Every briefing produced today is the deterministic one, whatever reportMode says, because the agent path has nothing behind it. If counsel is uncomfortable with metadata reaching an AI provider, that is currently the situation by construction — but do not rely on the per-case field to keep it that way: the default is agent, so the day the agent path ships, every case created before it will already be marked to use it. Set raw explicitly, and treat the default as the thing to change rather than the thing to trust.
How a reader reaches a source message
A briefing links to the original message in Outlook rather than copying it. That is a deliberate choice: copying source mail into a second system would create a second persistent copy of client correspondence, including plan-participant data. Linking keeps one copy, in Exchange. Opening a link requires the reader's own access to that mailbox, granted and revoked through normal Exchange permissions, so a reader without access gets a link that does not open for them.
Do not read the paragraph above as "the briefing cannot show anything to someone without mailbox access." The subject line is the link text, so it renders in plain text on the page to every recipient, whether or not the link works for them — as do the date, the direction, the counterparty domain, and the subject lines of grouped threads. A recipient with no rights to the mailbox still reads every subject line of a departed colleague's client correspondence.
This is the fact most likely to matter to a confidentiality analysis, because a subject line in a benefit-plan practice can identify a plan, an employer, and sometimes a participant. It is why the recipient list is capped, requires a written reason, and cannot be edited once a case is active — the recipient list is the access control here, not Exchange.
Who can open the briefing page, and who only gets the email
Two separate permissions decide what a recipient actually sees, and they are easy to conflate because being on the recipient list looks like access. Being emailed a briefing does not grant the ability to open it.
- To open the briefing page in TATER Ops — the reader needs the Auditor role or higher in that organization, and a TATER Security product licence. The page reads counterparties and subject lines, and every read is written to the audit log as exactly that, so it is not a Viewer-level surface.
- To open a source message from the page — the reader needs their own access to the monitored mailbox in Exchange. That is a separate grant, made and revoked in Microsoft 365, and it is described below.
A Viewer-level recipient receives the daily email and gets a "User does not have required role" error on the page. The email is still useful to them — it carries the counts, the integrity warnings, the checks that could not run, and the subject lines of the threads waiting on someone — but the per-message detail is closed to them. Check the roles of everyone you name on a case before you rely on them being able to follow the link, because nothing in the email says who it will work for.
A briefing link addresses the monitored mailbox explicitly — https://outlook.office.com/mail/departed.user@example.com/deeplink/read/<id>. Earlier builds emitted the link in the form Graph returns it under application permissions, which names no mailbox at all: clicked by a recipient it resolved the message id in their own mailbox, where it does not exist, so it failed for everyone regardless of the permissions they held. If you are reading an older briefing, its links will not open no matter what you grant.
The intermediate /owa/user@example.com/?ItemID=… form does not work either, and fails in a way worth recognising: it redirects into modern Outlook, drops the query, and opens the mailbox rather than the message. A closely related variant reports that the message was deleted — which is what resolving a valid id against the wrong mailbox looks like, not evidence that anything was actually deleted.
Granting the Exchange access without mounting the mailbox in anyone's Outlook. Full Access assigned directly to a user is auto-mapped: Exchange stamps msExchDelegateListLink and the mailbox appears in that person's Outlook profile whether they want it or not. Assigning the same Full Access to a mail-enabled security group does not auto-map, because that attribute is only stamped for direct user assignments — so group members can open a link without the mailbox being added to their profile. It must be mail-enabled; Add-MailboxPermission needs a recipient object and will not accept a plain Entra security group. A direct assignment with -AutoMapping:$false achieves the same thing for one named person. Expect the usual Exchange delay before a new grant takes effect.
Everything TATER reads here is metadata: subject, timestamp, participants, folder. Full Access to the mailbox grants the message bodies, previews and attachments that this feature deliberately never touches, and it grants them for the whole mailbox rather than the window the case covers. That is a real widening of access beyond what the case was authorized for, and the attestation names the counsel and jurisdictions it was reviewed under.
If you want the links to work for a narrower audience, Add-MailboxFolderPermission with Reviewer on Inbox and Sent Items is closer to the folders actually being watched, and also does not auto-map. Deciding that nobody needs the links, and letting the metadata on the page stand on its own, is a legitimate answer too.
Changing who receives the briefing
The recipient list can be edited on a live case, without stopping collection. Open TATER Ops → Departure Watch, find the case, and use the Recipients button on its card. Adding and removing in the same edit is fine — the change is sent as one operation and the resulting list is validated as a whole.
This is deliberately separate from the case editor. That editor changes scope — the window, the folders, the mailbox — and is refused outright while a case is active, because changing what is being collected midway through a collection is not safe. Changing who reads the briefing moves nothing about the collection, so it does not require a deactivate.
- Adding someone needs the Admin role and a written rationale, which is recorded on the case. It also requires an interactively signed-in human — an API key or automation cannot widen the audience for a named person's mail activity.
- Removing someone needs Admin, or that person removing themselves. A recipient can always take themselves off a briefing without asking an administrator.
- Every recipient must already be an organization member or a People record here. A mistyped address that happens to be deliverable would send the briefing to a stranger, so unknown addresses are refused by name rather than dropped silently.
- The subject of the case can never be a recipient. Adding them is refused, on the case's mailbox address and on every alias it resolves to. Sending someone a surveillance briefing about themselves discloses the monitoring to its subject and hands them their colleagues' client correspondence.
- The list cannot be emptied by an edit. A case that still collects but notifies nobody is monitoring with no reader. If monitoring should stop, deactivate the case — that is a different decision and it is recorded as one.
Every addition and removal is written to the audit log with the addresses involved, who made the change, when, and why — because "who could see this person's mail activity, and from when" is the question an auditor actually asks.
The machine-authored summary
A case set to agent report mode can carry a short written summary at the top of its briefing, interpreting the figures rather than restating them. The summary is written by an external routine through the stage_departure_watch_narrative MCP tool; TATER stores it, renders it, and delivers it.
The routine authors content and never chooses an audience. It cannot add a recipient, change the window, authorize a case or activate one — those are refused for any automated caller. The briefing still goes to the case's own recipient list, enforced by the server.
- It is labelled machine-authored wherever it appears, in the block itself rather than in a footnote, and it names the principal that wrote it and when. A reader deciding what to do about a colleague is entitled to know which sentences were generated.
- It is written for a specific date. A summary staged for a different date is ignored rather than attached, so a routine that has not run for three days cannot put Monday's analysis on Thursday's evidence.
- The briefing is built at 13:00 UTC (09:00 US Eastern in summer, 08:00 in winter). The routine must finish before that; anything staged afterwards applies to the next briefing.
- If no summary was staged, the briefing still goes out and says plainly that the narrative was requested and did not arrive, with the reason. A missing analysis must never read like a quiet day.
- You can read it before the recipients do. Open the briefing page in Ops and the staged summary is shown at the top, marked with the date it is for.
The summary is stored and rendered as plain text and escaped before it reaches the email. It is the one part of a briefing TATER did not generate, written by a model over mail that anyone able to write to the monitored mailbox has influenced, so it is never trusted as markup.
Where to get help, and related pages
- Email-to-Ticket Setup — the sibling case for the same Exchange RBAC-for-Applications mechanism, with the read/write variant of the role.
- Azure Registered Apps — the per-tenant credentials application this feature reuses.
- Staff Roster & Account Classification — who counts as staff, and how departures are recorded.
- Help Center home
The setup script is published at https://www.tatersecurity.com/Setup-TATERDepartureWatch.ps1 — see get the script for the download one-liner. Its comment-based help (Get-Help .\Setup-TATERDepartureWatch.ps1 -Full) covers what the script does, what it explicitly does not do, and the current state of the TATER side, plus worked examples. Note that its help block documents seven of the ten parameters — -GroupPrimarySmtpAddress, -ServicePrincipalObjectId and -UseBrowserAuth are described in the code but carry no help entry, so use the table above for those.
If a check on this page reports FAIL and the fix here does not resolve it, raise it with TATER Security with the full results table from the script — the check names and their detail lines are designed to be pasted verbatim.
Was this page helpful?
TATER