licenselens 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. licenselens-0.3.0/.debug-journal.md +47 -0
  2. licenselens-0.3.0/.dockerignore +14 -0
  3. licenselens-0.3.0/.env.example +11 -0
  4. licenselens-0.3.0/.github/ISSUE_TEMPLATE/check-request.md +31 -0
  5. licenselens-0.3.0/.github/PULL_REQUEST_TEMPLATE.md +18 -0
  6. licenselens-0.3.0/.github/workflows/ci.yml +77 -0
  7. licenselens-0.3.0/.github/workflows/publish.yml +26 -0
  8. licenselens-0.3.0/.gitignore +38 -0
  9. licenselens-0.3.0/CHANGELOG.md +164 -0
  10. licenselens-0.3.0/CODE_OF_CONDUCT.md +31 -0
  11. licenselens-0.3.0/CONTRIBUTING.md +58 -0
  12. licenselens-0.3.0/Dockerfile +23 -0
  13. licenselens-0.3.0/LICENSE +21 -0
  14. licenselens-0.3.0/PKG-INFO +161 -0
  15. licenselens-0.3.0/README.md +127 -0
  16. licenselens-0.3.0/SECURITY.md +42 -0
  17. licenselens-0.3.0/catalog/capabilities.yaml +180 -0
  18. licenselens-0.3.0/catalog/sku_service_plans.yaml +28 -0
  19. licenselens-0.3.0/checks/defender/mde-onboard-gap.yaml +31 -0
  20. licenselens-0.3.0/checks/defender/mdi-sensors-missing.yaml +31 -0
  21. licenselens-0.3.0/checks/defender/mdo-p2-policies-default.yaml +31 -0
  22. licenselens-0.3.0/checks/identity/id-access-reviews-unused.yaml +33 -0
  23. licenselens-0.3.0/checks/identity/id-ca-priv-gaps.yaml +32 -0
  24. licenselens-0.3.0/checks/identity/id-dormant-privileged.yaml +31 -0
  25. licenselens-0.3.0/checks/identity/id-idprotect-off.yaml +31 -0
  26. licenselens-0.3.0/checks/identity/id-pim-unused.yaml +32 -0
  27. licenselens-0.3.0/checks/identity/id-security-defaults-on.yaml +39 -0
  28. licenselens-0.3.0/checks/purview/pur-dlp-not-enforced.yaml +31 -0
  29. licenselens-0.3.0/checks/sentinel/sen-analytics-rule-coverage.yaml +31 -0
  30. licenselens-0.3.0/checks/sentinel/sen-ueba-not-enabled.yaml +31 -0
  31. licenselens-0.3.0/docs/adding-a-check.md +54 -0
  32. licenselens-0.3.0/docs/app-registration.md +88 -0
  33. licenselens-0.3.0/docs/architecture.md +72 -0
  34. licenselens-0.3.0/docs/comparison.md +28 -0
  35. licenselens-0.3.0/docs/getting-started.md +170 -0
  36. licenselens-0.3.0/docs/lab-seed-checklist.md +39 -0
  37. licenselens-0.3.0/docs/limitations.md +48 -0
  38. licenselens-0.3.0/docs/manual-test-checklist.md +112 -0
  39. licenselens-0.3.0/docs/msp-batch.md +63 -0
  40. licenselens-0.3.0/docs/permissions.md +56 -0
  41. licenselens-0.3.0/docs/tenant-provisioning-guide.md +179 -0
  42. licenselens-0.3.0/examples/sample-report/README.md +17 -0
  43. licenselens-0.3.0/examples/sample-report/security-license-lens-report.html +1040 -0
  44. licenselens-0.3.0/examples/sample-report/security-license-lens-report.json +895 -0
  45. licenselens-0.3.0/examples/sample-report/security-license-lens-report.md +257 -0
  46. licenselens-0.3.0/examples/tenants.yaml +23 -0
  47. licenselens-0.3.0/pyproject.toml +83 -0
  48. licenselens-0.3.0/scripts/check_creds.py +107 -0
  49. licenselens-0.3.0/src/licenselens/__init__.py +5 -0
  50. licenselens-0.3.0/src/licenselens/auth.py +173 -0
  51. licenselens-0.3.0/src/licenselens/batch.py +168 -0
  52. licenselens-0.3.0/src/licenselens/catalog/__init__.py +13 -0
  53. licenselens-0.3.0/src/licenselens/catalog/loader.py +123 -0
  54. licenselens-0.3.0/src/licenselens/cli.py +716 -0
  55. licenselens-0.3.0/src/licenselens/cli_prompts.py +247 -0
  56. licenselens-0.3.0/src/licenselens/collectors/__init__.py +1 -0
  57. licenselens-0.3.0/src/licenselens/collectors/access_reviews.py +16 -0
  58. licenselens-0.3.0/src/licenselens/collectors/arm.py +138 -0
  59. licenselens-0.3.0/src/licenselens/collectors/conditional_access.py +154 -0
  60. licenselens-0.3.0/src/licenselens/collectors/mde.py +158 -0
  61. licenselens-0.3.0/src/licenselens/collectors/privileged_roles.py +156 -0
  62. licenselens-0.3.0/src/licenselens/collectors/purview.py +125 -0
  63. licenselens-0.3.0/src/licenselens/collectors/secure_score.py +178 -0
  64. licenselens-0.3.0/src/licenselens/collectors/security_defaults.py +19 -0
  65. licenselens-0.3.0/src/licenselens/collectors/sentinel.py +196 -0
  66. licenselens-0.3.0/src/licenselens/collectors/signins.py +60 -0
  67. licenselens-0.3.0/src/licenselens/collectors/skus.py +106 -0
  68. licenselens-0.3.0/src/licenselens/collectors/workspace_discover.py +83 -0
  69. licenselens-0.3.0/src/licenselens/diff_report.py +133 -0
  70. licenselens-0.3.0/src/licenselens/doctor.py +419 -0
  71. licenselens-0.3.0/src/licenselens/engine/__init__.py +6 -0
  72. licenselens-0.3.0/src/licenselens/engine/evaluate.py +1207 -0
  73. licenselens-0.3.0/src/licenselens/engine/loader.py +99 -0
  74. licenselens-0.3.0/src/licenselens/engine/quality.py +143 -0
  75. licenselens-0.3.0/src/licenselens/engine/rank.py +143 -0
  76. licenselens-0.3.0/src/licenselens/engine/rollup.py +153 -0
  77. licenselens-0.3.0/src/licenselens/engine/runner.py +753 -0
  78. licenselens-0.3.0/src/licenselens/errors.py +26 -0
  79. licenselens-0.3.0/src/licenselens/graph.py +197 -0
  80. licenselens-0.3.0/src/licenselens/models.py +391 -0
  81. licenselens-0.3.0/src/licenselens/output.py +47 -0
  82. licenselens-0.3.0/src/licenselens/paths.py +37 -0
  83. licenselens-0.3.0/src/licenselens/report/__init__.py +7 -0
  84. licenselens-0.3.0/src/licenselens/report/html.py +39 -0
  85. licenselens-0.3.0/src/licenselens/report/json_report.py +17 -0
  86. licenselens-0.3.0/src/licenselens/report/markdown.py +138 -0
  87. licenselens-0.3.0/templates/report.html.j2 +536 -0
  88. licenselens-0.3.0/tests/__init__.py +0 -0
  89. licenselens-0.3.0/tests/fake_clients.py +257 -0
  90. licenselens-0.3.0/tests/fixtures/scan-baseline.json +13 -0
  91. licenselens-0.3.0/tests/fixtures/scan-new.json +13 -0
  92. licenselens-0.3.0/tests/test_auth.py +23 -0
  93. licenselens-0.3.0/tests/test_batch.py +125 -0
  94. licenselens-0.3.0/tests/test_catalog.py +115 -0
  95. licenselens-0.3.0/tests/test_checks_loader.py +45 -0
  96. licenselens-0.3.0/tests/test_cli.py +244 -0
  97. licenselens-0.3.0/tests/test_collectors_graph.py +391 -0
  98. licenselens-0.3.0/tests/test_collectors_live.py +270 -0
  99. licenselens-0.3.0/tests/test_diff_report.py +97 -0
  100. licenselens-0.3.0/tests/test_doctor_dry_run.py +61 -0
  101. licenselens-0.3.0/tests/test_doctor_permissions.py +115 -0
  102. licenselens-0.3.0/tests/test_evaluate_defender.py +68 -0
  103. licenselens-0.3.0/tests/test_evaluate_identity.py +219 -0
  104. licenselens-0.3.0/tests/test_evaluate_new_identity.py +129 -0
  105. licenselens-0.3.0/tests/test_evaluate_pim_dormant.py +84 -0
  106. licenselens-0.3.0/tests/test_evaluate_sentinel_purview.py +70 -0
  107. licenselens-0.3.0/tests/test_graph_client.py +75 -0
  108. licenselens-0.3.0/tests/test_output.py +43 -0
  109. licenselens-0.3.0/tests/test_permissions.py +62 -0
  110. licenselens-0.3.0/tests/test_quality.py +113 -0
  111. licenselens-0.3.0/tests/test_rank.py +118 -0
  112. licenselens-0.3.0/tests/test_rank_proxy.py +93 -0
  113. licenselens-0.3.0/tests/test_rollup.py +150 -0
  114. licenselens-0.3.0/tests/test_runner_live.py +249 -0
  115. licenselens-0.3.0/tests/test_scan_dry_run.py +168 -0
  116. licenselens-0.3.0/tests/test_skus_parse.py +32 -0
  117. licenselens-0.3.0/tests/test_workspace_discover.py +138 -0
  118. licenselens-0.3.0/uv.lock +1007 -0
