Azure / M365 CIS benchmark
Pentestas ships a modular CIS-aligned inspector framework that checks your Azure Entra, Exchange Online, SharePoint/OneDrive, Teams, and general M365 posture against the CIS Microsoft 365 Foundations Benchmark.
What it checks
Current inspectors (25, expanding):
Entra ID (6) — MFA enforced for admins, legacy auth blocked via Conditional Access, Security Defaults OR equivalent CA policies, user consent to apps restricted to low-risk scopes, guest-invite restricted to admins, two break-glass Global Admins.
Exchange Online (9) — SPF hard-fail, DKIM signing per domain, DMARC enforced, Safe Links, external forwarding blocked, modern auth enforced, mailbox auditing, external sender tag, transport-rule audit.
SharePoint (5) — external sharing level, default sharing link type + permission, anonymous-link lifetime, guest re-auth, legacy auth blocked.
Teams (4) — external access (federation) allowlist, guest policy, anonymous meeting join requires lobby, third-party apps restricted.
M365 general (1) — unified audit log enabled.
Run it
From the UI
Settings → Azure → Run CIS benchmark. Requires Azure credentials to be saved (service principal or paste-a-token).
From the API
POST /api/azure/cis-report?families=exchange,sharepoint,teams,entra,m365
Returns:
{
"report": {
"summary": {
"total": 25,
"counts": {"PASS": 14, "FAIL": 6, "SKIPPED": 5, "NOT_RUN": 0},
"pass_rate_percent": 56.0,
"by_family": {"exchange": {...}, "sharepoint": {...}, ...},
"by_profile": {"L1": {...}, "L2": {...}}
},
"rows": [
{
"check_id": "entra.mfa-admins",
"title": "Conditional Access enforces MFA for all admin roles",
"family": "entra",
"cis_ids": ["1.1.1"],
"cis_profiles": ["L1"],
"cis_license": ["E3"],
"severity": "CRITICAL",
"status": "FAIL",
"evidence": "Admin MFA not enforced for: Helpdesk Administrator, User Administrator",
"remediation": "Entra portal → Protection → Conditional Access → ...",
"references": [
"https://learn.microsoft.com/en-us/azure/active-directory/conditional-access/howto-conditional-access-policy-admin-mfa"
]
},
...
]
}
}
Credentials
The benchmark uses the same credentials as the Azure pentest:
- Service principal with Reader + Security Reader + AuditLog.Read.All.
- OR paste-a-token — run
az account get-access-tokenand paste the output. Works for one-hour scans.
See Azure subscription scan for setup details.
What "SKIPPED" means
Some CIS controls live only in Exchange PowerShell / Teams PowerShell (cmdlets like Get-SafeLinksPolicy, Get-TransportRule, Get-CsTeamsMeetingPolicy). Those aren't exposed via Graph, so Pentestas emits an honest SKIPPED row explaining which cmdlet verifies it. Future releases will add an Exchange PowerShell bridge via the Linux agent.
Adding custom checks
The inspector framework makes it trivial to add a check:
- Drop a file at
backend/tools/azure/cis/<family>/ci_<id>_<name>.py. - Subclass
CisInspector, set class attributes (CHECK_ID,CIS_IDS,CIS_PROFILES,DEFAULT_SEVERITY,REMEDIATION,REFERENCES). - Implement
audit(ctx)returning findings viaself.fail(...)/self.pass_record(...)/self.skipped(...).
The runner auto-discovers the file — no registration, no wiring.
See also
- Azure subscription scan — full deep-audit (beyond CIS)
- Plans and limits — CIS report is Business / Enterprise