A multi-tenant Flask service that subscribes to Microsoft 365 change events, scans new email, Teams chat, OneDrive and SharePoint content for personal data, then blocks, quarantines or alerts on it before it spreads. The same audit trail that fires the block also gives the data owner a one-click revert.
Watch every change, classify in milliseconds, act before the page is refreshed.
The DLP service sits between Microsoft 365 and the Data & More platform's classification stack. It subscribes to Graph change notifications for the tenants it serves, dedupes them, fetches the affected content, runs it through a fast regex prefilter and the platform's profilers, then maps the verdict to a per-tenant policy and acts on it inside the source system. Every action is written to an Elasticsearch audit index, and every block is paired with a revert link the data owner can use without leaving the email.
Graph subscriptions are kept healthy by a background scheduler that renews tokens, reconciles drift, and re-applies bulk source configurations whenever the tenant's selection changes.
Each item goes through a tenant-tuned regex prefilter (with ReDoS-protected timeouts). Only items that pass the prefilter are sent to the java_profiler for the heavier PII verdict.
The policy maps a (source, verdict) pair to one of five actions, all executed inside Microsoft 365: delete the message, move it to quarantine, redact a file, or just send a heads-up email or Teams card.
One change in Microsoft 365 fans out into one notification, one classification round trip, one policy decision and one audit row. The pipeline below is what every Teams message, OneDrive upload and SharePoint edit walks through.
Each source has its own Graph subscription, its own resource path format, its own content fetcher, and its own notion of what blocking means. The processor routes on the parsed resource and dispatches into the right fetcher and the right enforcement.
graph-management before tokens lapse.reauthorizationRequired (renew), missed (log; admin can re-apply) and subscriptionRemoved (log).Exchange is wired through an EXO transport rule that quarantines suspicious mail before the user sees it. The DLP service then picks the message out of quarantine, classifies it, and either delivers or blocks it. Non-quarantine Exchange notifications are dropped on sight, they are legacy or misconfiguration.
Classification runs in two stages so the cheap one filters the bulk and the expensive one only sees what survives.
A per-tenant list of regex patterns, compiled on first use and re-compiled whenever the pattern list changes. Each match runs with a 1-second ReDoS-protected timeout (via the third-party regex module) so a single pathological pattern cannot stall the worker.
If the prefilter does not short-circuit, the text goes to the java_profiler's /dlp/classify endpoint. The client wraps the call in a circuit breaker (5 failures, 60s reset) and a tenacity retry (3 attempts, exponential backoff on connection / timeout). On total failure it fails open as "no PII" rather than blocking innocent content.
/dlp/classify endpoint, which is optimized for binary block / allow decisions.The (source, verdict) pair is mapped to one of five actions by the per-tenant policy stored in dlp_config. Allow leaves the system silent; the other four all end with an audit row.
Either no PII was detected, or the file is on the tenant's exclusion list for OneDrive / SharePoint. Nothing is written, nothing is sent.
Content stays in place. The sender (or the file owner) receives an email or Teams card noting that the content tripped the policy. Useful for low-severity categories.
The enforcement layer takes action in the source: Teams message hidden behind a card, OneDrive / SharePoint file locked, Exchange mail dropped from quarantine. The audit row carries a revert token so the action is reversible.
The file is left untouched and the owner receives an email with three options: enforce now, cancel (with optional temporary or permanent exclusion), or do nothing. After the grace window, the scheduler picks up the entry and enforces automatically.
Inbound mail caught by the transport rule sits in quarantine. The DLP service classifies it and chooses between release (deliver), keep (block) and notify (under review). A separate scan-and-release notification path keeps the sender informed.
Every block-class action embeds a signed revert URL in the notification. The recipient clicks it and the revert_service reads reversal_data from the audit row to undo the action and write a revert audit row alongside.
A grace_block is the soft option for OneDrive and SharePoint: nothing happens in the source until the owner either takes a decision or the grace window expires.
Notifications are addressed to whichever identity matters for the source (sender in Teams, file owner in OneDrive, site owners in SharePoint). The transport is picked per tenant.
For Teams notifications the bot posts a proactive reply on the original thread (or a DM if the channel reply fails). The card surfaces the action, a justification snippet, a deep link to the message, and the revert URL.
The email transport is chosen per tenant: graph-management sends as the tenant's own mailbox when Graph mail settings are configured, otherwise dm-notify sends from the platform. Templates are per-action with per-tenant overrides for heading / body / footer.
The mockups below reproduce three real notifications the service sends: one Teams adaptive card, one block-class email, and one alert-class email. They are framed inside Field Manual exhibit cards but carry their own product colour identity so the reader sees what their users actually see.
Your content was flagged because it may contain sensitive data. Please review before sharing.
Analysis: This data contains personal data under GDPR regulations. The string "mit cpr er 010101…" likely represents a Danish CPR (Central Person Register) number, a unique identifier for an individual, thus falling under the definition of personal data.
This is an informational alert. No action has been taken on your content.
Your content was Blocked by the organization's data protection policy. Check the dlp quarantine to find the original mail.
| Action taken | block |
| Source | exchange |
| Subject | David Junge shared "Red hat" with you |
| Location | View flagged content |
If you believe this was a mistake, please contact your IT administrator.
Your content was flagged because it may contain sensitive data. Please review before sharing.
Analysis: This data contains personal data under GDPR regulations. It includes names ("Peter"), national identifiers ("NIN", "DNI", "número de identidad"), and identification numbers associated with individuals, along with details of family members' identification documents, clearly establishing the presence of personal information.
This is an informational alert. No action has been taken on your content.
Four daemon threads start at app boot and never stop. They keep the service healthy without any cron or external orchestrator.
Pings java_profiler and the AI profiler. Records up / down transitions and surfaces remediation actions.
Every 15 minutes. Flags tenants whose Graph subscriptions have gone quiet for too long.
Hourly. Re-applies bulk subscriptions for ALL and GROUPS modes so new users and group changes pick themselves up.
Walks the audit index for grace_block entries whose window has passed and dispatches the deferred enforcement.
From a Graph notification to an Exchange quarantine release, every Microsoft 365 change follows one tight loop, scoped to one tenant, in less time than the user takes to switch tabs. Every action is logged, every block is reversible, and every classifier is fail-open by design so the system never holds back content for the wrong reason.