If you find a security issue (e.g. credentials being logged, an endpoint leaking data), please do not open a public issue. Open a GitHub Security Advisory instead so it can be fixed before public disclosure.
Please include the affected version/commit, reproduction steps, and potential impact. We aim to acknowledge reports within a few business days and ask for reasonable time to remediate before public disclosure.
Actively developed; security fixes target the latest master. Pin to a released commit for production and review changes before upgrading.
Vigil365 is a self-hosted application meant to run on infrastructure the operating organisation controls — not a public SaaS. It runs in one of two trust models, set by Edition:Mode:
- Single organisation — one organisation monitors its own Microsoft 365 tenant. The app registration is single-tenant; one tenant's data in one database.
- MSP — a managed service provider monitors many client tenants. The app registration is multi-tenant so each client's Global Administrator can grant admin consent to the same permissions (read-only but one, below); the client can revoke it at any time in their own Entra admin center. Sign-in is still pinned to the MSP's own tenant — client users cannot sign in. Every row is tagged with its client; a global query filter and a write guard stop one client's data being read or written under another, enforced by an isolation test suite on SQL Server and PostgreSQL. Staff other than Admins see only the clients assigned to them. Client credentials (when a client uses its own) are encrypted at rest. See
docs/MSP_DATA_PROCESSING.md.
In both models:
- Read-only against your tenant — Graph access is app-only (client credentials) and the app never writes to the M365 tenant. Every permission is read-only except the optional
AttackSimulation.ReadWrite.All: Graph has no read-only permission for attack-simulation results. It also allows creating phishing simulations, so whoever holds the app's secret could; Vigil365 only reads with it. Seedocs/graph-permissions.md. - Data stays in-tenant — collected data is stored in the operator's own SQL database; nothing is sent to any third-party service.
- Network isolation is a primary control — designed to sit on a private network / behind a reverse proxy, not exposed directly to the internet.
- Secrets encrypted at rest — secrets kept in the database (SMTP password, webhook URLs, per-client credentials, a Graph client secret entered on the Setup page) are encrypted with ASP.NET Core Data Protection. Its key ring (
DataProtection:KeyPath) is not itself encrypted: file permissions protect it, so back it up with the database and guard it the same way. The Graph client secret the Windows installer creates is written toappsettings.Production.json, readable only by Administrators, SYSTEM and the service account. The SMTP password and credentials are never returned by the API; webhook URLs are returned only to Admins, for editing. - Database transport encryption — SQL connections use
Encrypt=True. - TLS in production — HSTS + HTTPS redirection enforced outside Development; TLS via reverse proxy or Kestrel certificate (see README "HTTPS / TLS").
- Safe error handling — API errors return generic messages; detail goes to server logs only.
- Security headers —
X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy: no-referrer. - Least privilege — Graph permission set scoped to the monitored services (read-only but one, above).
Implemented on a development branch and rolling into releases: identity sign-in (Entra ID / MSAL), role-based access control (Admin/Analyst/Viewer), an append-only audit trail, and certificate-based Graph auth. See docs/PROJECT_SUMMARY.md.
- Keep the app registration's client secret/certificate in a secret store; never commit credentials; rotate anything that may have been exposed.
- Serve over HTTPS in production and restrict network exposure.
- Apply OS, .NET, SQL Server, and dependency updates.
Microsoft occasionally changes, deprecates, or moves Graph API endpoints. If a dashboard card stops working or shows a permission error, please open an issue using this format:
Title: [BROKEN API] <page name> — <endpoint>
Include:
- Which page/card is affected (e.g. "Identity page — Risk Detections card")
- The error shown on screen (e.g. "403 Forbidden" or "404 Not Found")
- Your approximate date when it broke
- Link to the Microsoft changelog entry if you found one
| Endpoint area | Stability | Notes |
|---|---|---|
| Risky users / sign-ins | ✅ Stable | v1.0, unchanged since 2021 |
| MFA registration details | ✅ Stable | v1.0 |
| Intune device compliance | ✅ Stable | v1.0 |
| Conditional Access policies | ✅ Stable | v1.0 |
Defender XDR alerts (alerts_v2) |
✅ Stable | v1.0, replaced alerts in 2022 |
| Defender XDR incidents | ✅ Stable | v1.0 |
| Service health | ✅ Stable | v1.0 |
| Audit logs / sign-ins | ✅ Stable | v1.0 |
| Attack simulation | v1.0 but feature-flagged by license | |
| Insider Risk (IRM) | Requires Microsoft Purview license | |
| Identity health issues | /beta/ endpoint — may change without notice |
|
| MCAS alerts | Merging into Defender XDR over time |
Subscribe to the official Microsoft Graph changelog to get notified of breaking changes:
🔗 https://developer.microsoft.com/en-us/graph/changelog
Each dashboard card fetches independently. If one Graph endpoint returns an error (403, 404, 429), that card shows an inline error message — all other pages and cards keep working. No single API change can break the whole dashboard.