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.
- licenselens-0.3.0/.debug-journal.md +47 -0
- licenselens-0.3.0/.dockerignore +14 -0
- licenselens-0.3.0/.env.example +11 -0
- licenselens-0.3.0/.github/ISSUE_TEMPLATE/check-request.md +31 -0
- licenselens-0.3.0/.github/PULL_REQUEST_TEMPLATE.md +18 -0
- licenselens-0.3.0/.github/workflows/ci.yml +77 -0
- licenselens-0.3.0/.github/workflows/publish.yml +26 -0
- licenselens-0.3.0/.gitignore +38 -0
- licenselens-0.3.0/CHANGELOG.md +164 -0
- licenselens-0.3.0/CODE_OF_CONDUCT.md +31 -0
- licenselens-0.3.0/CONTRIBUTING.md +58 -0
- licenselens-0.3.0/Dockerfile +23 -0
- licenselens-0.3.0/LICENSE +21 -0
- licenselens-0.3.0/PKG-INFO +161 -0
- licenselens-0.3.0/README.md +127 -0
- licenselens-0.3.0/SECURITY.md +42 -0
- licenselens-0.3.0/catalog/capabilities.yaml +180 -0
- licenselens-0.3.0/catalog/sku_service_plans.yaml +28 -0
- licenselens-0.3.0/checks/defender/mde-onboard-gap.yaml +31 -0
- licenselens-0.3.0/checks/defender/mdi-sensors-missing.yaml +31 -0
- licenselens-0.3.0/checks/defender/mdo-p2-policies-default.yaml +31 -0
- licenselens-0.3.0/checks/identity/id-access-reviews-unused.yaml +33 -0
- licenselens-0.3.0/checks/identity/id-ca-priv-gaps.yaml +32 -0
- licenselens-0.3.0/checks/identity/id-dormant-privileged.yaml +31 -0
- licenselens-0.3.0/checks/identity/id-idprotect-off.yaml +31 -0
- licenselens-0.3.0/checks/identity/id-pim-unused.yaml +32 -0
- licenselens-0.3.0/checks/identity/id-security-defaults-on.yaml +39 -0
- licenselens-0.3.0/checks/purview/pur-dlp-not-enforced.yaml +31 -0
- licenselens-0.3.0/checks/sentinel/sen-analytics-rule-coverage.yaml +31 -0
- licenselens-0.3.0/checks/sentinel/sen-ueba-not-enabled.yaml +31 -0
- licenselens-0.3.0/docs/adding-a-check.md +54 -0
- licenselens-0.3.0/docs/app-registration.md +88 -0
- licenselens-0.3.0/docs/architecture.md +72 -0
- licenselens-0.3.0/docs/comparison.md +28 -0
- licenselens-0.3.0/docs/getting-started.md +170 -0
- licenselens-0.3.0/docs/lab-seed-checklist.md +39 -0
- licenselens-0.3.0/docs/limitations.md +48 -0
- licenselens-0.3.0/docs/manual-test-checklist.md +112 -0
- licenselens-0.3.0/docs/msp-batch.md +63 -0
- licenselens-0.3.0/docs/permissions.md +56 -0
- licenselens-0.3.0/docs/tenant-provisioning-guide.md +179 -0
- licenselens-0.3.0/examples/sample-report/README.md +17 -0
- licenselens-0.3.0/examples/sample-report/security-license-lens-report.html +1040 -0
- licenselens-0.3.0/examples/sample-report/security-license-lens-report.json +895 -0
- licenselens-0.3.0/examples/sample-report/security-license-lens-report.md +257 -0
- licenselens-0.3.0/examples/tenants.yaml +23 -0
- licenselens-0.3.0/pyproject.toml +83 -0
- licenselens-0.3.0/scripts/check_creds.py +107 -0
- licenselens-0.3.0/src/licenselens/__init__.py +5 -0
- licenselens-0.3.0/src/licenselens/auth.py +173 -0
- licenselens-0.3.0/src/licenselens/batch.py +168 -0
- licenselens-0.3.0/src/licenselens/catalog/__init__.py +13 -0
- licenselens-0.3.0/src/licenselens/catalog/loader.py +123 -0
- licenselens-0.3.0/src/licenselens/cli.py +716 -0
- licenselens-0.3.0/src/licenselens/cli_prompts.py +247 -0
- licenselens-0.3.0/src/licenselens/collectors/__init__.py +1 -0
- licenselens-0.3.0/src/licenselens/collectors/access_reviews.py +16 -0
- licenselens-0.3.0/src/licenselens/collectors/arm.py +138 -0
- licenselens-0.3.0/src/licenselens/collectors/conditional_access.py +154 -0
- licenselens-0.3.0/src/licenselens/collectors/mde.py +158 -0
- licenselens-0.3.0/src/licenselens/collectors/privileged_roles.py +156 -0
- licenselens-0.3.0/src/licenselens/collectors/purview.py +125 -0
- licenselens-0.3.0/src/licenselens/collectors/secure_score.py +178 -0
- licenselens-0.3.0/src/licenselens/collectors/security_defaults.py +19 -0
- licenselens-0.3.0/src/licenselens/collectors/sentinel.py +196 -0
- licenselens-0.3.0/src/licenselens/collectors/signins.py +60 -0
- licenselens-0.3.0/src/licenselens/collectors/skus.py +106 -0
- licenselens-0.3.0/src/licenselens/collectors/workspace_discover.py +83 -0
- licenselens-0.3.0/src/licenselens/diff_report.py +133 -0
- licenselens-0.3.0/src/licenselens/doctor.py +419 -0
- licenselens-0.3.0/src/licenselens/engine/__init__.py +6 -0
- licenselens-0.3.0/src/licenselens/engine/evaluate.py +1207 -0
- licenselens-0.3.0/src/licenselens/engine/loader.py +99 -0
- licenselens-0.3.0/src/licenselens/engine/quality.py +143 -0
- licenselens-0.3.0/src/licenselens/engine/rank.py +143 -0
- licenselens-0.3.0/src/licenselens/engine/rollup.py +153 -0
- licenselens-0.3.0/src/licenselens/engine/runner.py +753 -0
- licenselens-0.3.0/src/licenselens/errors.py +26 -0
- licenselens-0.3.0/src/licenselens/graph.py +197 -0
- licenselens-0.3.0/src/licenselens/models.py +391 -0
- licenselens-0.3.0/src/licenselens/output.py +47 -0
- licenselens-0.3.0/src/licenselens/paths.py +37 -0
- licenselens-0.3.0/src/licenselens/report/__init__.py +7 -0
- licenselens-0.3.0/src/licenselens/report/html.py +39 -0
- licenselens-0.3.0/src/licenselens/report/json_report.py +17 -0
- licenselens-0.3.0/src/licenselens/report/markdown.py +138 -0
- licenselens-0.3.0/templates/report.html.j2 +536 -0
- licenselens-0.3.0/tests/__init__.py +0 -0
- licenselens-0.3.0/tests/fake_clients.py +257 -0
- licenselens-0.3.0/tests/fixtures/scan-baseline.json +13 -0
- licenselens-0.3.0/tests/fixtures/scan-new.json +13 -0
- licenselens-0.3.0/tests/test_auth.py +23 -0
- licenselens-0.3.0/tests/test_batch.py +125 -0
- licenselens-0.3.0/tests/test_catalog.py +115 -0
- licenselens-0.3.0/tests/test_checks_loader.py +45 -0
- licenselens-0.3.0/tests/test_cli.py +244 -0
- licenselens-0.3.0/tests/test_collectors_graph.py +391 -0
- licenselens-0.3.0/tests/test_collectors_live.py +270 -0
- licenselens-0.3.0/tests/test_diff_report.py +97 -0
- licenselens-0.3.0/tests/test_doctor_dry_run.py +61 -0
- licenselens-0.3.0/tests/test_doctor_permissions.py +115 -0
- licenselens-0.3.0/tests/test_evaluate_defender.py +68 -0
- licenselens-0.3.0/tests/test_evaluate_identity.py +219 -0
- licenselens-0.3.0/tests/test_evaluate_new_identity.py +129 -0
- licenselens-0.3.0/tests/test_evaluate_pim_dormant.py +84 -0
- licenselens-0.3.0/tests/test_evaluate_sentinel_purview.py +70 -0
- licenselens-0.3.0/tests/test_graph_client.py +75 -0
- licenselens-0.3.0/tests/test_output.py +43 -0
- licenselens-0.3.0/tests/test_permissions.py +62 -0
- licenselens-0.3.0/tests/test_quality.py +113 -0
- licenselens-0.3.0/tests/test_rank.py +118 -0
- licenselens-0.3.0/tests/test_rank_proxy.py +93 -0
- licenselens-0.3.0/tests/test_rollup.py +150 -0
- licenselens-0.3.0/tests/test_runner_live.py +249 -0
- licenselens-0.3.0/tests/test_scan_dry_run.py +168 -0
- licenselens-0.3.0/tests/test_skus_parse.py +32 -0
- licenselens-0.3.0/tests/test_workspace_discover.py +138 -0
- 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,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.
|