Security License Lens
The security you already own (and ignore).
You pay for E5, Entra ID P2, Defender, and related SKUs. A lot of the useful controls in those SKUs never get turned on. LicenseLens maps what you own to the controls that should be on, then reports the gaps: you pay for X, we expected Y, we observed Z.
CLI: licenselens · Requires Python 3.12+
Documentation: d4rk-pri0r.github.io/licenselens
Sample report (dry-run): examples/sample-report/ — a scrubbed dry-run tenant.
Quick start
# One-command offline demo → HTML report (macOS / Linux / Windows)
pipx install licenselens
licenselens demo
# Interactive scan: prompts for anything missing (TTY)
licenselens scan
# Or jump straight to a live tenant walkthrough
licenselens quickstart
On Windows, the PyPI wheel bundles the PowerShell collector bridge since 0.4.0 (shipping in the 0.4.0 release; PyPI latest is 0.3.0 until it is published — see Releases), so the email pack and all PowerShell-only collectors run from a plain pipx install — see the Windows guide (requires Python 3.12+ and pipx).
Contributors: see contributing.md for the development setup.
In a terminal, licenselens scan asks demo vs live tenant, sign-in method, and
other missing options. Flags and AZURE_* env vars always win when set.
Non-interactive environments default to dry-run (or exit with a clear error on
--live without credentials).
Default priority packs are identity + endpoint. They shape the headline rollup and top actions; enabled checks still evaluate unless --workload filters them. Email policy config is not readable via Graph (PowerShell-only); use --allow-email-proxy only if you explicitly want a labeled Secure Score degraded path.
Live / MSP
licenselens doctor --live --auth client_secret
licenselens scan --live --auth client_secret -o reports
licenselens batch tenants.yaml -o reports
What it looks like
One HTML file with posture, entitlements, ranked gaps, and an explore view of every assessed control. It runs with JavaScript off and does not make network requests.
The dashboard: what you own, what's working, and what to fix first.
Every finding shows its evidence and a direct link to the admin page.
The same report at mobile width.
A concrete example
The most common finding, id-ca-priv-gaps:
- You pay for Microsoft 365 E5, so Conditional Access is licensed for every user.
- We expect MFA and legacy-auth blocking enforced through a CA policy.
- We observed zero Conditional Access policies → the report marks the tenant
EXPOSED. - Do this → enable an MFA CA policy (a few hours of work). The gap closes on the next scan.
Why Security License Lens?
| Tool | Optimizes for |
|---|---|
| ScubaGear | CISA baseline compliance |
| Maester | Continuous config tests (Pester) |
| Microsoft Secure Score | Score + recommendations (not SKU-gated) |
| License waste scripts | Seat assignment efficiency |
| Security License Lens | Owned SKUs → expected high-value controls → unused/default gaps |
See the documentation site for the full tool comparison.
Diff, discovery, and batch
# Compare two scan JSON artifacts by check_id
licenselens diff reports/before.json reports/after.json -o reports/diff.md
# Discover Sentinel-capable workspaces (prints ARM resource IDs)
licenselens discover-workspace --auth client_secret
# Multi-tenant scans from tenants.yaml (per-tenant reports + index.md)
licenselens batch tenants.yaml -o reports
Full check pack (v0.4.0)
166 checks · 31 capabilities · 11 profiles · 135 pinned SCuBA coverage rows (131 mapped) · package/sample 0.4.0
Checks run as direct, proxy, manual (you confirm), or dynamic
(direct first, Secure Score only if direct is missing). The report still records
whether a dynamic check ended up direct or proxy.
The authoritative pack lives in the generated reference (do not maintain a partial public table here):
- Check reference — collector, support state, evaluator, capabilities, evidence keys
- Capabilities · Profiles · Permissions · Coverage
- Machine-readable: reference/reference.json · manifest.json
Unlicensed capabilities report not_licensed instead of false gaps.
Known limitations
See limitations.md for the full list. Short version:
- Email pack off by default — no Graph API for MDO policy config (PowerShell-only);
--allow-email-proxyis opt-in and labeled (dynamic / Secure Score path) - Some surfaces are manual (operator-confirmed) or proxy (Secure Score); see the check reference for per-check state
- Sentinel needs a workspace ARM ID + Azure RBAC
- Sign-in / MDE inventories may truncate on huge tenants
- Findings are advisory, not a compliance certification
- No product telemetry by default
Architecture
SKUs / service plans → capability catalog → eligible checks
→ collectors (Graph / MDE / ARM) → findings → HTML / JSON / Markdown
Permissions
See permissions.md and app-registration.md.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success (no gap/partial findings) |
| 1 | Completed with gap or partial findings |
| 2 | Auth / configuration / API error |
Contributing
See contributing.md and adding-a-check.md.
Security
security.md — read-only, no telemetry by default.
License
Disclaimer
Security License Lens is an independent open-source project and is not affiliated with, endorsed by, or sponsored by Microsoft Corporation. Findings are advisory. “Microsoft”, “Entra”, “Defender”, “Sentinel”, and “Purview” are trademarks of their respective owners.
Metadata
Release files for licenselens 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| licenselens-0.4.0.tar.gz | 1.9 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| licenselens-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.9 MB
Release files / licenselens-0.4.0.tar.gz
| Download URL | licenselens-0.4.0.tar.gz |
|---|---|
| Size | 1.9 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
667e32ae44eb77e964ddcc6f55d34c01623f331400a1279b0197dcdb82c0b673
|
|
BLAKE2b-256 checksum How to use checksums |
fd845742a130e779aed1f0b11512ec8230c12ccdcdfcdf6befe640543222e040
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.
Transparency logRelease files / licenselens-0.4.0-py3-none-any.whl
| Download URL | licenselens-0.4.0-py3-none-any.whl |
|---|---|
| Size | 958.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9e6090f11fa70b226f98c0ab46ddafdd8a72e0499e8f3f0f70644441af68b207
|
|
BLAKE2b-256 checksum How to use checksums |
280db313de2bb1fa1de7e29885589af10a43dae8e4c0eef5956926b6808e8c5b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.
Transparency log