Skip to content

Repository files navigation

M365 Security Alert Dashboard

A self-hosted Microsoft 365 security monitoring dashboard that aggregates alerts from Defender XDR, Entra ID Protection, Intune, Exchange Online, Compliance, and more — all in one place, collected on a schedule (every 15 minutes by default).

No third-party SaaS required. Runs entirely on your own Windows host using Microsoft Graph API.


Vigil365 Overview Dashboard

Vigil365 Login Screen

Features

Pages & Monitoring Coverage

Page What it monitors
Overview Secure Score trend, KPI tiles, recent alerts, risky users, compliance summary, alert policy status
Identity Risky users, risky sign-ins, risk detections, MFA coverage, PIM assignments, MDI alerts, foreign sign-ins
Devices Intune compliance, non-compliant devices, stale devices, MDE endpoint alerts
Email MDO protection alerts by category (malware, phish, spam), threat breakdown
Incidents & Alerts Unified Defender XDR incidents + alerts, severity KPI tiles, date-range filter
Compliance DLP alerts, MCAS alerts, IRM insider risk alerts, attack simulations
Alert Center Custom alert policies, policy templates, triggered alert history, acknowledge/resolve workflow
Service Health M365 service advisories and incidents, per-service health status
M365 Connectivity Sign-in health, connectivity issues
Licenses & Users License SKU breakdown, inactive users, expiring licenses
Conditional Access Policy list, state breakdown (Enabled/Report-only/Disabled), per-policy detail, gap analysis
Tenant Activity Directory audit events with search, day-range filter, and CSV export — the audit surface behind activity-based alerting
Sign-in Locations Success/failure breakdown and country drill-down (tabular; no map)

Enterprise Features

  • Alert Policy Engine — metric, activity, and anomaly policies (MFA drop, risky-user spike, role assignment, app-consent, PIM changes, and more) with thresholds; auto-evaluates against live data. Triggered alerts are stored server-side in SQL Server with a full acknowledge / snooze / resolve / assign / notes workflow.
  • Activity & Anomaly Alerting — alerts on tenant audit activity (privileged role changes, app credential adds, CA policy edits) and on statistical spikes, not just static thresholds.
  • Notifications — Microsoft Teams, email (SMTP), and generic webhook delivery, with per-channel digest mode and delivery-failure self-alerting.
  • Reports — scheduled executive digest (daily/weekly/monthly) over email with a CSV attachment, plus a live preview.
  • Trends — historical posture tracking (Secure Score, risky users, compliance) from periodic snapshots.
  • Recommendations — a single findings hub folding in Conditional Access gaps and SharePoint/OneDrive sharing posture.
  • Entity Investigation — drill into any user or device for a merged timeline of its alerts and audit activity.
  • RBAC & User Management — in-app Admin / Analyst / Viewer roles, invitations, and a tamper-evident (SHA-256 hash-chained) audit trail of privileged actions.
  • Global Search — Ctrl+K palette across alerts, users, devices, and pages.
  • Detail Panels — click any alert, user, device, or policy for all fields and a direct "View in M365 Portal →" deep link.
  • Search, Filter, Export — full-text search, dropdown filters, and CSV export on every page (sortable columns on the active-alert queue).
  • Dark Mode, collapsible sidebar, toast notifications, saved filter presets, and a responsive layout.

Stack

Layer Technology
Backend ASP.NET Core 8 Minimal API
Frontend React 18 + TypeScript + Vite
Sign-in Microsoft Entra sign-in (MSAL) + in-app RBAC
Collection Graph app-only — client secret or certificate
Scheduler .NET BackgroundService — every 15 minutes
Storage SQL Server Express (EF Core migrations)
Icons lucide-react

Prerequisites

  1. Windows 10/11 or Windows Server 2019+, and local administrator rights
  2. Node.js 20+ (for building the installer)
  3. .NET 8 SDK (for building the installer)

Nothing else is required on the server. SQL Server Express and Azure CLI are installed by the setup wizard if they are not already there.

All paths also need a Microsoft 365 tenant where you can create an app registration.

Install (Interactive Setup Wizard)

Option 1: Download pre-built installer (Recommended) Download Vigil365-Setup.exe from the latest release and run it as Administrator.

Option 2: Build the installer yourself (Optional) If you prefer to build the executable yourself from source, open PowerShell and run:

pwsh -File scripts/build-installer.ps1

Then execute the generated dist/Vigil365-Setup.exe as Administrator.

That is the whole install: it is a single self-contained file that carries the application, the web UI, and the .NET runtime, so the deployment server needs no source tree, no Node.js, and no .NET installed.

The wizard will:

  1. Check administrator rights and install Azure CLI if it is missing.
  2. Register the Vigil365 application in Microsoft Entra (reusing an existing registration if you have one).
  3. Install SQL Server Express, or detect and reuse an instance you already have, and create the SQL login the service needs.
  4. Set up HTTPS, including the certificate, for a network install.
  5. Unpack the application and run it as an auto-starting Windows Service.

