Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

M365 Security Dashboard — Self-Hosted Microsoft 365 Security & Compliance Posture Monitoring

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.

License: MIT PHP 8.3+ Laravel 13 Self-hosted Read-only

M365 Security Dashboard


Contents


Why this exists

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.

Design principles

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.

Try it in two minutes

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 serve

Three 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.


How it works for the person using it

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.


Features

Identity and MFA

  • 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.

Conditional Access

  • 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 authenticationStrength as well as the classic builtInControls — modern policies use the former, and checking only the latter under-reports enforcement.

Devices and compliance

  • 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".

Posture and trends

  • 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.

Reporting and evidence

  • 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.

Multi-tenant operations

  • Header tenant switcher that remembers your last choice; ?tenant=slug keeps 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.

How it compares

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.


Screenshots

Walkthrough

The dashboard opens with a verdict, not a number

Tenant dashboard

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.

Registered and enforced are separate columns

Users and MFA

The list opens riskiest-first. You came here to find accounts without MFA, not to scroll past two hundred healthy people.

What is actually enforced, not just what exists

Conditional Access

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.

What the tenant permits, which no per-user report shows

Authentication methods

A tenant can sit at 100% MFA registration while still allowing SMS for everyone.

Standing privilege separated from PIM-eligible

Privileged access

Both read as "admin" per-user. Only one of them is live the moment an account is phished.

Incidents, not just alerts

Defender incidents

Twelve alerts about one compromised mailbox is one incident. Counting alerts overstates how much is going wrong.

Drift — the thing a point-in-time scan cannot do

Configuration drift

A report written for the person receiving it

Client report

"3 administrator accounts can sign in with only a password" — not admin_mfa_coverage_pct: 66.7.

More — tenant management and the in-app setup guide

Tenant management

Setup guide

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 social regenerates the header card the same way. Both need Chrome or Edge already installed — set CHROME_PATH if it lives somewhere unusual. They carry their own dependencies in scripts/package.json, installed on first run, so the capture toolchain never has to build inside the front-end image.


Requirements

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.


Installation

Docker Compose (fastest)

git clone https://github.com/kapoordeepanshu/m365-security-dashboard.git
cd m365-security-dashboard
docker compose up -d

Then 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-storage volume. Your APP_KEY lives in it, at storage/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 every open() carrying O_CREAT on one, so neither key:generate nor the install wizard could ever write it. Settings you change in the wizard live in the database; the container's .env is disposable.

Manual install

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 ./public

Open the app in a browser. The install wizard checks your server, configures the database, creates the schema and your first account.

Two steps that are not optional — manual installs only

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>&1

Keep 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 the app-storage volume, as described above.


Command reference

Everything you need from a terminal. All commands run from the project root.

Accounts

# 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 rule

Roles are owner (everything, including accounts and app registrations), admin (tenants and syncs) and analyst (read-only).

Syncing

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.

Sample data

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 stop

Sample 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.

Reports and maintenance

php artisan m365:send-reports   # queue any scheduled report that is due now
php artisan m365:prune          # apply retention to sync logs, drift and snapshots

Running it

php 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, together

Checking it

php 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.log

After deploying

php 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 memory

Do 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 or m365:user.


Connecting Microsoft 365

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:

  1. Register one application in your own Entra ID tenant, as Multitenant, with this redirect URI: https://your-server/tenants/consent/callback
  2. Add read-only Graph permissions (see below) and grant admin consent in your own tenant.
  3. Create a client secret — copy the Value, not the Secret ID. It is shown once.
  4. Add it to the dashboard, then add each customer tenant by its Directory (tenant) ID.
  5. 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.

Finding a tenant ID without portal access

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.


Permissions

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.

What is deliberately never requested

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.


How it works

  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.


Security

  • Read-only by construction. No write scope anywhere, and Mail.Read is 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 restrictive Permissions-Policy, and HSTS over HTTPS.
  • Session cookie marked Secure automatically when APP_URL is 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 guard

Found a vulnerability? Please see SECURITY.md rather than opening a public issue.


FAQ

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.


What it does

  • 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, never Mail.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

What it does not do yet

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.


Need a hand? Paid setup and custom work

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:

1. Install and go-live

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_KEY and 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.

2. Bulk tenant onboarding

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

3. Custom development

  • 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.

4. Deployment security review

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.

What I do not offer

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.

Getting a quote

Email deepanshukapoor [at] live [dot] com with three things:

  1. Which of the four above you want
  2. Roughly how many tenants you manage
  3. 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.


Contributing

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.


Licence and legal

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.

Acknowledgements

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

About

Self-hosted, multi-tenant Microsoft 365 security and compliance dashboard. Tracks MFA coverage, Conditional Access, Secure Score, device compliance and configuration drift over time — agentless, read-only Microsoft Graph, on your own server.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages