Releases: Galvnyz/M365-Assess
Release list
v2.12.0
Completes the Report Clarity & Executive Briefing milestone and advances Trust at Scale. The report now opens with a decision-maker Executive Briefing, summary visuals speak plain English, the shipped registry is partitioned to what the tool actually assesses (with CI-enforced generated statistics), every check carries an explicit severity rating, large tenants get complete Graph collections via pagination/throttling retry, and EXO 3.8+ installs no longer have to be uninstalled to run an assessment. No breaking API changes.
Added
Invoke-SafeGraphRequest— Graph pagination + throttling retry (#952) — new shared helper inCommon/that follows@odata.nextLinkuntil collections are exhausted (bounded by a page cap that warns instead of truncating silently) and retries 429/503/504 responses with Retry-After-aware exponential backoff. First migrations:Get-EntAppSecurityConfig(replaces five hand-rolled pagination loops), the Stryker risky-apps query, and the Conditional Access policy fetch — tenants beyond one Graph page of apps/service principals/grants/policies now get complete results. Remaining call sites migrate incrementally under #952.- Executive Briefing first screen (#963): the report now opens with a compliance-led briefing built for decision-makers. A verdict card answers "are we compliant?" for the headline framework (readiness label, donut, plain-English coverage sentence with a quick-win projection, framework switcher chips), three stat tiles cover actionable criticals / quick wins / Microsoft Secure Score (demoted from hero), and a "What to do first" list deep-links the top Now-lane actions into the findings table. No CheckIDs, status vocabulary, or unexpanded acronyms appear on this screen. The old critical banner is superseded by the "Needs attention now" tile.
-HeadlineFrameworkparameter (#963): sets which framework(s) headline the Executive Briefing (tab-completes fromcontrols/frameworks/*.json; unknown ids fail fast with the valid list). Defaults to CIS Microsoft 365. The FrameworkQuilt opens on the same headline framework so both sections tell one story.REPORT_DATAgainsheadlineFrameworksandassessedAt.- Generated registry statistics with CI drift gate — public check counts and framework coverage numbers are now generated from
controls/registry.jsonbyscripts/Build-RegistryStats.ps1into marker blocks in README.md,controls/README.md, anddocs/user/COMPLIANCE.md, plus a new fully-generateddocs/reference/COVERAGE.md(per-framework mapping coverage, CISA SCuBA product-pillar coverage, per-collector counts, severity-rating and learn-more completeness). A new "Registry stats in sync" CI quality gate runs the script with-Checkand fails any PR that changes registry data without regenerating the docs — hand-typed check counts can no longer drift from the shipped registry.
Changed
- EXO 3.8.0+ no longer needs to be uninstalled — side-by-side version pinning (#231) — the module helper previously force-uninstalled ALL ExchangeOnlineManagement versions before installing 3.7.1, breaking other tooling that needs newer EXO. Now: if any compatible (< 3.8.0) version is installed alongside a newer one, the gate passes and
Connect-Servicepins the session import to it (Import-Module -RequiredVersion); when no compatible version exists, the repair installs 3.7.1 side-by-side without touching newer versions. The upstream MSAL conflict (msgraph-sdk-powershell#3576) remains unfixed as of EXO 3.10.0, so the < 3.8.0 session requirement itself is unchanged. - All 292 checks now carry explicit severity ratings (#956) — the 86 checks that silently displayed the
Mediumfallback are now rated inrisk-severity.json, seeded from the upstream CheckIDimpactRating.severity(38 High, 42 Medium, 2 Critical, 2 Low, 2 Info) and marked as seeded for future M365-context curation; the curated-vs-seeded rubric is documented incontrols/README.md.-QuickScanselects ~40 more checks than before (the seeded Critical/High additions) — runtime grows accordingly. COVERAGE.md now reports severity completeness at 292 of 292. - Report clarity pass (#962): summary visuals now group Skipped/Unknown/NotApplicable/NotLicensed as one muted "Not assessed" bucket (donut, framework bars, domain cards, conditional KPI tile); the findings table, filter chips, and appendix keep the full nine-status vocabulary. CheckID column ships hidden by default (re-enable via Columns). New "How to read this table" legend with one-sentence tooltips on every status badge. Number formats standardized to "N of M". Jargon sweep: PIM/SPF/DKIM/DMARC expanded on first use, plain-language scoring blurbs, "Targeted" eyebrow renamed "Critical exposure".
- Registry partitioned to M365 collector scope —
controls/registry.jsonpreviously carried the entire upstream CheckID registry, including 814 Windows-endpoint (WIN-*) and Azure-subscription (AZ-*) checks that no collector in this module can emit. The registry is now filtered to the 15 M365 collector families declared in the newcontrols/sync-scope.json(292 checks, including the 5 local extensions), shrinking the shipped registry from ~7.0 MB to ~3.0 MB and making every check count and framework statistic reflect what the tool actually assesses. Thesync-checkidworkflow applies the same partition on every upstream sync; out-of-scope content remains available in the upstream CheckID release.Import-ControlRegistryenforces the scope at load time as defense-in-depth and warns if an unpartitioned registry is detected. - Registry entries now expose
severityRated—Import-ControlRegistrymarks whether a check'sriskSeveritycame from an explicit rating inrisk-severity.json($true) or is theMediumfallback ($false), so downstream consumers can distinguish rated findings from defaulted ones. Display behavior is unchanged; 206 of 292 checks currently carry explicit ratings.
Fixed
- NaN counts for not-assessed statuses in framework math (#962):
buildFrameworkDataincremented count keys it never initialised for the four exotic statuses, producingNaNand a silent donut gap. Counting now routes through onesummaryBucket()helper. - Mismatched denominators in two KPI hints (#962): the Fails KPI hint said "of all checks" and the SharePoint pass-rate hint counted unscored checks, while their bars used the scored-check denominator. Both now say "of N scored checks".
- Framework group keys rendered as raw codes in the framework breakdown (#948) — several framework group maps were missing labels for groups the registry maps checks into, so those groups displayed the bare section number/prefix as the group name:
cis-m365-v6.jsonsections4(Intune) and9(Fabric / Power BI),cisa-scuba.jsonserviceMS.INTUNE,hipaa.jsonthe ten Breach Notification + Privacy Rule sections added by the CheckID v3.4.0 sync (#912), andiso-27002.jsongroups9/10(ISO 27001 management-clause ids that leak in via the upstream 27001/27002 mapping conflation, #871 — labels mirroriso-27001.jsonand can be dropped when upstream diverges the mappings). A new regression test cross-checks every group key derivable from registry controlIds against each framework's group map so a registry sync can no longer reintroduce unlabeled groups silently. - Stale check counts in docs —
docs/user/COMPLIANCE.mdcited three conflicting registry sizes (294, 1106, and 270); all now state the real M365-scoped count (292). The Metadata-Consistency test that guards the COMPLIANCE.md count pointed at a file location retired in the #906 docs consolidation and silently skipped; it now checksdocs/user/COMPLIANCE.mdand actually enforces the count. The registry-integrity suite gained a test that fails if an unpartitioned registry is ever committed, and the valid-collector test now derives its allowlist fromcontrols/sync-scope.jsoninstead of a hardcoded copy.
v2.11.0
The Data Quality & Accuracy milestone — 29 of 30 issues closed (#871 stays blocked on upstream CheckID/SCF). No breaking API changes. Ships several new findings-table capabilities, three research/spec decision artifacts, a misleading-check fix (SSPR semantic mismatch), and the CheckID v3.4.0 registry refresh.
Added
- Sequence column + filter chips on findings table (#898) — the Now/Next/Later/Done lane is now visible as a colour-coded pill column in the findings table, with filter chips above the table that slice to a single lane. Makes the table self-sufficient for "show me my Now lane" workflows that previously required the Roadmap section.
- Copy-finding-as-markdown button (#901) — per-row Copy button serializes the finding (title, status, recommendation, remediation) to a markdown summary on the clipboard. Designed for fast handoff into tickets or email.
- Truncated check-id with hover-tooltip in finding-detail (#900) — long
controlIdvalues now ellipsis-truncate with the full value available on hover viatitleattribute, tightening the layout without losing information. - REPORT-USER-GUIDE.md (#897, #904) — comprehensive walkthrough of the HTML report's interactive features: edit mode, finalize, hide blocks, framework quilt, roadmap lanes, and theme switcher. The missing user-facing companion to
REPORT-INTERNALS.md. - Roadmap-vs-findings-table hybrid decision (#899) —
docs/research/roadmap-vs-findings-table.mdsets the v3.0 trajectory: Roadmap stays as a presentation-mode view, interactive capabilities migrate into the findings table, Roadmap goes view-only at v3.0. - Owner / Ticket Phase 5 spec (#903) —
docs/specs/2026-04-30-owner-ticket-interaction.mdlocks down v1/v2 split for the 7 open design questions blocking #863 Phase 5 (free-form owner, single-owner-per-finding, ticket status as free-form dropdown, inline overlay edit, hide on Pass, persistence-on-hide, schemaVersion: 2). - Remediation-path rot decision (#879) —
docs/research/remediation-path-rot-decision.mdchooses Option A: prefer Microsoft Learn URLs over hardcoded admin-center breadcrumb paths as the primary remediation surface. Implementation phases tracked separately.
Changed
- CheckID v3.4.0 registry sync (#912) — 200+ control updates, expanded HIPAA coverage to all three Subparts (C Security, D Breach Notification, E Privacy Rule). Local framework taxonomy declarations restored after the sync stripped them; #914 tracks making the sync script preserve them going forward.
- Aggressive docs consolidation (#905, #906, #907, #910) — folder restructure + 19 stub redirects from the v2.10.x docs cleanup, plus a full module install matrix added to QUICKSTART covering all required Microsoft Graph submodules + EXO + SPO + Teams + PnP per OS.
- Microsoft Secure Score panel disclaimer — small italic note at the foot of the score card explaining that Microsoft refreshes the score on a delay (up to 24 hours) and that the value shown reflects Microsoft's last published value at assessment time, not the live tenant state. Surfaced during v2.11.0 live-test as a credibility / expectation-setting gap.
Fixed
- ENTRA-SSPR-001 collector measured the wrong setting (#878) — was reading
/policies/authenticationMethodsPolicy.registrationEnforcement.authenticationMethodsRegistrationCampaign(the MFA Registration Campaign) and labeling it as SSPR enablement. The legacy "Self service password reset enabled" (None / Selected / All) toggle is not exposed by Microsoft Graph as of the 2026-04 audit. Collector now emitsStatus=Reviewwith a manual-verify instruction pointing at the current Entra admin center path and the canonical MS Learn enablement walkthrough. Setting name realigned to the upstream registry entry. Filed CheckID upstream issue (Galvnyz/CheckID#399) for the registry's staleremediation.portal.pathand missing manual-only signal. - Quality Gates "Permissions matrix in sync" gate (#911) —
docs/PERMISSIONS.mdwas moved todocs/reference/PERMISSIONS.mdin #906 butscripts/Build-PermissionsMatrix.ps1's default-OutputPathwas not updated; CI's-Checkstep started failing on every PR. Updated default + regenerated the doc.
v2.10.1
Patch release. Four collector data-quality bugs surfaced during v2.10.0 live-test, plus the #845 taxonomy closeout. No breaking API changes. Sets the stage for v2.11.0 — Data Quality & Accuracy milestone, which covers the broader collector audit work surfaced this sprint (#878 SSPR semantic mismatch, #879 remediation-path rot, #886 PIM logic bug, #888 break-glass duplication, #884 Review/Unknown/Skipped audit).
Fixed
- ENTRA-ENTAPP-020 false-positives Microsoft Graph PowerShell SDK (#880) — the Microsoft first-party allowlist filtered SPs by a single owner-tenant GUID; Microsoft actually publishes first-party apps from at least 4 tenants. Empirical observation surfaced both Microsoft Corp (
72f988bf...) and the dedicated Graph Command Line Tools tenant (cdc5aeea-15c5-4db6-b079-fcadd2505dc2) as sources missing from the allowlist. Now covers all 4 known owner tenants. Long-term hardening (AppId-based allowlist) tracked in #887 - ENTRA-PIM- false-negatives on E5 Developer + other E5 variants (#881)* — license detection matched against a hardcoded list of 4 SkuIds, missing Developer Pack, education tiers, government tiers, and partner SKUs. Switched to detection by
AAD_PREMIUM_P2service plan ID (eec0eb4f-6444-4f95-aba0-50c24d67f998), which all P2-bundling SKUs resolve to. Same patternGet-TeamsSecurityConfig.ps1already uses for Teams licensing - ENTRA-ADMIN-003 break-glass detail hides matched UPNs in Review state (#882) — listed account names in the Pass branch but dropped them in the Review branch, the more important branch for the user. Both branches now list matched UPNs +
[DISABLED]tag, with displayName fallback when UPN is null - SPO-AUTH-001 always returned "Not available via API" Review (#883) —
Get-SharePointSecurityConfig.ps1read$spoSettings['legacyAuthProtocolsEnabled']but Graph v1.0sharepointSettingsexposes the property asisLegacyAuthProtocolsEnabled(boolean properties on this resource use theis-prefix). Wrong key returned$nullin every tenant; the check now correctly reports Pass/Fail - MITRE / STIG taxonomy decision documented + regression-guarded (#845, shipped via #877) — every framework JSON in
controls/frameworks/now declares either native taxonomy (groupBy+ groups map) OR an explicit fallback decision (taxonomyDecision: "domain-fallback"+taxonomyReason). New Pester regression intests/Behavior/Framework-Taxonomy.Tests.ps1enforces it. 13 frameworks have native taxonomy; MITRE ATT&CK + STIG declared deliberate domain-fallback (technique IDs / opaque rule IDs don't carry tactic / category metadata)
v2.10.0
The Polish & Audits milestone — 10 of 10 issues closed. Three audit-flavored research artifacts (ISO 27001/27002, CIS M365 v6.0.1, per-control narrative content) plus the long-standing SharePoint prefix-bug in the report's "Why It Matters" renderer. No breaking API changes.
Added
- Per-prefix narrative content for the finding-detail "Why It Matters" callout (#854) — expanded coverage from ~22 to ~70 prefix families across SPO / EXO / DNS / DEFENDER / ENTRA / CA / INTUNE / COMPLIANCE / TEAMS / POWERBI / PBI / FORMS. Order preserves more-specific-first (e.g.,
EXO-FORWARDbefore genericEXO-). Newdocs/research/narrative-content-sources.mdcaptures source-authority by family (Microsoft Learn, CIS M365 v6.0.1, NIST 800-63B / 800-53 r5, CISA, M3AAWG, FBI IC3, MITRE ATT&CK). Per-checkId refinement + architectural move tocontrols/narrative-overlay.jsondeferred to follow-up - ISO 27001 vs 27002 mapping audit (#858) — new
docs/research/iso-27001-vs-27002-audit.mddocuments the upstream SCF conflation (1020 / 1020 identical mappings) and recommends the upstream-fix path. Newiso-27002.jsonframework definition (was missing — registry tagged checks withiso-27002but no framework JSON existed). Newtests/Behavior/Iso-27001-27002-Mapping-Audit.Tests.ps1with skip-until-upstream divergence assertion + always-runs informational stats. Follow-up tracker #871 monitors when upstream lands - CIS M365 v6.0.1 mapping audit (#848) — new
docs/research/cis-m365-v6-audit.mdcatalogs section-9 POWERBI-/PBI- merge-artifact duplicates (11/11 clusters all parallel pairs — every Power BI / Fabric check ships twice and inflates coverage counts) + section-4 EXO-* labeling anomaly (4/6 checks are EXO despite section being named "Microsoft Intune"). Newtests/Behavior/Cis-M365-v6-Mapping-Audit.Tests.ps1with skip-until-upstream regressions on both anomalies + informational stats
Fixed
- SPO- / SHAREPOINT- prefix matching bug in
whyItMatters()(#854) — the chain checkedstartsWith('SHAREPOINT-')but the registry usesSPO-. Every SharePoint finding was hitting the generic fallback since SPO content was added. Now matches correctly with sub-prefix specificity (SHARING/B2B, SITE/ACCESS, SCRIPT/SWAY, SYNC/OD, MALWARE/VERSIONING/LOOP/AUTH/SESSION) plus legacy SHAREPOINT-/20B- catches retained for compatibility
v2.9.3
Big patch release covering the v2.7.0 — Deep UX milestone closeout plus the v2.10.0 — Polish & Audits UX work to date. Despite "patch" in the version label, this release ships a substantial new framework-coverage UX (per a Claude Design handoff) and several new capabilities — but no breaking API changes. The 3 remaining audit-flavored items (CIS mapping integrity, ISO 27001/27002, per-control narrative content) ship in v2.10.0 proper.
Added
- Artifact provenance + chain-of-custody (#867) — every audience-facing output now carries the M365-Assess version + generated-at timestamp. New
_Assessment-Provenance.jsonat the assessment root captures toolName / toolVersion / registryDataVersion / tenant info / sectionsRun + aoutputArtifactsarray with SHA-256 hashes of every other file in the folder. The XLSX Compliance Matrix sheet's Title row, the_Assessment-Summary_*.csvcomment header, and the_PermissionDeficits.jsonpayload all gained a version field too. Per-section CSVs intentionally unchanged - FrameworkQuilt redesign — adaptive single/multi layout (#751, #855) — One unified component branches on framework count: 0 → empty state, 1 → single-framework focus surface (donut score + family chart + primary CTA), 2+ → sortable comparison table + coverage chart + drill-down. Implements the Direction-Merged design from
docs/design/framework-redesign/. New components:ScoreDonut(animated SVG ring),FwManageButton(real form-control dropdown replacing the chip-shaped picker),CompareTableM(sortable),CoverageChart(sorted bar with position markers),FamilyChartM(clickable family rows),FilterBanner(action feedback with single Clear-all),ProfileChipsM(level chips promoted out of the buried expanded panel),GapsCTA(real primary action button) - HideableBlock for any card or section (#712) — extends edit-mode finding-row hide to ANY card/section. New generic
<HideableBlock hideKey>wrapper with hover ✕ overlay and ↩ Restore. Persists into REPORT_OVERRIDES on Finalize. v1 wraps 15 elements (Score card, 4 KPIs, 3 roadmap lanes, 7 appendix sub-cards) - Native taxonomy for 4 more frameworks (#845 partial) — HIPAA (6 safeguards via 164.X), SOC 2 TSC (5 criteria), Essential Eight (8 strategies), CISA SCUBA (6 services). Brings native-taxonomy coverage to 12 of 14 supported frameworks. MITRE ATT&CK + STIG documented as deliberate domain fallback
- Findings table columns resizable + sortable (#846) — drag handle on the right edge of every header (8px hot zone, 60px min-width, fr columns snap to px on first drag). Click sortable headers (Status / Finding / Domain / CheckID / Severity) to cycle none → asc → desc → none. Status sorts by enum order so Fail comes first. Both persist per-tenant in localStorage
-BaselineLabelparameter — already present from v2.9.2, no change here- Design handoff package (#856) —
docs/design/framework-redesign/committed as the source of truth for the redesign spec docs/LEVELS.md(#844) — semantic reference for level/profile chips. Locks down the per-check trust-the-registry model and rejects synthetic inheritance in code
Changed
- FilterBar consolidation (#847) — collapsed from 5 stacked rows to a single flowing row with vertical dividers between groups (STATUS / SEVERITY / FRAMEWORK / DOMAIN / LEVEL). Groups break as units when the viewport is narrower; chips inside a group never break across a separator. Density-aware compact mode (existing
[data-density="compact"]selector) halves vertical padding. FilterBar height drops from ~250px to ≤120px on a 1440px viewport - CIS M365 v6 sections complete — added the missing
4: Microsoft Intuneand9: Microsoft Fabricto the framework JSON so the family breakdown no longer shows(unmapped)rows for those sections - Topbar text-size control split into A− / A+ (#852) — replaced the single A/A+/A++ cycling button with two adjacent buttons that step one position each direction and disable at the boundaries. Tooltips show direction + current size. Persistence (
m365-text-scalelocalStorage key) unchanged
Fixed
- Appendix Email-authentication card SPF/DKIM predicates (#860) — the appendix used
r.SPF === 'Pass'andr.DKIMStatus === 'Pass'to count passing domains, but the data fields contain raw SPF records andOK/Not configuredfor DKIMStatus. The card always reported0/N passingeven when the top-of-report DNS panel showed correct counts. Aligned predicates withDnsAuthPanel - Finding-detail Current value outline now reflects status (refs #674) — was always red regardless of status, so a Pass finding's Current value visually read as failing. Now color-coded per tier: Pass green / Fail red / Warning amber / Review accent / Info muted. Recommended outline unchanged (always green — it's the target state)
- Level chip text "L2 ⊇ L3" was backwards (#844) — already removed in #855's framework redesign; audit confirms no remaining text states it.
docs/LEVELS.mdenshrines the corrected semantic
v2.9.2
Polish release: HTML report layout cleanup, XLSX matrix readability, and a -SaveBaseline UX papercut. One breaking change to a parameter type (see Changed).
Added
- ScoringViews section header (#835) — the scoring-tabs panel now has a proper
01c · Scoringeyebrow +Posture views by audienceh2 above it (was a "naked" tab strip with no section context). Big % number is color-coded by tier: ≥80 green, 60–79 amber, <60 red, using the theme-safe--success-text/--warn-text/--danger-textpalette across all 4 themes. New<section id="scoring">anchor enables sidebar deep-linking -BaselineLabelparameter onInvoke-M365Assessment(#809) — optional custom label paired with the new switch-form-SaveBaseline. Pre-existingauto-<timestamp>naming via-AutoBaselineis unchanged
Changed
-SaveBaselineis now a[switch](BREAKING, #809) —-SaveBaseline 'mylabel'no longer works; migrate to-SaveBaseline -BaselineLabel 'mylabel'. The bare-SaveBaselineform now auto-labels asmanual-<timestamp>. Why the breaking shape: PowerShell parameter binding does not allow a single non-switch parameter to accept BOTH the bare flag form AND a string-value form; the two-parameter shape is the only PowerShell-legal way to honor the bare--SaveBaselinerequest- Compliance Matrix XLSX
Horizoncolumn renamed toSequence(#840) — Pass-status rows now showDone(was empty) with green color-coding matching the Pass status cell. Source data unchanged so the Remediation Roadmap sheet still excludes Pass rows correctly - Permissions panel moved from top-of-report to Appendix (#834) — was wedged between the Domain rollup and findings table; now renders as a card alongside Tenant / MFA / CA in
Appendix · tenant. Theid="permissions"deep-link anchor is preserved - Sidebar Domains demoted from top-level group to collapsible sub-tree under Findings & Action (#836) — the per-domain entries are filter shortcuts into the findings table, not separate destinations, so they read more truthfully nested under FINDINGS & ACTION.
Domain posturelink under EXECUTIVE (separate destination) is preserved
Fixed
- FilterBar sticky pin no longer follows the user past the findings section (#838) —
.filter-bar-active'sposition: stickywas scoped to the App's main scroll container and stayed pinned through Roadmap, the Permissions card, and Tenant Appendix. Now gates the sticky class on the App's existing scrollspy signal (active === 'findings') so the bar releases when the user scrolls past the table. Reuses the IntersectionObserver already driving sidebar highlighting; no new event listeners
v2.9.1
Hotfix to v2.9.0. Caught by live-tenant validation post-tag — the underlying lesson is also addressed by a new .claude/rules/releases.md rule requiring live verification before any future tag/publish.
Fixed
- HTML report rendered as a blank black screen when
window.REPORT_DATA.permissions.sections.<X>.requiredcame back empty.ConvertTo-Jsonround-trips empty arrays asnulland single-element arrays as bare scalars; the newPermissionsPanelReact component (#812 / v2.9.0) called.join()on the field directly. Emptyrequired: []for sections likeEmail(which uses EXO, not Graph) becamenull, the call threw, and React unmounted the entire app tree. Fixed by routing all three array fields (required,missing, top-levelmissing) through anasArraycoercion helper inside the component - HTML report generation silently failed when
-AutoBaselinewas supplied. PowerShell's parser interpretedNew-Object -TypeName System.Collections.Generic.Dictionary[string, System.IO.DirectoryInfo](introduced in C1 #780 / v2.9.0) as an array literal — the comma between the two type parameters became the array operator, producingCannot convert 'System.Object[]' to the type 'System.String' required by parameter 'TypeName'at runtime. Switched to::new()which parses the type literal unambiguously. Added the previously-missingtests/Common/Get-BaselineTrend.Tests.ps1regression suite — the bug slipped through because the function had zero Pester coverage and the runtime parse only fails inside theBuild-ReportData->Build-SectionHtml->Get-BaselineTrendcall stack - Report-generation failures no longer get swallowed silently. The
catchblock inInvoke-M365Assessment.ps1now also callsWrite-Warningand points at the log file path, instead of writing only to the assessment log. A consultant running a 5-minute assessment shouldn't have to grep the log to discover the report didn't generate
v2.9.0
The Trust Hardening release. Surfaced by an external review on 2026-04-25; closed all 30+ child issues + the parent epic (#766) over Sprints 1-9.
Added
- Standardized evidence schema (D1 #785) -- 8 optional fields on
Add-SecuritySetting(ObservedValue,ExpectedValue,EvidenceSource,EvidenceTimestamp,CollectionMethod,PermissionRequired,Confidence,Limitations). Promotes the report appendix from a raw-JSON blob to a structuredEvidenceTableReact component. New "Evidence Details" sheet in the XLSX matrix. 5-collector proof-of-pattern migration. Newdocs/EVIDENCE-MODEL.mdfield reference + migration cookbook - Sanitized evidence package (D4 #788) -- new
-EvidencePackageand-Redactswitches onInvoke-M365Assessment. Bundles HTML + XLSX + structured findings + permissions summary + run metadata + SHA-256 manifest as a ZIP suitable for auditor handoff. Deterministic SHA-256-truncated hash tokens replace UPNs (<user-a3f81b29>), IPs, GUIDs, tenant display name -- same input always produces the same token across the package, preserving join keys for cross-finding correlation. Newdocs/EVIDENCE-PACKAGE.md - License-adjusted scoring views (D2 #786) -- 6-tab toggle on the executive-summary panel. Headline strict-rule Pass% in the score card stays invariant; tabs are exploration tools for different audiences (CISO / audit lead / account exec / MSP technician / sales engineer / auditor). New
docs/SCORING.mddocuments per-view denominators - Remediation export formats (D3 #787) -- new public
Export-M365Remediationcmdlet writes GitHub Issues markdown (one .md per finding + bulkcreate-issues.shhelper), executive-summary markdown, Jira CSV, and a technical-backlog markdown table. Reads existing assessment artifacts; does NOT re-run the assessment. Sample outputs indocs/samples/remediation-exports/ - Behavioral test suite (C5 #784) -- 29 Pester tests in
tests/Behavior/covering permission-map consistency, status normalization, check-ID uniqueness, report-math denominator, baseline drift, QuickScan filter, cloud env mapping, module compatibility downgrade, evidence schema regression guard, wrapper-deprecation guard - Linux/macOS cross-platform smoke lane (B6 #777) -- new advisory CI job. Catches platform-shaped bugs (path separators, Linux case-sensitivity, line endings) without paying the heavy install cost of the full Microsoft.Graph SDK on each runner
- Mocked Graph/EXO fixture infrastructure (D5 partial #789, #822) --
tests/Fixtures/{Graph,Exchange,Intune,Reports}/with JSON snapshots; per-collector fixture-driven test pattern. Infrastructure-only ship; per-collector migration deferred to future minor releases - HTML Permissions panel (#812 B2 followup) --
Test-GraphPermissionswrites_PermissionDeficits.json;Build-ReportDatasurfaces it; new ReactPermissionsPanelcomponent renders a per-section deficit table. Also populates the previously-stubpermissions-summary.jsonin evidence packages - Release-candidate workflow (#722) -- pushes to a
release-candidatebranch auto-tagvX.Y.Z-rc.N(auto-incrementing). Existingrelease.ymlmachinery handles them as GH pre-releases (PSGallery publish skipped for-tags) - Sovereign cloud support matrix (C2 #781) -- new
docs/SOVEREIGN-CLOUDS.md - React provenance + license attribution (C4 #783) -- new
docs/REPORT-FRONTEND.md;npm audit --audit-level=highadvisory CI step; committedTHIRD-PARTY-LICENSES.md - Data Handling deep dive (A4 #770, F2 #792) -- new
docs/DATA-HANDLING.md; pointers from README andSECURITY.md - REPORT_DATA schema reference (F5 #794) -- new
docs/REPORT-SCHEMA.md - Contributor guides (F6 #795, F7 #796) -- new
docs/TESTING.md+ newdocs/RELEASE-PROCESS.md - Baseline storage normalized to tenant GUID (C1 #780) -- baseline folders keyed by tenant GUID rather than mixed display name / domain prefix. Read-fallback preserves legacy folders during migration
- Trust-hardening label set in the GH issue vocabulary
Changed
- Wrapper API surface (C3 #782) -- 13 legacy
Get-M365*SecurityConfig/*RetentionConfigwrappers now emit a once-per-sessionWrite-Warningdeprecation notice naming v3.0.0 as the removal target and pointing to theInvoke-M365Assessment -Section <name>replacement. Wrappers stay exported in v2.9.x; full removal ships in v3.0.0 - CI quality gates (B5 #776) trigger on doc edits that gate version consistency (
README.md,CHANGELOG.md,docs/**, etc.) via a lighter docs-gates job - Connection profile path (B1 #772) -- moved out of module root to a per-user app-data path with read-fallback for legacy locations
- First-class data-state rendering (B8 #779) --
NotApplicable,NotLicensed,Skipped,Unknownget explicit treatment + counts in HTML report, XLSX matrix, executive summary, framework totals, remediation roadmap, drift comparison - App-only Graph permission validation (B2 #773) -- queries the running SP's app-role assignments against required roles per selected
-Section; emits per-section deficits in console + structured_PermissionDeficits.json - Status taxonomy (B3 #774) --
Add-SecuritySettingValidateSet now includesNotApplicableandNotLicensed - Generated permissions matrix (B7 #778) --
docs/PERMISSIONS.mdis now generated fromAssessmentMaps.ps1+PermissionDefinitions.ps1. CI step fails any PR that modifies the source maps without regenerating the doc
Fixed
- README "What's New" header was stale (A1 #767)
- Stale
.m365assess.jsonvalidation note: confirmed already in.gitignore(A3 #769; closed-on-arrival)
Notes
- PowerShell
[ValidateRange]rejects$nulleven on[Nullable[double]]params, so theConfidencefield's range check is implemented inside the helper body rather than via decorator - Redaction-order subtlety: email/UPN redaction runs before tenant-name redaction. Reversing the order would partially-redact
admin@contoso.comtoadmin@<tenant>.comand leave the address undetectable by later regexes - Pester 5 substitutes
<token>inIttitles, so test names use plain ASCII to avoid the implicit data-placeholder syntax - Two-stage wrapper deprecation (v2.9 warns + still exports, v3.0 removes) chosen over the locked plan's single-stage approach because removing a function and adding a warning to it are mutually exclusive
v2.6.0
Combined release covering the v2.5.0 (closed 2026-04-24) and v2.6.0 -- Finding Interaction (closed 2026-04-25) milestones.
Added
- Smart search in the findings table: pressing Enter cycles through matches (Shift+Enter reverses), an inline
N/Mcounter shows position, Esc clears. Active match auto-expands and scrolls into view; previously cycled-to row auto-collapses to keep the table tidy. Reuses the existingfocusFinding+highlight-focusinfrastructure (#697) - Every top-level section header in the report is now collapsible. New
useCollapsibleSectionhook applied to Posture trend, Framework coverage, Findings table, Roadmap, Stryker, and Appendix.beforeprintlistener auto-expands so PDF/print exports never lose collapsed content. PlainuseStateper the existing no-localStorage policy (#737) - New
Get-RemediationLanePowerShell helper inCommon/is the single source of truth for Now/Next/Later lane bucketing. Build-ReportData precomputeslaneonto each finding; HTML report and XLSX export both read it instead of duplicating bucketing rules (#715) - XLSX export gains a Horizon column on the Compliance Matrix sheet (color-coded Now=red, Next=amber, Later=blue; empty for Pass) and a new dedicated Remediation Roadmap sheet -- one row per actionable finding, grouped by Now/Next/Later, sorted within each group by severity then CheckId. Closes the parity gap with the HTML report's roadmap (#715)
- New
Effortcolumn on the Compliance Matrix sheet surfacing the registry-derived effort estimate (#715) - Framework coverage panel CTA shows the filtered count when one or more level chips are active:
View N of TOTAL findings matching L1 + E3 ->. Reuses the existingmatchProfileTokenhelper (#748) - Inline
L2 contains L3indicator chip on the CMMC chip row with hover tooltip clarifying that every L3 control is also assessed at L2 by design (verified by probe: zero L3-only-by-controlId checks exist in v2.22.1+ registry) (#744) - Framework cards in the Framework Quilt carry a rotating chevron affordance in their top-right corner plus keyboard accessibility (
role="button",aria-expanded, Enter/Space to toggle,:focus-visibleoutline) and a more visible hover state (#743) - FilterBar Level row appears for CIS M365 as well as CMMC, with L1 / L2 / E3 / E5 only chips. Chips write to the same
filters.profilefield used by the Framework Quilt panel chips, so selecting a level in either place lights up the other (#740) - CMMC complete posture view in the Framework Quilt -- the CMMC detail panel surfaces EZ-CMMC handoff gaps (out-of-scope / partial / coverable / inherent) alongside the existing L1/L2/L3 coverage stats.
REPORT_DATA.cmmcHandoffandREPORT_DATA.cmmcCoverageare part of the report data contract.sync-checkid.ymlpulls the handoff artifact on every scheduled sync (#594) - Clickable CMMC and CIS profile chips in the Framework Quilt expanded panel toggle each level's membership in
filters.profile(multi-select). The findings table filters to checks whosefwMeta[fw].profilesmatches at least one active token; the panel's Coverage by Domain bars re-compute against the same filter so the chart and findings stay consistent (#730, #731, #736)
Changed
- Posture trend section is now opt-in via
-IncludeTrendonInvoke-M365Assessment(threaded through Export-AssessmentReport -> Build-ReportData ->window.REPORT_DATA.trendOptIn). Baselines still auto-save for drift comparison; only the trend section visibility is gated. Default behaviour: no trend section unless explicitly requested (#750) - Remediation block in the findings detail panel splits PowerShell commands from portal navigation steps. New
.remediation-ps(code-style with violet "PowerShell" label) and.remediation-portal(prose with accent "Portal" label) blocks; segments preserve original order but never share a line (#687) - Roadmap card visual polish: lane-level color tint via
:has()selector (Now=danger, Next=warn, Later=accent), stronger solid divider between guidance and metadata blocks, severity tag adopts chip palette colors, Learn more block gets accent border (#686) - Remediation roadmap default lane distribution rebalanced: Warnings and Reviews land in Later by default unless their severity is critical; only Fail-status findings earn a spot in Now / Next. Observed effect on reference tenant (147 tasks): Now 18 / Next 52 (was 126) / Later 77 (was 3) (#709)
- Framework coverage panel is expanded by default on report load; first visible framework opens automatically so L1/L2 chips and Coverage by Domain bars are immediately visible (#735)
- FilterBar FRAMEWORK and DOMAIN dropdowns cluster at the left of their row instead of being pushed apart by a flex-grow rule (#741)
- 12px vertical spacing added between the Executive Summary tile row and the critical-findings banner below it (#742)
sync-checkid.ymldeclares its CheckID release channel explicitly. Subscribes to the stable channel only; defense-in-depth verify-channel step fails the workflow if a preview-channel payload is received. Aligns with the cross-repo channel model from CheckID v0.1- Removed the local
Get-CmmcLevelsFromControlIdoverride inBuild-ReportData.ps1now that CheckID v2.22.1 publishes identity-semanticframeworks.cmmc.profilesupstream. Downstream code consumes registry values directly with zero behavioural change - Dropped "EZ-CMMC" project name from the Handoff gaps panel heading and footnote text in the report. Heading reads
Handoff gaps; footnote endstracked separately.Counts and chip behaviour unchanged (#732) - CheckID registry synced to v3.0.0 (additive
frameworks.X.sourcefield on per-framework mappings; no breaking changes for M365-Assess; all 1106 checks preserved including local extensions)
Fixed
- Posture trend was silently filtering baselines by tenant GUID while
Invoke-M365Assessmentsaves baselines with the tenant domain as the folder-name suffix.Build-SectionHtml.ps1now prefers the tenant'sDefaultDomainfrom the tenant CSV over the log-derived short-form prefix or the GUID (#733) - CMMC Level 1 and Level 2 coverage counts were identical on every tenant because CheckID's
registry.jsonuniformly tagged every CMMC-mapped check withprofiles=[L1,L2]. Build-ReportData now derives profiles from thecontrolIdstring. Observed effect on reference tenant: L1 drops from 233 to 118; L2 and L3 unchanged (now also resolved upstream in CheckID v2.22.1)
v2.4.0
Added
- User-controlled text scale cycle in the topbar (A / A+ / A++) — scales finding-title and detail body text without touching chrome; preference persisted in localStorage (#689, #704)
- Expand-all / collapse-all button on the findings table header (#688)
- Explicit "skipped" grey segment on domain-card and framework-quilt bars, with matching muted label and hover tooltip explaining the color legend (#703)
- Print preview auto-expands the first visible framework in the Framework Coverage section via
beforeprintlistener, ensuring framework details always render in the PDF output (#694)
Changed
- EDIT MODE banner now renders above the topbar (was below) by reordering the Topbar fragment — the existing
position: sticky; top: 0CSS then pins it to the top of the main column (#693) - Left sidebar:
DOMAINSsection collapsed by default with a+toggle;DETAILSrenamed toFindings & actionwith an accent top-border for visual emphasis; all+expand indicators right-aligned consistently (#695, #702) - Filter bar restructured into three rows (search / status+severity / framework+domain) and adds
.filter-bar-activesticky treatment when search or any filter is active (#696) - Topbar icon-btn-group now right-aligns even when wrapping to a second row in narrow viewports (#700)
- Email authentication posture bars now render as discrete per-domain segments (green for pass, red for fail) instead of a single partial bar — 3 green + 1 red is more legible than a 75% filled bar (#699)
- Domain-card meta counts colored to match their bar segments (pass = success, warn = amber, fail = danger, review = accent, skipped = muted) — the row now doubles as a legend (#703)
Fixed
- Duplicate subsection headings removed from Domain Posture (Intune / SharePoint / AD-Hybrid / Email Auth panels each render their title exactly once; outer wrapper divs were repeating the internal panel label) (#701)
- Microsoft-managed / Customer-earned Secure Score split tiles hidden when
microsoftScore === 0— the computation uses an invalidactionType = 'ProviderGenerated'discriminator so the split was always broken; tiles will return once the classification logic is corrected (#698)
Documentation / Branding
- Remaining "Azure AD" literals in PS remediation strings and Setting labels replaced with "Microsoft Entra" across SharePoint B2B, CA device compliance, Entra join/joined devices, PIM role paths, and P1/P2 premium warnings (#667)