Choose who needs to reach it

The wizard's first question decides everything else:

Just this computer — the default, for evaluating Vigil365. It binds http://localhost:8080 (loopback only), needs no certificate, and changes no firewall rules. Entra permits http for loopback redirect URIs, so sign-in works as-is. Nothing else on your network can reach it.

Other people on our network — asks for a hostname and a certificate. Entra refuses plain http:// redirect URIs for anything but localhost, so once Vigil365 is reachable by name, HTTPS is required and the wizard asks where the certificate comes from:

Option Use when
A certificate already on this server Your organisation issues certificates from an internal CA (most common). The wizard lists what is in LocalMachine\My and marks hostname matches with a ✓.
A .pfx file You hold a wildcard or externally-issued certificate as a file.
Create one for me (default) You have neither and want to get running now.

The last option generates a certificate and trusts it on that server only — that machine stops warning, every other browser still warns. Fine for a pilot, not fine to leave in place: on a security product, a warning you tell people to click through is training them to ignore the one that matters. Re-run the wizard and pick one of the first two options to replace it; re-running reuses the existing Entra app registration rather than creating another.

If the server is reachable from the internet, scripts/request-cert.ps1 gets a free, publicly-trusted certificate from Let's Encrypt instead:

pwsh -File scripts/request-cert.ps1 -Hostname vigil365.yourcompany.com -Email you@yourcompany.com

Everything else is detected or derived: the first administrator is taken from your Azure CLI sign-in, and an existing SQL Server instance is found and reused rather than installing a second one.

After the wizard finishes, open the app and finish Setup in the browser to supply the Graph credentials used for collection.

For detailed instructions on user roles (RBAC), setting up SMTP notifications, and general configuration, see the Administration & Configuration Guide.


Development (hot-reload)

Run both simultaneously:

# Terminal 1 — backend
cd src\M365SecurityDashboard.Api
$env:ASPNETCORE_ENVIRONMENT = "Development"
dotnet watch run

# Terminal 2 — frontend
cd src\m365-security-dashboard-client
npm run dev

Frontend dev server: http://localhost:5173 (proxies API calls to backend)


Testing (E2E)

Vigil365 uses Playwright for end-to-end testing. The test suite automatically spins up the backend API and the frontend dev server, verifies the login flow, and ensures the dashboard renders correctly.

To run the tests locally:

cd src\m365-security-dashboard-client
npm install
npx playwright install --with-deps
npx playwright test

To run tests in UI mode:

npx playwright test --ui

HTTPS / TLS (required for production)

Outside Development the app enforces HTTPS (HSTS + redirect). Plain HTTP is only for local development. Two supported ways to serve TLS:

Option A — Reverse proxy (recommended)

Terminate TLS at IIS / Nginx / Caddy and proxy to the app on localhost. Example Caddy config:

vigil365.yourcompany.com {
    reverse_proxy localhost:8080
}

Run the app bound to localhost only (--urls http://localhost:8080) so it is never directly exposed; the proxy handles certs (e.g. automatic Let's Encrypt).

Option B — Kestrel with a certificate

Let the app terminate TLS directly by configuring a Kestrel HTTPS endpoint in appsettings.Production.json (Kestrel reads this automatically — no code change):

{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://0.0.0.0:443",
        "Certificate": { "Path": "C:\\certs\\vigil365.pfx", "Password": "YOUR_PFX_PASSWORD" }
      }
    }
  }
}

