Essential Eight maturity assessment for Microsoft Entra ID / Microsoft 365 tenants.
E8 Posture connects to your tenant via the Microsoft Graph API, evaluates configuration against the ACSC Essential Eight Maturity Model (ML0–ML3), and produces an interactive executive dashboard plus a printable report with per-control remediation guidance.
Demo mode is on by default. No credentials required — explore the full UI against a realistic mock tenant before connecting a real environment.
The ACSC Essential Eight is the baseline cyber security framework for Australian government agencies and the de-facto standard for private-sector organisations operating in or with the Australian market. Eight mitigation strategies are prioritised by their effectiveness against the most common attack techniques:
| Strategy | Focus |
|---|---|
| Multi-factor authentication | Credential theft and account takeover |
| Restrict administrative privileges | Privilege escalation and lateral movement |
| Patch operating systems | Exploitation of known OS vulnerabilities |
| Application control | Execution of malicious or unauthorised code |
| Patch applications | Exploitation of known application vulnerabilities |
| Configure Microsoft Office macro settings | Macro-based malware delivery |
| User application hardening | Browser and PDF reader exploits |
| Regular backups | Ransomware and destructive attacks |
E8 Posture currently covers the four strategies assessable via the Microsoft Graph API (MFA, Admin, OS Patch, Application Control). The remaining four require endpoint agent data or on-premises tooling outside the scope of the API.
| Control | Strategy | What it measures | ML signal |
|---|---|---|---|
| E8-MFA-01 | Multi-factor Authentication | Percentage of users with an MFA method registered | ML1 / ML2 |
| E8-MFA-02 | Multi-factor Authentication | Conditional Access policy enforcing MFA for all users | ML2 |
| E8-MFA-03 | Multi-factor Authentication | Conditional Access policy enforcing MFA for privileged roles | ML2 |
| E8-MFA-04 | Multi-factor Authentication | Conditional Access policy blocking legacy authentication clients | ML1 |
| E8-MFA-05 | Multi-factor Authentication | Admins with phishing-resistant methods (FIDO2 / WHfB) registered | ML3 |
| E8-ADMIN-01 | Restrict Administrative Privileges | Permanent Global Administrator count vs. absolute and ratio thresholds | ML1 |
| E8-ADMIN-02 | Restrict Administrative Privileges | Ratio of PIM-eligible to permanent role assignments | ML2 |
| E8-ADMIN-03 | Restrict Administrative Privileges | Global Administrators holding Microsoft 365 user licences | ML1 |
| E8-OSPATCH-01 | Patch Operating Systems | Proportion of Intune-managed devices in a compliant state | ML1 |
| E8-OSPATCH-02 | Patch Operating Systems | Presence of a compliance policy with a minimum OS version requirement | ML1 |
| E8-APPCTL-01 | Application Control | Intune WDAC enforcement profile (enforced vs. audit-only vs. absent) | ML1 |
A strategy reaches maturity level L only when every control whose maturityLevelSignal === L
has status pass. A partial or not_assessable result at any level blocks progression from
that level upward. The overall posture score is a 0–100 normalisation of the equal-weighted
average across all four assessed strategies: round(sum(mlVector) / (4 × 3) × 100).
flowchart LR
subgraph Client["Browser (React Server Components)"]
Dashboard["/ Dashboard"]
DrillDown["/strategy/[id]"]
Report["/report/[scanId]"]
Scans["/scans"]
end
subgraph Server["Next.js Server"]
DemoClient["Demo Client\n(fixtures)"]
GraphClient["Graph Client\n(typed fetch + retry)"]
MSAL["MSAL token cache\n(ConfidentialClientApp)"]
Runners["Check Runners\nMFA · Admin · OS Patch · App Control"]
Engine["Scoring Engine\ncomputeStrategyMaturity()"]
SupabaseLib["Supabase Client\n(RLS-enforced)"]
end
subgraph External["External Services"]
GraphAPI["Microsoft Graph API\nv1.0 / beta"]
Entra["Entra ID / M365 Tenant"]
DB[("Supabase Postgres\nSydney ap-southeast-2")]
end
Client -->|"Server Component render"| Server
DemoClient -->|"fixture JSON"| Runners
GraphClient --> MSAL
MSAL -->|"app-only Bearer token"| GraphAPI
GraphAPI --> Entra
GraphAPI --> Runners
Runners --> Engine
Engine -->|"PostureScore"| Client
SupabaseLib -->|"RLS policies"| DB
-
App-only permissions, no delegated access. The Graph app registration uses
client_credentialsflow; it never impersonates a user. Required permissions areReports.Read.All,Policy.Read.All,RoleManagement.Read.Directory,DeviceManagementManagedDevices.Read.All,DeviceManagementConfiguration.Read.All, andUser.Read.All. -
Tenant PII is never persisted. The database stores aggregated counts, policy names, and configuration settings — not user identities, UPNs, or personal data.
-
Row-Level Security on every table. Every Supabase table enables RLS; rows are visible only to the authenticated user who created them. The service-role client (used only in server-side scan writes) bypasses RLS intentionally and is never exposed to the browser.
-
Secrets in environment variables only. The Azure client secret, Supabase service key, and credential encryption key are in
.env.localand are never committed. -
Graph throttle-aware. All Graph calls retry on HTTP 429 (respecting the
Retry-Afterheader) and 503, with exponential backoff up to three attempts.
The following aspects of the ACSC Essential Eight cannot be verified solely via the Microsoft Graph API and require supplementary evidence:
| Limitation | Why |
|---|---|
| OS patch timing | Intune compliance policies capture whether a minimum version is required, not whether patches were applied within the ACSC-mandated 48-hour / 1-month windows. Windows Update for Business or WSUS reports are needed. |
| Application control policy content | Custom OMA-URI profiles (opaque to the Graph API) are flagged as not_assessable. The full WDAC allow-list cannot be inspected via API; manual review is required. |
| Phishing-resistant MFA enforcement | The check verifies that admins have registered phishing-resistant methods, not that they are required by a Conditional Access authentication strength policy (Entra P2). |
| Break-glass account detection | Accounts are identified as break-glass by a /break.?glass/i regex on displayName. Non-standard naming conventions will cause them to be counted as regular Global Administrators. |
| Four of the Essential Eight strategies | Patch applications, Office macro settings, user application hardening, and regular backups are not in scope — they require endpoint agents or on-premises tooling unavailable via Graph. |
- Phase 2 — Live tenant connection: OAuth 2.0 client-credentials flow against a real Entra ID app registration; tenant onboarding UI; encrypted credential storage in Supabase.
- Phase 3 — Scheduled scans: Cron-triggered background scans via Supabase Edge Functions; scan-to-scan diff view highlighting regressions.
- Phase 4 — Remaining Essential Eight strategies: Patch applications (Intune apps compliance), Office macro settings (Intune configuration profiles), user application hardening (browser baseline policies).
- Phase 5 — Multi-tenant SaaS: Supabase per-organisation RLS, billing, and a public API for exporting posture data to GRC platforms.
- Node.js 22+
- pnpm 10+
- A Microsoft Entra ID app registration with the Graph permissions listed in Security
- A Supabase project in the Sydney region (
ap-southeast-2)
git clone https://github.com/MG-Teban/e8-posture.git
cd e8-posture
pnpm installCopy .env.example to .env.local and fill in the values:
cp .env.example .env.local| Variable | Description |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase anon (public) key |
SUPABASE_SERVICE_ROLE_KEY |
Supabase service-role key (server-only) |
CREDENTIALS_ENCRYPTION_KEY |
32-byte hex AES-256-GCM key for tenant secret storage |
AZURE_TENANT_ID |
Entra ID tenant ID |
AZURE_CLIENT_ID |
App registration client ID |
AZURE_CLIENT_SECRET |
App registration client secret |
NEXT_PUBLIC_DEMO_MODE |
Set to true to use fixture data (default for production) |
Leave NEXT_PUBLIC_DEMO_MODE=true to run the full UI without Graph credentials.
Apply the Supabase migration to create the schema and RLS policies:
# With the Supabase CLI installed:
supabase db push
# Or apply the SQL file manually via the Supabase dashboard:
# supabase/migrations/20260616000000_init_schema.sqlpnpm devOpen http://localhost:3000.
pnpm test # run once
pnpm test:watch # watch mode
pnpm test:coverage # v8 coverage reportAll 48 tests run in Node (no browser required). The integration test in
tests/integration/scan-pipeline.test.ts runs the full scan pipeline against the
mock-tenant fixtures and snapshots the ML vector — update the snapshot with
pnpm test -- --update-snapshots if you change the fixture data.
-
In the Entra admin centre, go to App registrations → New registration.
-
Name:
e8-posture· Account type: This directory only · No redirect URI needed. -
After creation, copy the Application (client) ID and Directory (tenant) ID to your
.env.local. -
Go to Certificates & secrets → New client secret. Copy the secret value (shown only once) to
AZURE_CLIENT_SECRET. -
Go to API permissions → Add a permission → Microsoft Graph → Application permissions and add:
Permission Reason Reports.Read.AllMFA registration details Policy.Read.AllConditional Access policies RoleManagement.Read.DirectoryDirectory role assignments (PIM + permanent) DeviceManagementManagedDevices.Read.AllIntune device compliance state DeviceManagementConfiguration.Read.AllIntune configuration profiles (WDAC) User.Read.AllUser count and assigned licences -
Click Grant admin consent for your directory.
- Import the repository in the Vercel dashboard.
- Add the environment variables from
.env.exampleunder Project → Settings → Environment Variables. - Set
NEXT_PUBLIC_DEMO_MODE=truefor the production environment (demo mode is on by default — remove or set tofalseonce you have live credentials). - Deploy. The build runs
pnpm buildand the app is served as a Next.js serverless deployment.
MIT