@@ -0,0 +1,47 @@
1
+ # Debug Journal - pytest collection import
2
+ Started: 2026-08-11T12:38:29-04:00
3
+ Goal: Determine why the documented local baseline command fails to import `tests.fake_clients` and preserve a reliable full-suite gate.
4
+
5
+ ## Environment snapshot (Phase 0)
6
+ - Runtime: Python >=3.12 project launched through `uv run`; exact interpreter query pending.
7
+ - Entry: `uv run pytest -q`
8
+ - Ports / sockets: none.
9
+ - Git HEAD: `3f76fb0`; branch `main` is one commit ahead of `origin/main`; pre-existing untracked `.omo/`.
10
+ - References read: `debugging/references/runtimes/python.md`, `debugging/references/methodology/00-setup.md`, `debugging/references/methodology/02-investigate.md`.
11
+
12
+ ## Hypotheses
13
+ 1. [OPEN] The `pytest` console-script launcher omits the repository root from `sys.path`, while `python -m pytest` includes it - distinguishing evidence: interpreter and `sys.path` captured under both launchers - if true, fix is: invocation contract.
14
+ 2. [OPEN] Python 3.14 resolves an installed or namespace package named `tests` instead of this repository's directory - distinguishing evidence: `sys.version`, `find_spec("tests")`, and import result on Python 3.14 versus a supported runtime - if true, fix is: package marker.
15
+ 3. [OPEN] Pytest's configured `pythonpath = ["src"]` and import mode make `tests.fake_clients` invalid because `tests/` is not a package - distinguishing evidence: successful collection after a temporary package marker or explicit root path, with launcher unchanged - if true, fix is: package marker.
16
+
17
+ ## Failed hypothesis round counter
18
+ - Round 1: pending.
19
+ - Round 2: pending.
20
+
21
+ ## Artifacts to revert
22
+ - [ ] `.debug-journal.md` - remove after final verification.
23
+ - [ ] `.git/info/exclude` local line for `.debug-journal.md` - remove after journal cleanup.
24
+ - [ ] Existing `.venv/` and `.pytest_cache/` were touched by the baseline `uv run pytest -q`; preserve rather than deleting pre-existing user state.
25
+
26
+ ## Findings
27
+ ### 2026-08-11T12:35:06-04:00 - baseline full suite
28
+ - Source: `uv run pytest -q`
29
+ - Value: `ModuleNotFoundError: No module named 'tests'` while importing `tests/test_collectors_graph.py`.
30
+ - Interpretation: collection fails before any test executes.
31
+ - Refutes/Confirms: establishes the repro; hypotheses remain open.
32
+
33
+ ## Oracle Triple (if invoked)
34
+
35
+ ## Final fix
36
+
37
+ ### 2026-08-11T12:39:00-04:00 - interpreter and import resolution
38
+ - Source: `uv run python -c '<runtime introspection>'`
39
+ - Value: `3.14.6`; executable `/Users/waifumachine/licenselens/.venv/bin/python3`; `tests_spec` resolves to `/Users/waifumachine/licenselens/tests`; `fake_spec` resolves to `/Users/waifumachine/licenselens/tests/fake_clients.py`.
40
+ - Interpretation: local namespace-package resolution works when Python is launched as a module from the repository root.
41
+ - Refutes/Confirms: refutes H2 shadowing in the module launcher; H1/H3 remain open.
42
+
43
+ ### 2026-08-11T12:39:00-04:00 - module invocation control
44
+ - Source: `uv run python -m pytest -q`
45
+ - Value: `144 passed in 1.35s`.
46
+ - Interpretation: production and tests are healthy under the module invocation; the failure is launcher/package-path specific.
47
+ - Refutes/Confirms: supports H1 and H3; refutes a general suite defect.
@@ -0,0 +1,14 @@
1
+ .venv
2
+ .venv/
3
+ __pycache__
4
+ *.pyc
5
+ *.egg-info
6
+ .pytest_cache
7
+ .ruff_cache
8
+ reports
9
+ dist
10
+ build
11
+ openspec
12
+ docs
13
+ examples
14
+ tests
@@ -0,0 +1,11 @@
1
+ # Copy to .env for local live scans (never commit real secrets).
2
+ # Prefer exporting these in your shell or CI secret store.
3
+
4
+ AZURE_TENANT_ID=00000000-0000-0000-0000-000000000000
5
+ AZURE_CLIENT_ID=00000000-0000-0000-0000-000000000000
6
+
7
+ # Required only for --auth client_secret
8
+ AZURE_CLIENT_SECRET=
9
+
10
+ # Optional: default public client is used for device-code if CLIENT_ID omitted
11
+ # (not recommended for production assessments — register your own app).
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: Check request
3
+ about: Propose a new Security License Lens check
4
+ title: "check: "
5
+ labels: check
6
+ ---
7
+
8
+ ## Capability / SKU
9
+
10
+ Which Microsoft SKU or service plan unlocks this?
11
+
12
+ ## Expected configuration
13
+
14
+ What should be true when the capability is realized?
15
+
16
+ ## Gap signal
17
+
18
+ How do we detect default / unused / partial configuration?
19
+
20
+ ## Workload
21
+
22
+ - [ ] Identity / Entra
23
+ - [ ] Defender / XDR
24
+ - [ ] Sentinel
25
+ - [ ] Purview
26
+ - [ ] Endpoint
27
+ - [ ] Other
28
+
29
+ ## References
30
+
31
+ Learn.microsoft.com or product docs links.
@@ -0,0 +1,18 @@
1
+ ## Summary
2
+
3
+ <!-- What changed and why -->
4
+
5
+ ## Type
6
+
7
+ - [ ] New check
8
+ - [ ] Catalog update
9
+ - [ ] Collector / engine
10
+ - [ ] Docs
11
+ - [ ] Fix
12
+
13
+ ## Checklist
14
+
15
+ - [ ] `ruff check src tests` passes
16
+ - [ ] `pytest` passes
17
+ - [ ] `licenselens scan --dry-run` still works
18
+ - [ ] No secrets or customer tenant data included
@@ -0,0 +1,77 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.12", "3.13"]
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+
23
+ - name: Install package
24
+ run: |
25
+ python -m pip install --upgrade pip
26
+ pip install -e ".[dev]"
27
+
28
+ - name: Ruff lint
29
+ run: ruff check src tests
30
+
31
+ - name: Ruff format check
32
+ run: ruff format --check src tests
33
+
34
+ - name: Pytest with coverage
35
+ run: pytest tests/ --cov=licenselens --cov-report=term-missing --cov-fail-under=72 -q
36
+
37
+ - name: Dry-run scan
38
+ # Exit 1 means gaps/partial findings (expected in demo data)
39
+ run: |
40
+ set +e
41
+ licenselens scan -o reports
42
+ code=$?
43
+ if [ "$code" -ne 0 ] && [ "$code" -ne 1 ]; then exit "$code"; fi
44
+
45
+ build:
46
+ runs-on: ubuntu-latest
47
+
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+
51
+ - name: Set up Python
52
+ uses: actions/setup-python@v5
53
+ with:
54
+ python-version: "3.12"
55
+
56
+ - name: Build wheel + sdist
57
+ run: |
58
+ python -m pip install --upgrade pip
59
+ pip install build
60
+ python -m build
61
+ ls -la dist/
62
+
63
+ - name: Smoke-test installed wheel
64
+ run: |
65
+ python -m venv /tmp/wheel-venv
66
+ /tmp/wheel-venv/bin/pip install dist/*.whl
67
+ /tmp/wheel-venv/bin/licenselens version
68
+ /tmp/wheel-venv/bin/licenselens checks
69
+ /tmp/wheel-venv/bin/licenselens diff --help
70
+ /tmp/wheel-venv/bin/licenselens batch --help
71
+ /tmp/wheel-venv/bin/licenselens discover-workspace --help
72
+ set +e
73
+ /tmp/wheel-venv/bin/licenselens scan -o /tmp/wheel-reports
74
+ code=$?
75
+ if [ "$code" -ne 0 ] && [ "$code" -ne 1 ]; then exit "$code"; fi
76
+ # HTML rendered means templates + catalog were packaged via licenselens/data
77
+ test -f /tmp/wheel-reports/security-license-lens-report.html
@@ -0,0 +1,26 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ id-token: write
10
+ contents: read
11
+
12
+ jobs:
13
+ pypi-publish:
14
+ name: Build and publish
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+ - name: Build distributions
22
+ run: |
23
+ python -m pip install --upgrade build
24
+ python -m build
25
+ - name: Publish to PyPI
26
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,38 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ .eggs/
7
+ dist/
8
+ build/
9
+ .venv/
10
+ venv/
11
+ .env
12
+ .env.*
13
+ !.env.example
14
+
15
+ # Test / coverage
16
+ .pytest_cache/
17
+ .coverage
18
+ htmlcov/
19
+ .mypy_cache/
20
+ .ruff_cache/
21
+
22
+ # IDE
23
+ .idea/
24
+ .vscode/
25
+ *.swp
26
+ .DS_Store
27
+
28
+ # Security License Lens outputs
29
+ reports/
30
+ reports-live/
31
+ *.report.html
32
+ *.report.json
33
+ out/
34
+
35
+ # Local planning / tooling (not part of the public product)
36
+ openspec/
37
+ .opencode/
38
+ .omo/
@@ -0,0 +1,164 @@
1
+ # Changelog
2
+
3
+ All notable changes to Security License Lens are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/).
7
+
8
+ ## [0.3.0] — 2026-08-12
9
+
10
+ ### Added
11
+ - **Scan diff (`licenselens diff`)** — compare two scan JSON artifacts by
12
+ `check_id`; groups new gaps, resolved, improved, worsened, unchanged, and
13
+ confidence changes; Markdown or JSON output
14
+ - **Batch scans (`licenselens batch`)** — run one `tenants.yaml` across many
15
+ tenants; per-tenant `reports/<slug>/<timestamp>/` output plus `index.md`;
16
+ one failing tenant no longer aborts the batch
17
+ - **Workspace discovery (`licenselens discover-workspace`)** — enumerate
18
+ subscriptions and Log Analytics workspaces, probe each for
19
+ `Microsoft.SecurityInsights/alertRules`, and print Sentinel-capable ARM IDs
20
+ - **Doctor profiles (`licenselens doctor --profile`)** — `basic` (core Graph)
21
+ is the default; `full` also probes the Defender for Endpoint API and the
22
+ Sentinel workspace when `--workspace-resource-id` is provided
23
+ - **Auto-discovery in scans** — `run_scan(..., discover_workspaces=True)`
24
+ selects a workspace only when exactly one Sentinel-capable workspace exists
25
+ (otherwise warns and refuses to guess)
26
+ - **Structured output layout** — `output.build_report_dir` slug/timestamp
27
+ nesting with a `flat=True` legacy mode
28
+ - **Demo and quickstart commands** — `licenselens demo`, `licenselens quickstart`
29
+ - **Top-card rollup** — capability rollup (% realized), ranked moves (impact,
30
+ exposure, effort), EXPOSED chip for legacy auth and MFA-less GA
31
+ - **Interactive `scan`** — in a TTY, prompts for demo vs live, auth method,
32
+ missing credentials, output dir, optional browser open / Sentinel / doctor;
33
+ non-TTY stays dry-run by default and never hangs
34
+ - **Batch defaults** — `tenants.yaml` top-level `defaults:` merged into each
35
+ tenant; optional `packs` and `allow_email_proxy`
36
+ - **HTML TAGLINE** — report now uses the product tagline in its header and footer
37
+ - **Entitlement provenance** — every detected SKU traces back to its parent
38
+ SKU, so reports explain which entitlement licensed each capability; CLI
39
+ pack selection is validated against the catalog
40
+ - **Doctor permission check** — required Graph permission tuples are synced
41
+ to collector definitions, and doctor reports any missing
42
+ `graphPermissions` per collector
43
+ - **Tenant provisioning guide** — step-by-step docs for app registration,
44
+ permissions, and seeding a tenant for a first live assessment
45
+ - **Credential gate** — `scripts/check_creds.py` gives a three-state verdict
46
+ for live validation: `CREDENTIALS_OK`, `BLOCKED` (missing `AZURE_*`
47
+ variables), or `CONNECTIVITY_FAILED`, never printing the client secret
48
+
49
+ ### Changed
50
+ - **Default packs** are `identity` + `endpoint` (email off by default)
51
+ - **MDO email check** no longer uses Secure Score by default. No Graph API
52
+ reads Safe Links / Safe Attachments / preset policy config (Exchange Online
53
+ PowerShell only). Opt-in: `--allow-email-proxy` (labeled, never rolls up to
54
+ fully working). Doctor reports email policy unreadability with a
55
+ portal/PowerShell one-line fix.
56
+ - **Live SKIPPED warnings** now separate "cannot verify via Graph" (email
57
+ policy config) from genuinely unimplemented evaluators
58
+ - **Reports are evidence-led** — every finding ties observed evidence to the
59
+ expected control and the capability it maps to, with actionable next steps
60
+ - **CLI counts are truthful** — license priority figures reflect what was
61
+ actually detected, exposure rendering is consistent, and disclosure
62
+ markers flag degraded proxy paths
63
+ - **Security Defaults guidance corrected** — guidance now distinguishes
64
+ tenants where Security Defaults is the right baseline from tenants licensed
65
+ for Conditional Access / Identity Protection
66
+ - **Explicit proxy opt-outs respected** — quality policy and ranked-move
67
+ output honor `--allow-email-proxy` opt-outs and never present a degraded
68
+ path as fully working
69
+ - **CI** now enforces `ruff format --check` and a 65% line coverage floor
70
+
71
+ ### Fixed
72
+ - **Error handling hardened** — Security Defaults, Conditional Access, and
73
+ Access Reviews evaluators degrade to a clear per-check status instead of
74
+ crashing the scan on unexpected API responses
75
+ - **Lint debt** — full Ruff lint and format cleanup across `src` and `tests`
76
+ - **Reproducible installs** — `uv.lock` is now tracked in the repository
77
+ - **Recurring scan instructions** — docs no longer misroute readers setting
78
+ up repeat assessments
79
+ - **JSON compatibility** — `CapabilitySummary` keeps the legacy JSON shape
80
+ for existing consumers
81
+
82
+ ### Notes
83
+ - `diff` rank ordering mirrors engine finding priority (gap < partial <
84
+ skipped < error < ok < not_licensed)
85
+ - Batch entries support `auth_mode` aliases (`device`, `client_secret`,
86
+ `azure_cli`, `dry_run`)
87
+
88
+ ## [0.2.0] — 2026-08-05
89
+
90
+ ### Added
91
+ - **Complete 10-check pack (Session F)**
92
+ - `sen-analytics-rule-coverage` — ARM SecurityInsights alert rules
93
+ - `sen-ueba-not-enabled` — ARM UEBA / entity analytics settings
94
+ - `pur-dlp-not-enforced` — Secure Score DLP/information-protection proxy
95
+ - ARM client (`https://management.azure.com`) for Sentinel
96
+ - CLI workspace binding: `--workspace-resource-id` or subscription/RG/name
97
+ - Doctor probe for Sentinel workspace when workspace ID is provided
98
+ - Dry-run demos for Sentinel + Purview; sample report updated
99
+
100
+ ### Notes
101
+ - All 10 registered checks evaluate when licensed (no more `skipped` for the original pack)
102
+ - Sentinel live scans require workspace + Microsoft Sentinel Reader (or equivalent)
103
+ - MDO/MDI/Purview may still rely on Secure Score proxies
104
+
105
+ ## [0.2.0b1] — 2026-08-05
106
+
107
+ ### Added
108
+ - **Defender pack (Session E)**
109
+ - `mdo-p2-policies-default` via Secure Score control signals (Safe Links/Attachments proxy)
110
+ - `mde-onboard-gap` via Defender for Endpoint API machine inventory vs licensed units
111
+ - `mdi-sensors-missing` via Secure Score control signals
112
+ - Secure Score collector (`SecurityEvents.Read.All`)
113
+ - MDE API client (`Machine.Read.All` on WindowsDefenderATP)
114
+ - Doctor probes for Secure Score and MDE API
115
+
116
+ ### Notes
117
+ - MDO/MDI use Secure Score as a proxy when direct policy/sensor APIs are unavailable
118
+ - Sentinel and Purview checks remain registered but skipped
119
+
120
+ ## [0.1.0] — 2026-08-05
121
+
122
+ First production-oriented release for **identity-first** Microsoft tenant assessments.
123
+
124
+ ### Added
125
+ - Product branding: **Security License Lens** (`licenselens` CLI package)
126
+ - Entitlement catalog mapping SKUs/service plans → capabilities with plain-language outcomes
127
+ - Ten registered checks (identity, defender, sentinel, purview, endpoint)
128
+ - **Live identity evaluators**
129
+ - `id-ca-priv-gaps` — Conditional Access MFA + legacy auth block
130
+ - `id-idprotect-off` — risk-based CA (Identity Protection outcomes)
131
+ - `id-pim-unused` — standing privileged roles vs PIM eligibility
132
+ - `id-dormant-privileged` — enabled privileged users with no recent successful sign-in
133
+ - Live Microsoft Graph auth: device code, client secret, Azure CLI
134
+ - Graph client with pagination and 429/5xx retries
135
+ - Live `subscribedSkus` collection and capability resolution
136
+ - `licenselens doctor` preflight (token, organization, SKUs, CA, role assignments)
137
+ - Static HTML / JSON / Markdown reports with customer-facing copy first
138
+ - Exit codes: `0` success, `1` gaps/partial, `2` auth/config/Graph error
139
+ - Docs: app registration, permissions, architecture, comparison, contributing checks
140
+ - Scrubbed dry-run sample report under `examples/sample-report/`
141
+
142
+ ### Notes
143
+ - Defender, Sentinel, and Purview checks are registered but still return `skipped` until collectors ship (v0.2 roadmap)
144
+ - Sign-in sampling for dormant privileged is page-capped; large tenants may see a truncation warning
145
+ - Findings are advisory; not a Microsoft product and not a compliance certification
146
+
147
+ ## [0.1.0b3] — 2026-08-05
148
+
149
+ - Session C: PIM + dormant privileged evaluators
150
+
151
+ ## [0.1.0b2] — 2026-08-05
152
+
153
+ - Session B: Conditional Access + Identity Protection evaluators
154
+
155
+ ## [0.1.0b1] — 2026-08-05
156
+
157
+ - Session A: live auth + subscribed SKUs + doctor
158
+
159
+ ## [0.1.0a1] — 2026-08-05
160
+
161
+ - Initial scaffold: catalog, checks YAML, dry-run engine, HTML report, plain-language layer
162
+
163
+ [0.3.0]: https://github.com/d4rk-pri0r/licenselens/releases/tag/v0.3.0
164
+ [0.1.0]: https://github.com/d4rk-pri0r/licenselens/releases/tag/v0.1.0
@@ -0,0 +1,31 @@
1
+ # Code of Conduct
2
+
3
+ ## Our pledge
4
+
5
+ We pledge to make participation in Security License Lens a harassment-free experience for everyone.
6
+
7
+ ## Our standards
8
+
9
+ Examples of behavior that contributes to a positive environment:
10
+
11
+ - Using welcoming and inclusive language
12
+ - Being respectful of differing viewpoints
13
+ - Accepting constructive criticism gracefully
14
+ - Focusing on what is best for the community
15
+
16
+ Examples of unacceptable behavior:
17
+
18
+ - Trolling, insulting/derogatory comments, and personal or political attacks
19
+ - Public or private harassment
20
+ - Publishing others' private information without permission
21
+ - Other conduct which could reasonably be considered inappropriate
22
+
23
+ ## Enforcement
24
+
25
+ Report unacceptable behavior to the maintainer via GitHub Security advisories or a private maintainer contact channel listed in SECURITY.md.
26
+
27
+ Maintainers may remove, edit, or reject contributions that do not align with this Code of Conduct.
28
+
29
+ ## Attribution
30
+
31
+ Adapted from the Contributor Covenant, version 2.1.
@@ -0,0 +1,58 @@
1
+ # Contributing to Security License Lens
2
+
3
+ Thanks for helping map paid Microsoft security capabilities to real configuration.
4
+
5
+ ## Ways to contribute
6
+
7
+ 1. **New checks** (highest value) — YAML under `checks/<workload>/`
8
+ 2. **Catalog updates** — service plan / SKU mappings in `catalog/`
9
+ 3. **Collectors** — read-only Graph / Azure data sources
10
+ 4. **Docs and samples** — clearer MSP/consultant workflows
11
+ 5. **Bug fixes and tests**
12
+
13
+ ## Development setup
14
+
15
+ ```bash
16
+ python3 -m venv .venv
17
+ source .venv/bin/activate
18
+ pip install -e ".[dev]"
19
+ ruff check src tests
20
+ pytest
21
+ licenselens scan -o reports
22
+ ```
23
+
24
+ ## Adding a check
25
+
26
+ Follow [docs/adding-a-check.md](docs/adding-a-check.md).
27
+
28
+ Minimum bar for a new check PR:
29
+
30
+ - Valid YAML with stable `id`
31
+ - `required_capabilities` pointing at catalog entries
32
+ - Clear remediation and at least one reference URL
33
+ - Unit test or fixture when collector logic is included
34
+ - No write/remediation API calls
35
+
36
+ ## Code style
37
+
38
+ - Python 3.12+, type hints preferred
39
+ - `ruff` for lint
40
+ - Do not commit secrets, tenant IDs from real customers, or unredacted reports
41
+
42
+ ## Public docs and releases
43
+
44
+ This repo is a standalone product. Keep GitHub-facing material presentation-neutral:
45
+
46
+ - Do **not** mention conference talks, CFPs, slide decks, “talk-ready” milestones, or event names in README, CHANGELOG, docs, CLI help, release notes, or commit messages meant for GitHub.
47
+ - Describe features in product terms (quick start, default packs, demo command).
48
+ - Local planning tools (OpenSpec changes, editor agent config) stay out of the repo — see `.gitignore`.
49
+
50
+ ## Pull requests
51
+
52
+ - One logical change per PR
53
+ - Link related issues
54
+ - Include dry-run output snippet when behavior changes
55
+
56
+ ## Code of conduct
57
+
58
+ Be respectful. See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
@@ -0,0 +1,23 @@
1
+ FROM python:3.12-slim
2
+ LABEL org.opencontainers.image.source="https://github.com/d4rk-pri0r/licenselens"
3
+ LABEL org.opencontainers.image.description="Security License Lens — detect Microsoft security configuration debt: capabilities you pay for but leave unused."
4
+ LABEL org.opencontainers.image.licenses="MIT"
5
+
6
+ ENV PYTHONUNBUFFERED=1
7
+ ENV PIP_NO_CACHE_DIR=1
8
+ WORKDIR /app
9
+
10
+ COPY pyproject.toml README.md LICENSE* ./
11
+ COPY src ./src
12
+ COPY checks ./checks
13
+ COPY catalog ./catalog
14
+ COPY templates ./templates
15
+
16
+ RUN pip install --upgrade pip && pip install .
17
+
18
+ # Reports land in /reports; mount a host directory there for dry-run or live scans.
19
+ RUN mkdir /reports
20
+ WORKDIR /reports
21
+
22
+ ENTRYPOINT ["licenselens"]
23
+ CMD ["demo", "-o", "/reports"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 d4rk-pri0r
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.