Set the Azure App Registration SPA redirect URI and Auth:RedirectUri to the HTTPS URL (e.g. https://vigil365.yourcompany.com).

Credential hygiene: prefer certificate auth for Graph over a client secret, store secrets in a vault or environment variables (never in committed files), and rotate any secret that has ever been exposed.


API Reference

Method Endpoint Description
GET /api/dashboard/overview Aggregated overview data
GET /api/dashboard/identity Identity & MFA data
GET /api/dashboard/devices Intune device compliance
GET /api/dashboard/email-protection MDO email alerts
GET /api/dashboard/security-incidents Defender XDR incidents
GET /api/dashboard/defender-alerts Defender XDR alerts
GET /api/dashboard/mdi-alerts Microsoft Defender for Identity alerts
GET /api/dashboard/mcas-alerts Defender for Cloud Apps alerts
GET /api/dashboard/risk-detections Entra ID risk detections
GET /api/dashboard/attack-simulation Attack simulation results
GET /api/dashboard/servicehealth M365 service health
GET /api/dashboard/licenses License SKU usage
GET /api/dashboard/conditional-access CA policies
GET /api/dashboard/ca-gaps Conditional Access gap analysis
GET /api/dashboard/sharing-posture SharePoint/OneDrive sharing posture
GET /api/dashboard/signin-locations Sign-in locations
GET /api/audit-events Tenant directory audit events
GET /api/entity/{kind}/{id} Entity investigation timeline (user/device)
GET /api/setup/status First-run setup progress
GET /api/setup/permissions Graph permission reference + status
POST /api/collector/run Trigger manual data collection
GET /api/collector/runs Collection run history

Unknown /api/* paths return 404 JSON. The full surface (alerts workbench, notification settings, report schedules, RBAC) is larger than this excerpt.


Security & Maturity

Read this before relying on Vigil365. This is an open-source read-only visibility aggregator, currently ready for production (v1.0.0). It surfaces signals that already exist across your Microsoft 365 admin centers in one place. It is not a replacement for native Microsoft security tooling (Defender XDR, Entra ID Protection, Purview), and it does not make security decisions or change configuration for you. Treat its output as a convenience view, verify findings in the source portal before acting, and do your own review of the code before deploying it in a sensitive environment.

What is in scope by design

  • Read-only, least privilege. Nearly every Graph permission requested is *.Read.All. The one exception is AttackSimulation.ReadWrite.All, which Microsoft Graph offers with no read-only variant — the app only reads with it and never launches simulations. If you don't use the attack-simulation view, don't grant it. The app cannot modify users, devices, policies, or tenant settings.
  • Recommends, never remediates. The Recommendations view and "Fix in M365 Portal →" links tell you what to change and deep-link you to the right blade, but the app makes no changes itself — every remediation happens in Microsoft's tooling, by you.
  • No inbound exposure by default. In development the API binds to localhost. A production deployment (deploy.ps1) runs behind Kestrel with a TLS certificate; anything beyond localhost is a deliberate choice you make.
  • App-only collection via MSAL (Azure.Identity) using a client secret or certificate; user sign-in via Entra with in-app RBAC. Standard Microsoft auth, not a homegrown scheme. All Graph traffic is HTTPS/TLS.

How credentials and secrets are handled

  • The Graph client secret is never committed to source. Use .NET User Secrets (dev) or appsettings.Production.json / environment variables (prod, both gitignored).
  • Notification secrets stored in the database (SMTP password, Teams/Slack & generic webhook URLs) are encrypted at rest with the Windows Data Protection API (DPAPI), machine scope — a leaked database row cannot be decrypted on another machine. Secrets are decrypted only in memory at send time and the SMTP password is never returned by the API.
  • Recommended: use certificate-based authentication instead of a client secret for production. A non-exportable certificate in the Windows cert store removes the plaintext shared secret entirely; Vigil365 supports a certificate thumbprint or PFX path, with a secret only as a fallback.

Host hardening checklist (your responsibility)

The security of this app is only as good as the box it runs on. Before production use:

  • Run on a dedicated, patched, hardened Windows host — not a shared workstation or a machine that handles untrusted input
  • Run the service under a dedicated low-privilege service account, not an admin or your own login
  • Enable BitLocker / full-disk encryption so the database and secrets are protected at rest
  • Keep the host off the public internet; access the dashboard over the LAN/VPN only
  • If you must expose it, put it behind a reverse proxy with TLS and authentication
  • Ensure the host has endpoint protection and is monitored — a compromised host can read tokens in memory while the app runs
  • Rotate the Graph secret/certificate on a schedule and immediately if the host is ever suspected compromised
  • Restrict who can read appsettings.Production.json and the SQL database with NTFS/SQL permissions

Operational resilience

  • Rate limiting is handled automatically (429 Retry-After respected).
  • A failed individual Graph source does not stop the whole collection run; each card degrades independently.
  • Logs are newline-delimited JSON on stdout and in logs/vigil365-.json beside the app. Files roll daily (and at 10 MB) with the newest 14 files retained. Configure Logging__File__Path, Logging__File__RetainedFileCountLimit, and Logging__File__FileSizeLimitBytes for the host policy. Docker persists them in the vigil365-logs volume at /app/logs.
  • Log events include request correlation IDs and structured fields. Do not put access tokens, client secrets, or notification credentials in log messages.
  • Follow the Operations Runbook for SQL/key-ring backups, restore drills, and upgrades.

Found a security issue? See SECURITY.md — please report privately, not in a public issue.


Contributing

Pull requests welcome. Please:

  • Do not commit credentials or tenant-specific data
  • Keep appsettings.json with placeholder values only
  • Test with npm run build and dotnet build before submitting

License

MIT — see LICENSE

About

Self-hosted Microsoft 365 security operations dashboard — aggregates Defender XDR, Entra ID Protection, Intune, Compliance and more into one place.

Topics

Resources

Contributing

Security policy

Stars

254 stars

Watchers

5 watching

Forks

Releases

Packages

Contributors

Languages