Open-source, self-hosted, multi-tenant security dashboard for Microsoft 365. Track MFA coverage, Conditional Access, Secure Score, device compliance, licence waste and configuration drift across every tenant you manage — from your own server, with read-only access to Microsoft Graph.
Built for MSPs, IT service providers and internal IT teams who need continuous evidence of their Microsoft 365 security posture, not a one-off scan.
- Why this exists
- Try it in two minutes
- How it works for the person using it
- Features
- How it compares
- Screenshots
- Requirements
- Installation
- Connecting Microsoft 365
- Permissions
- How it works
- Security
- FAQ
- What it does
- Need a hand? Paid setup and custom work
- Contributing
- Licence and legal
There are excellent free Microsoft 365 assessment tools — ScubaGear, Maester, Microsoft365DSC, monkey365 and others. They all share the same shape: you run a PowerShell script, it produces an HTML file, and that file is a snapshot of one tenant at one moment.
That is genuinely useful, and it is not what this project does.
The gap is continuity. A point-in-time scan can tell you MFA coverage is 54% today. It cannot tell you it was 66% last Tuesday and that someone excluded a group from the policy in between. It cannot show you a 90-day Secure Score trend, compare twelve tenants side by side, or produce the continuously maintained evidence that cyber-insurance renewals and compliance frameworks increasingly demand.
This is a persistent application with a database behind it. It writes a dated snapshot on every sync, so history accumulates from day one — and history cannot be backfilled from Microsoft after the fact.
| Principle | What it means in practice |
|---|---|
| Read-only, always | There is no write scope anywhere in the codebase. Nothing here can change a customer's configuration. |
| Never confidently wrong | Where the data is ambiguous, the UI says so. A licence-gated metric shows "not available", never a zero. |
| Agentless | Nothing is installed in any tenant. No appliance, no agent, no service account that has to keep existing. |
| Your server, your data | No SaaS backend, no telemetry, no phoning home. Fonts are self-hosted so there is not even a CDN request. |
| Degrade gracefully | Most SMB tenants have no Entra ID P1, P2 or Intune. Every panel handles their absence explicitly. |
You do not need to register an Entra ID application to evaluate this. Seed a realistic demo estate and click around first:
git clone https://github.com/kapoordeepanshu/m365-security-dashboard.git
cd m365-security-dashboard
composer install && npm install && npm run build
cp .env.example .env && php artisan key:generate
touch database/database.sqlite && php artisan migrate
php artisan m365:demo # three demo tenants with 120 days of history
php artisan serveThree tenants are created — one healthy, one middling, one in genuinely poor shape — so every state
the interface has to render is visible, including the licence-gated ones. They never contact
Microsoft Graph. Remove them with php artisan m365:demo --fresh.
Demo tenants use Microsoft's own fictional companies (Fabrikam, Tailspin Toys, Lamna Healthcare). Those names are trademark-cleared by Microsoft precisely so samples never collide with a real business — which matters when the sample data attributes invented security failings to a named organisation.
The whole application is tenant-first. You pick one tenant from the switcher in the header, and everything below is that tenant's data. The choice is remembered between visits.
┌──────────────────────────────────────────────────────────────┐
│ [ Fabrikam, Inc. ▾ ] Search / ☾ │ ← switcher
├────────────────┬─────────────────────────────────────────────┤
│ FABRIKAM │ Dashboard │
│ ▸ Dashboard │ ┌───────────────────────────────────────┐ │
│ Users │ │ ( 96 ) Good │ │ ← the verdict:
│ Devices │ │ /100 1 issue worth addressing │ │ one sentence,
│ Cond.Access │ │ Start with: 14 accounts … │ │ before any figure
│ Licences │ └───────────────────────────────────────┘ │
│ Alerts │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ MANAGE │ │ MFA │ │ ADMINS │ │ DEVICES │ │ ← every card links
│ Tenants │ │ 96.7% │ │ 5 │ │ 244 │ │ to its full table
│ Settings │ │ ╱╲──╱ │ │ ╱──╲ │ │ ──╱╲ │ │
│ Setup guide │ │ View → │ │ View → │ │ View → │ │
└────────────────┴─────────────────────────────────────────────┘
The dashboard opens with a verdict, not a number. A row of figures tells you what; it never
tells you whether. 54.2% only means something if you already know what good looks like. So the
first thing on the page is a 0–100 posture score and one sentence: "3 critical issues need
attention. Start with: 3 administrators without MFA."
That score is deliberately not Microsoft Secure Score. Secure Score grades Microsoft's entire recommendation catalogue, much of which is irrelevant to a 40-seat business that can never reach a good number. This one weights the handful of controls that actually get tenants breached — administrator MFA, enforcement, legacy authentication — so a small business can plausibly reach 100.
Every table is the same table. One component drives users, devices, policies, licences, alerts and drift: server-side pagination, sortable columns, quick filters, search, and a CSV export that streams the entire filtered result set rather than just the page you are looking at.
Lists are sorted by what you came for. The users table opens riskiest-first, not alphabetically. You opened it to find accounts without MFA, not to scroll past two hundred healthy people.
- MFA registered vs. MFA enforced, modelled separately. These are different facts from different Graph sources. A user can have an authenticator app registered while no policy ever requires them to use it. Collapsing them into one boolean is the most common way these dashboards end up wrong.
- Administrator MFA coverage tracked separately — the finding every insurance questionnaire asks about first.
- Phishing-resistance ranking. Accounts relying on SMS or voice are flagged rather than counted as clean, because auditors increasingly ask specifically about phishing-resistant methods.
- Stale account detection (where Entra ID P1 makes sign-in activity visible).
- Guest account inventory and privileged role membership.
- Full policy inventory with derived flags: requires MFA, blocks legacy authentication, targets all users, targets directory roles, requires compliant device.
- Report-only policies are never counted as coverage. They enforce nothing, and they are frequently mistaken for active protection during an audit.
- Security Defaults detected as the tenant-wide MFA fallback.
- Detects MFA requirements expressed through
authenticationStrengthas well as the classicbuiltInControls— modern policies use the former, and checking only the latter under-reports enforcement.
- Entra ID registered devices merged with the Intune-managed subset.
- Compliance state, encryption, platform breakdown, stale-device detection.
- Tenants without Intune get an explicit "not managed" state, not a blank column that reads as "non-compliant".
- Microsoft Secure Score with the full control breakdown, retained beyond Microsoft's own ~90-day window.
- 90-day trend charts for MFA coverage, Secure Score and user counts.
- Configuration drift detection — automatic events when a watched control moves the wrong way between daily snapshots, with severity and an acknowledgement trail.
- Defender alerts (
alerts_v2) by severity, status and service source. - Licence inventory with unassigned-seat detection — usually the fastest money an MSP can find for a client.
- Branded PDF report per tenant, written for the person receiving it rather than the person
running the tool — "3 administrator accounts can sign in with only a password", not
admin_mfa_coverage_pct: 66.7. Rendered with dompdf, so there is no headless browser to install. - CSV export on every table, streaming the full filtered set.
- Scheduled email reports, weekly or monthly, per tenant or across the estate, with the PDF attached and each schedule honouring its own timezone.
- Date range picker — 7 days, 30, 90, 12 months or custom — driving every chart and delta on the page consistently.
- Header tenant switcher that remembers your last choice;
?tenant=slugkeeps links shareable. - Tenant management on one page: list, add, edit and disconnect, plus bulk sync.
- Global search (press
/or Ctrl-K) across tenants, users and devices — find a person without knowing which tenant they are in. - Application audit log: who added a tenant, rotated a credential or removed an account. Distinct from tenant drift, which records what changed inside Microsoft 365.
- Live-updating figures with a visible refresh state (polling — no websocket daemon to supervise).
- Per-collector sync progress, so a slow sync looks intentional rather than hung.
- Role-based access: owner, administrator, read-only analyst.
| Free PowerShell tools | Microsoft Lighthouse | Commercial SaaS | This project | |
|---|---|---|---|---|
| Cost | Free | Free (partner-only) | Per tenant / month | Free, MIT |
| Historical trend | ✗ | Limited | ✓ | ✓ |
| Configuration drift alerts | ✗ | ✗ | ✓ | ✓ |
| Multi-tenant side by side | ✗ | ✓ | ✓ | ✓ |
| Self-hosted / data sovereignty | ✓ | ✗ | ✗ | ✓ |
| Runs unattended | ✗ | ✓ | ✓ | ✓ |
| MFA enforced vs. registered | ✗ | ✗ | Varies | ✓ |
| Client-ready PDF report | ✗ | ✗ | ✓ | ✓ |
| Scheduled email reports | ✗ | ✗ | ✓ | ✓ |
| Read-only by construction | ✓ | ✗ | Varies | ✓ |
If you want a fast one-off assessment of a single tenant, use ScubaGear or Maester — they are better at that, and they are excellent. Use this when you need the same picture every day, across every tenant, with the history kept.
A 0–100 posture score and one sentence — "1 issue worth addressing. Start with: 70 accounts rely on SMS or voice for MFA." — before any figure. Every card below links to its full table.
The list opens riskiest-first. You came here to find accounts without MFA, not to scroll past two hundred healthy people.
Report-only policies never count as coverage. MFA is detected through both builtInControls and
grantControls.authenticationStrength, because modern policies use the latter and leave the former
empty.
A tenant can sit at 100% MFA registration while still allowing SMS for everyone.
Both read as "admin" per-user. Only one of them is live the moment an account is phished.
Twelve alerts about one compromised mailbox is one incident. Counting alerts overstates how much is going wrong.
"3 administrator accounts can sign in with only a password" — not admin_mfa_coverage_pct: 66.7.
These are captured from the running application against the demo tenants, by
npm run screenshots. Re-run it after a UI change and the documentation cannot drift from the product.npm run socialregenerates the header card the same way. Both need Chrome or Edge already installed — setCHROME_PATHif it lives somewhere unusual. They carry their own dependencies inscripts/package.json, installed on first run, so the capture toolchain never has to build inside the front-end image.
| Minimum | Notes | |
|---|---|---|
| PHP | 8.3 | With openssl, pdo, mbstring, curl, bcmath, fileinfo, intl, zip |
| Database | MySQL 8 / MariaDB 10.6 / PostgreSQL 13 / SQLite 3.35 | SQLite is fine for a handful of tenants |
| Web server | Nginx, Apache or Caddy | Anything that can serve a Laravel app |
| Node.js | 20+ | Build-time only — not needed at runtime |
| Redis | Optional | The database queue driver is the default, deliberately |
On the Microsoft side you need one Entra ID tenant you control, with permission to create an app registration. Customer tenants need nothing installed — only a one-time admin consent.
git clone https://github.com/kapoordeepanshu/m365-security-dashboard.git
cd m365-security-dashboard
docker compose up -dThen open http://localhost:8080 and create your account. That is the whole install.
The container generates APP_KEY, waits for the database, applies the migrations, and starts the
queue worker and scheduler on its own — so the wizard skips straight to the only step that needs a
person. Nothing else has to be run afterwards for the dashboard to start filling with data.
Back up the
app-storagevolume. YourAPP_KEYlives in it, atstorage/app/app.key, and every stored tenant credential is encrypted with that key. Lose the volume and those credentials are unrecoverable — you would re-enter every tenant's client secret.docker run --rm -v m365-security-dashboard_app-storage:/data -v "$PWD":/backup \ alpine tar czf /backup/m365-storage.tar.gz -C /data .The key is deliberately not kept in
.env. Compose used to bind-mount that file, but a single-file bind mount is a mount point: the kernel refuses everyopen()carryingO_CREATon one, so neitherkey:generatenor the install wizard could ever write it. Settings you change in the wizard live in the database; the container's.envis disposable.
git clone https://github.com/kapoordeepanshu/m365-security-dashboard.git
cd m365-security-dashboard
composer install --no-dev --optimize-autoloader
npm install && npm run build
cp .env.example .env
php artisan key:generate
# Point your web server's document root at ./publicOpen the app in a browser. The install wizard checks your server, configures the database, creates the schema and your first account.
Skip this if you used Docker. The worker container already runs both.
A queue worker and a cron entry. Without them the dashboard installs cleanly and then stays empty, which is the single most common way self-hosted tools get abandoned.
# 1. Queue worker — runs the syncs
php artisan queue:work --tries=1
# 2. Scheduler — writes the daily snapshot behind every trend and drift alert
* * * * * cd /path/to/m365-security-dashboard && php artisan schedule:run >> /dev/null 2>&1Keep the worker alive with systemd or Supervisor. A sample unit file is in
docs/deployment.md.
Back up your
APP_KEY. Tenant credentials are encrypted with it. Lose it and every stored secret becomes unrecoverable — every tenant has to be reconnected by hand. On a manual install it is in.env; under Docker it is in theapp-storagevolume, as described above.
Everything you need from a terminal. All commands run from the project root.
# Create an account, or reset the password on an existing one.
# This is your way back in if you are locked out, if the installer refuses to
# run twice, or if the only owner account gets deleted.
php artisan m365:user you@example.com --role=owner
php artisan m365:user you@example.com --password='secret' --name='Your Name'
php artisan m365:user you@example.com --force # skip the 12-character ruleRoles are owner (everything, including accounts and app registrations),
admin (tenants and syncs) and analyst (read-only).
php artisan m365:sync # queue a sync for every tenant
php artisan m365:sync --tenant=acme-corp # one tenant, by slug or tenant ID
php artisan m365:sync --tenant=acme --now # run inline, skipping the queue
php artisan m365:sync --due # only collectors whose cadence has elapsed--now is the fastest way to tell a Graph problem apart from a queue problem: if it works inline but
not when queued, your worker is not running.
php artisan m365:demo # three sample tenants, 120 days of history
php artisan m365:demo --fresh # remove the old ones first, then recreate
php artisan m365:demo --remove-only # remove them and stopSample tenants are prefixed DEMO, never contact Microsoft Graph, and can also be loaded and
removed from Settings. Removal matches on the slug prefix, so a real tenant can never be caught
by it.
php artisan m365:send-reports # queue any scheduled report that is due now
php artisan m365:prune # apply retention to sync logs, drift and snapshotsphp artisan serve # the web server
php artisan queue:work --tries=1 # REQUIRED — syncs do not run without it
php artisan schedule:work # the scheduler, for local testing
composer dev # all of the above plus Vite, togetherphp artisan test # 11 tests including the read-only guard
sh docker/entrypoint.test.sh docker/entrypoint.sh # container start-up logic
./vendor/bin/pint # code style
php artisan about # environment, drivers and cache status
php artisan queue:failed # jobs that failed and why
tail -f storage/logs/laravel.logphp artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache
# then restart the queue worker — it holds the old code in memoryDo not run
php artisan db:seed. It is deliberately empty; a fresh install starts with no data, and the first account comes from the wizard orm365:user.
Full click-by-click instructions live in docs/microsoft-setup.md, and the
same guide is built into the app under Setup guide, because the moment you need it is the moment
you are staring at a connection error.
The short version:
- Register one application in your own Entra ID tenant, as Multitenant, with this redirect URI:
https://your-server/tenants/consent/callback - Add read-only Graph permissions (see below) and grant admin consent in your own tenant.
- Create a client secret — copy the Value, not the Secret ID. It is shown once.
- Add it to the dashboard, then add each customer tenant by its Directory (tenant) ID.
- Send each customer an admin-consent link (generated for you). One Global Administrator opens it once and approves. Nothing else is required from them, ever.
Every tenant publishes its ID at a public endpoint — no sign-in needed:
https://login.microsoftonline.com/{their-domain.com}/.well-known/openid-configuration
The GUID appears in the issuer field.
Every permission is an application permission and read-only. There is no write scope anywhere in this codebase.
Every feature in this table is built and working. The column says whether you have to grant the permission, not whether the feature exists. Skip any marked Optional and the matching panel shows an explicit "not available" state instead of a misleading zero.
| Permission | Unlocks | Grant it? |
|---|---|---|
Organization.Read.All |
Tenant profile, verified domains, licence inventory | Required |
User.Read.All |
User inventory, guests, disabled accounts, licence assignment | Required |
Directory.Read.All |
Admin role membership, directory settings | Required |
Policy.Read.All |
Conditional Access policies, Security Defaults | Required |
AuditLog.Read.All |
MFA registration detail, sign-in activity, stale accounts | Required |
Group.Read.All |
Groups, Microsoft 365 groups, Teams-enabled groups | Optional |
Device.Read.All |
Registered devices, trust type, operating systems | Optional |
DeviceManagementManagedDevices.Read.All |
Intune device compliance and encryption | Optional |
IdentityRiskyUser.Read.All |
Detects Entra ID P2, which gates the PIM eligibility panel | Optional |
SecurityEvents.Read.All |
Microsoft Secure Score | Optional |
SecurityAlert.Read.All |
Microsoft Defender alerts (alerts_v2) |
Optional |
Reports.Read.All |
Service usage, mailbox and SharePoint activity | Optional |
Optional does not mean second-class. Microsoft Defender alerts, for example, are fully implemented — collected on a 15-minute cadence, shown by severity, status and service source, charted on the dashboard and included in the PDF report. The permission is optional only because plenty of tenants have no Defender product provisioned, and asking for less is a legitimate choice.
Mail.Read. It would grant the ability to read every email body in the tenant. No
security-conscious administrator should consent to that for a dashboard, and nothing here needs it —
mailbox counts come from usage reports instead.
Customer tenants (Microsoft 365)
│ read-only, app-only auth, one token per sync
▼
┌─────────────────────────────────────────────┐
│ Your server │
│ │
│ Scheduler ──> Queue ──> Collectors ──> DB │
│ │ │
│ daily snapshot │
│ │ │
│ drift detection │
│ │ │
│ Web dashboard │
└─────────────────────────────────────────────┘
Authentication. App-only (client credentials) with a certificate or client secret. Only the tenant ID is stored per customer — a fresh access token is requested per sync and cached in memory for slightly less than its lifetime. No refresh tokens are stored anywhere, ever.
Throttling. Microsoft Graph throttles per-tenant and per-application, so an MSP syncing 100
tenants through one app registration will hit 429s routinely. The client honours Retry-After, backs
off exponentially with jitter, batches requests 20 at a time, and gives each collector its own cadence
so nothing pulls everything every hour.
Collectors. Nine independent units — tenant profile, licences, Conditional Access, users and MFA, MFA enforcement, groups, devices, Secure Score, alerts. One failing never aborts the rest; the run ends partial with an honest record of what could not be read.
Snapshots. One append-only row per tenant per day, holding the metrics and the capability map that was true that day — so a zero recorded while Intune was unavailable stays distinguishable from a genuine zero.
- Read-only by construction. No write scope anywhere, and
Mail.Readis never requested — both enforced by a test that fails the build if either appears. - Credentials encrypted at rest with
APP_KEY, hidden from serialisation, never rendered back into a form. Covered by tests. - Login throttled per email + IP and per IP, so rotating the email buys no extra attempts.
- Search and live-polling endpoints throttled, so an authenticated account cannot enumerate the estate a character at a time.
- Security headers sent by the application, not left to the web server config: CSP locked to
'self'on every directive,X-Frame-Options: DENY,nosniff,Referrer-Policy: same-origin, a restrictivePermissions-Policy, and HSTS over HTTPS. - Session cookie marked
Secureautomatically whenAPP_URLis HTTPS. - Role boundaries tested: analysts cannot reach any route that changes state or touches a credential.
- The installer closes itself once setup completes; reopening it requires filesystem access.
- No outbound requests except to Microsoft Graph and the Entra login endpoints. Fonts and assets are served from your own origin — the CSP would block a CDN even if one were added by mistake.
php artisan test # 11 tests, 277 assertions — includes the read-only guardFound a vulnerability? Please see SECURITY.md rather than opening a public issue.
Does this work with a single tenant, or only for MSPs?
Both. Register the app as single-tenant instead of multitenant and connect just your own organisation. Everything else is identical.
Does anything get installed in my customers' tenants?
No. It is entirely agentless. A Global Administrator grants admin consent once via a link; after that the tenant is never touched again except by read-only Graph queries from your server.
Can this change my tenant configuration?
No. There is no write permission requested and no write code path. A test asserts that the HTTP client
exposes no PATCH, PUT or DELETE method at all, so a write path cannot be added by accident.
Can it read our email?
No. Mail.Read is not requested and never will be. Mailbox and Teams figures come from the aggregate
usage reports, which return counts and activity dates — never message content, subjects or recipients.
What if my tenants do not have Entra ID P1 or P2?
Most SMB tenants do not. Sign-in activity needs P1 and Identity Protection needs P2, so those panels render an explicit "Requires Entra ID P1" state rather than a misleading zero. Everything else — MFA registration, Conditional Access, licences, Secure Score, groups — works on any tier.
Why polling instead of websockets?
Because it keeps a self-hosted deployment to a PHP process and a database. Adding a websocket daemon to supervise is a real operational cost for a refresh interval measured in seconds.
Why not use the official Microsoft Graph SDK?
It is a large dependency whose generated models drift constantly, and this project only ever performs read-only GETs plus batching. The whole networking layer is a few hundred readable lines instead.
Can I rebrand it?
Yes — MIT licensed. The product name, colours and typography are defined in one config file and one CSS token block.
How much history does it keep?
Snapshots default to three years, sync logs to 90 days. Snapshots are a few hundred bytes each and are the only data here that cannot be rebuilt from Microsoft afterwards, so they are retained longest.
-
Microsoft Defender alerts, by severity, status and service source
-
Microsoft Secure Score with history retained beyond Microsoft's own ~90-day window
-
Intune device compliance and encryption
-
Branded PDF export for client reporting and QBRs
-
Scheduled email reports, weekly or monthly, with the PDF attached
-
CSV export on every table
-
Configuration drift detection with an acknowledgement trail
-
Global search and an application audit log
-
Defender incidents — the correlated case an analyst works with, not just the raw alerts
-
Authentication methods policy — what the tenant permits, so "100% MFA registered" cannot hide "SMS still allowed for everyone"
-
Privileged access — permanent standing privilege separated from PIM-eligible
-
Sign-in activity — whether legacy authentication is actually used, and by which clients
-
Exchange, SharePoint, OneDrive and Teams usage — from
Reports.Read.All, neverMail.Read -
Guided app registration — generates a PowerShell or Azure CLI script that creates the Entra application, adds every permission and grants consent, then prints the three values to paste back
Listed for the same reason every licence-gated panel says "not available" rather than showing a zero: a feature list you cannot trust is worse than a short one.
- Identity Protection risky users and risk detections. The P2 capability is detected and gates the PIM panel, but the risky-user and risk-detection feeds are not collected or shown.
- Service health — Microsoft 365 advisories and incidents (
ServiceHealth.Read.All). - Tenant audit log. The application audit log records what you did in this dashboard; the Microsoft 365 unified audit log is a separate feed and is not read.
- Threshold alerting. Drift detection watches seven fixed metrics. Alert rules are not yet user-configurable, and there is no push notification — reporting is scheduled email only.
- Sign-in geography. Country is collected but only summarised; there is no map or drill-down.
- Deep links into the Microsoft admin centres from individual rows.
Purview DLP, Insider Risk, Defender for Cloud Apps and attack-simulation reporting are deliberately out of scope. This is a posture and evidence tool, not a second Defender console.
Ideas and pull requests welcome — see CONTRIBUTING.md.
The software is free and always will be. Every feature listed above works in the copy you just cloned. Nothing is time-limited, nothing needs a licence key, there is no "enterprise edition", and the application contains no upsell, telemetry or call-home of any kind.
Always free:
- The application and every feature in it
- All future updates and security fixes
- Bug reports, feature requests and setup questions in GitHub Issues
What is paid is my time. Four things you can hire me for:
I get it running on your server and prove it works. You receive:
- Entra ID app registered, permissions added, admin consent completed
- The app deployed with HTTPS, and the queue worker and scheduler running as real services — the two things that, left out, leave the dashboard permanently empty
- Your first tenants connected, with the figures checked against the Microsoft admin centre so you know they are right
APP_KEYand database backups configured, and clear instructions on where to keep them- A 45-minute handover call and a one-page runbook for whoever maintains it next
Done over screen-share, normally in a single session.
For MSPs with more tenants than patience.
- Consent links prepared and tracked for every tenant, so you can see who has and has not approved
- Every connection verified, and licence gaps flagged — which tenants lack Entra ID P1 or P2, so you know why some panels are empty rather than assuming the tool is broken
- A baseline posture report per client, ready to send out
- One CSV of the whole estate ranked by risk, so you know which client to call first
- White-labelling: your name, logo, colours and branded PDF reports
- Custom report layouts for your QBRs
- New Graph collectors for data the project does not pull yet
- Integration into your PSA or ticketing system
- Single sign-on for your own staff
Anything reusable goes back into the public repository under MIT. You get the feature built and maintained by everyone afterwards, instead of a private fork you have to keep patching yourself. Work that is only about you — your branding, your internal systems — stays yours.
An independent look at how your install is secured: who can reach it, how credentials are stored, who holds which role, and whether you could actually recover it. You get written findings ranked by severity, each with the fix.
Useful when a client or an auditor asks you to evidence how their data is handled.
Being straight about this so nobody wastes an email:
- Hosting it for you. I work on your server. Your tenant credentials and your customers' posture data never sit on infrastructure I control — which is how it should be for a tool like this.
- Changing your Microsoft 365 settings. This tool is read-only by design. It tells you MFA is missing; switching it on is your job.
- Microsoft licences or CSP. I am not a Microsoft partner or reseller. Buy those from Microsoft or your existing partner.
- 24/7 monitoring or a SOC. Nobody is watching your alerts at 3am.
- Support for forks you have modified. Happy to look, but I cannot promise to fix someone else's changes.
Email deepanshukapoor [at] live [dot] com with three things:
- Which of the four above you want
- Roughly how many tenants you manage
- When you need it done
You get a reply within two working days, with a fixed price wherever the job allows one — and a straight "you do not need to pay me for this" if that is the honest answer.
Keep questions free. Anything about installing, configuring or using the software belongs in GitHub Issues, not this inbox. A public answer helps the next person with the same problem; a private one helps nobody.
Not affiliated with, endorsed by, or a reseller of Microsoft. These services cover this open-source project only. They are not Microsoft licences, Microsoft support, or a CSP relationship.
Contributions are very welcome, especially:
- Microsoft Graph corrections (endpoints and permission names change often)
- Compliance framework mappings
- Translations
- Deployment recipes for platforms not yet covered
See CONTRIBUTING.md for the development setup.
MIT — see LICENSE. Use it commercially, rebrand it, resell services built on it.
Third-party notices, trademark statements and framework licensing are in NOTICE.md.
The short version:
- Not affiliated with Microsoft. Microsoft, Microsoft 365, Entra ID, Defender, Intune and Microsoft Graph are trademarks of the Microsoft group of companies, used here descriptively to identify the services this software reads from. No endorsement is claimed or implied.
- Graph is used as a documented public API, with credentials you supply, under an application you register in your own tenant, with consent granted by the tenant owner. Your use is subject to the Microsoft APIs Terms of Use.
- No CIS Benchmark content is included. CIS licenses its benchmarks CC BY-NC-SA, which forbids commercial use and forces share-alike terms — both incompatible with MIT. Excluded deliberately.
- Demo data uses Microsoft's own fictional companies (Fabrikam, Tailspin Toys, Lamna Healthcare), which Microsoft cleared for exactly this purpose so samples never collide with a real business.
- This is not an audit, certification or legal advice. It reports whether technical controls it can observe appear to be in place.
The free tools in this space set the bar for what tenant assessment should look like, and this project was designed around the gap they leave rather than in competition with them: ScubaGear (CISA), Maester, Microsoft365DSC and monkey365.
Keywords: Microsoft 365 security dashboard · M365 security posture · self-hosted M365 monitoring · multi-tenant Microsoft 365 dashboard · MSP Microsoft 365 tools · Entra ID security reporting · Microsoft Graph dashboard · MFA coverage report · Conditional Access audit · Microsoft Secure Score tracking · M365 compliance evidence · open source SSPM











