Skip to content

Releases: Galvnyz/M365-Assess

v2.12.0

Choose a tag to compare

@github-actions github-actions released this 12 Jun 20:04
e6da22a

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 in Common/ that follows @odata.nextLink until 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.
  • -HeadlineFramework parameter (#963): sets which framework(s) headline the Executive Briefing (tab-completes from controls/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_DATA gains headlineFrameworks and assessedAt.
  • Generated registry statistics with CI drift gate — public check counts and framework coverage numbers are now generated from controls/registry.json by scripts/Build-RegistryStats.ps1 into marker blocks in README.md, controls/README.md, and docs/user/COMPLIANCE.md, plus a new fully-generated docs/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 -Check and 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-Service pins 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 Medium fallback are now rated in risk-severity.json, seeded from the upstream CheckID impactRating.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 in controls/README.md. -QuickScan selects ~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 scopecontrols/registry.json previously 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 new controls/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. The sync-checkid workflow applies the same partition on every upstream sync; out-of-scope content remains available in the upstream CheckID release. Import-ControlRegistry enforces the scope at load time as defense-in-depth and warns if an unpartitioned registry is detected.
  • Registry entries now expose severityRatedImport-ControlRegistry marks whether a check's riskSeverity came from an explicit rating in risk-severity.json ($true) or is the Medium fallback ($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): buildFrameworkData incremented count keys it never initialised for the four exotic statuses, producing NaN and a silent donut gap. Counting now routes through one summaryBucket() 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.json sections 4 (Intune) and 9 (Fabric / Power BI), cisa-scuba.json service MS.INTUNE, hipaa.json the ten Breach Notification + Privacy Rule sections added by the CheckID v3.4.0 sync (#912), and iso-27002.json groups 9/10 (ISO 27001 management-clause ids that leak in via the upstream 27001/27002 mapping conflation, #871 — labels mirror iso-27001.json and 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 docsdocs/user/COMPLIANCE.md cited 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 checks docs/user/COMPLIANCE.md and 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 from controls/sync-scope.json instead of a hardcoded copy.

v2.11.0

Choose a tag to compare

@Daren9m Daren9m released this 01 May 16:25
6aac1be

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 controlId values now ellipsis-truncate with the full value available on hover via title attribute, 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.md sets 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.md locks 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.md chooses 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 emits Status=Review with 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 stale remediation.portal.path and missing manual-only signal.
  • Quality Gates "Permissions matrix in sync" gate (#911)docs/PERMISSIONS.md was moved to docs/reference/PERMISSIONS.md in #906 but scripts/Build-PermissionsMatrix.ps1's default -OutputPath was not updated; CI's -Check step started failing on every PR. Updated default + regenerated the doc.

v2.10.1

Choose a tag to compare

@Daren9m Daren9m released this 30 Apr 03:02
576aa75

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_P2 service plan ID (eec0eb4f-6444-4f95-aba0-50c24d67f998), which all P2-bundling SKUs resolve to. Same pattern Get-TeamsSecurityConfig.ps1 already 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.ps1 read $spoSettings['legacyAuthProtocolsEnabled'] but Graph v1.0 sharepointSettings exposes the property as isLegacyAuthProtocolsEnabled (boolean properties on this resource use the is- prefix). Wrong key returned $null in 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 in tests/Behavior/Framework-Taxonomy.Tests.ps1 enforces 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

Choose a tag to compare

@Daren9m Daren9m released this 29 Apr 16:06
ccd3d45

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-FORWARD before generic EXO-). New docs/research/narrative-content-sources.md captures 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 to controls/narrative-overlay.json deferred to follow-up
  • ISO 27001 vs 27002 mapping audit (#858) — new docs/research/iso-27001-vs-27002-audit.md documents the upstream SCF conflation (1020 / 1020 identical mappings) and recommends the upstream-fix path. New iso-27002.json framework definition (was missing — registry tagged checks with iso-27002 but no framework JSON existed). New tests/Behavior/Iso-27001-27002-Mapping-Audit.Tests.ps1 with 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.md catalogs 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"). New tests/Behavior/Cis-M365-v6-Mapping-Audit.Tests.ps1 with skip-until-upstream regressions on both anomalies + informational stats

Fixed

  • SPO- / SHAREPOINT- prefix matching bug in whyItMatters() (#854) — the chain checked startsWith('SHAREPOINT-') but the registry uses SPO-. 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

Choose a tag to compare

@Daren9m Daren9m released this 29 Apr 03:57
3a868a6

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.json at the assessment root captures toolName / toolVersion / registryDataVersion / tenant info / sectionsRun + a outputArtifacts array with SHA-256 hashes of every other file in the folder. The XLSX Compliance Matrix sheet's Title row, the _Assessment-Summary_*.csv comment header, and the _PermissionDeficits.json payload 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
  • -BaselineLabel parameter — 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 Intune and 9: Microsoft Fabric to 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-scale localStorage key) unchanged

Fixed

  • Appendix Email-authentication card SPF/DKIM predicates (#860) — the appendix used r.SPF === 'Pass' and r.DKIMStatus === 'Pass' to count passing domains, but the data fields contain raw SPF records and OK/Not configured for DKIMStatus. The card always reported 0/N passing even when the top-of-report DNS panel showed correct counts. Aligned predicates with DnsAuthPanel
  • 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.md enshrines the corrected semantic

v2.9.2

Choose a tag to compare

@Daren9m Daren9m released this 27 Apr 19:28
0ca0d9a

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 · Scoring eyebrow + Posture views by audience h2 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-text palette across all 4 themes. New <section id="scoring"> anchor enables sidebar deep-linking
  • -BaselineLabel parameter on Invoke-M365Assessment (#809) — optional custom label paired with the new switch-form -SaveBaseline. Pre-existing auto-<timestamp> naming via -AutoBaseline is unchanged

Changed

  • -SaveBaseline is now a [switch] (BREAKING, #809)-SaveBaseline 'mylabel' no longer works; migrate to -SaveBaseline -BaselineLabel 'mylabel'. The bare -SaveBaseline form now auto-labels as manual-<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--SaveBaseline request
  • Compliance Matrix XLSX Horizon column renamed to Sequence (#840) — Pass-status rows now show Done (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. The id="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 posture link under EXECUTIVE (separate destination) is preserved

Fixed

  • FilterBar sticky pin no longer follows the user past the findings section (#838).filter-bar-active's position: sticky was 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

Choose a tag to compare

@Daren9m Daren9m released this 26 Apr 18:39
a353441

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>.required came back empty. ConvertTo-Json round-trips empty arrays as null and single-element arrays as bare scalars; the new PermissionsPanel React component (#812 / v2.9.0) called .join() on the field directly. Empty required: [] for sections like Email (which uses EXO, not Graph) became null, the call threw, and React unmounted the entire app tree. Fixed by routing all three array fields (required, missing, top-level missing) through an asArray coercion helper inside the component
  • HTML report generation silently failed when -AutoBaseline was supplied. PowerShell's parser interpreted New-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, producing Cannot 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-missing tests/Common/Get-BaselineTrend.Tests.ps1 regression suite — the bug slipped through because the function had zero Pester coverage and the runtime parse only fails inside the Build-ReportData -> Build-SectionHtml -> Get-BaselineTrend call stack
  • Report-generation failures no longer get swallowed silently. The catch block in Invoke-M365Assessment.ps1 now also calls Write-Warning and 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

Choose a tag to compare

@Daren9m Daren9m released this 26 Apr 17:28
ae1340f

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 structured EvidenceTable React component. New "Evidence Details" sheet in the XLSX matrix. 5-collector proof-of-pattern migration. New docs/EVIDENCE-MODEL.md field reference + migration cookbook
  • Sanitized evidence package (D4 #788) -- new -EvidencePackage and -Redact switches on Invoke-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. New docs/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.md documents per-view denominators
  • Remediation export formats (D3 #787) -- new public Export-M365Remediation cmdlet writes GitHub Issues markdown (one .md per finding + bulk create-issues.sh helper), executive-summary markdown, Jira CSV, and a technical-backlog markdown table. Reads existing assessment artifacts; does NOT re-run the assessment. Sample outputs in docs/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-GraphPermissions writes _PermissionDeficits.json; Build-ReportData surfaces it; new React PermissionsPanel component renders a per-section deficit table. Also populates the previously-stub permissions-summary.json in evidence packages
  • Release-candidate workflow (#722) -- pushes to a release-candidate branch auto-tag vX.Y.Z-rc.N (auto-incrementing). Existing release.yml machinery 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=high advisory CI step; committed THIRD-PARTY-LICENSES.md
  • Data Handling deep dive (A4 #770, F2 #792) -- new docs/DATA-HANDLING.md; pointers from README and SECURITY.md
  • REPORT_DATA schema reference (F5 #794) -- new docs/REPORT-SCHEMA.md
  • Contributor guides (F6 #795, F7 #796) -- new docs/TESTING.md + new docs/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 / *RetentionConfig wrappers now emit a once-per-session Write-Warning deprecation notice naming v3.0.0 as the removal target and pointing to the Invoke-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, Unknown get 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-SecuritySetting ValidateSet now includes NotApplicable and NotLicensed
  • Generated permissions matrix (B7 #778) -- docs/PERMISSIONS.md is now generated from AssessmentMaps.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.json validation note: confirmed already in .gitignore (A3 #769; closed-on-arrival)

Notes

  • PowerShell [ValidateRange] rejects $null even on [Nullable[double]] params, so the Confidence field'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.com to admin@<tenant>.com and leave the address undetectable by later regexes
  • Pester 5 substitutes <token> in It titles, 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

Choose a tag to compare

@Daren9m Daren9m released this 25 Apr 10:55
43face9

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/M counter 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 existing focusFinding + highlight-focus infrastructure (#697)
  • Every top-level section header in the report is now collapsible. New useCollapsibleSection hook applied to Posture trend, Framework coverage, Findings table, Roadmap, Stryker, and Appendix. beforeprint listener auto-expands so PDF/print exports never lose collapsed content. Plain useState per the existing no-localStorage policy (#737)
  • New Get-RemediationLane PowerShell helper in Common/ is the single source of truth for Now/Next/Later lane bucketing. Build-ReportData precomputes lane onto 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 Effort column 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 existing matchProfileToken helper (#748)
  • Inline L2 contains L3 indicator 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-visible outline) 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.profile field 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.cmmcHandoff and REPORT_DATA.cmmcCoverage are part of the report data contract. sync-checkid.yml pulls 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 whose fwMeta[fw].profiles matches 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 -IncludeTrend on Invoke-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.yml declares 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-CmmcLevelsFromControlId override in Build-ReportData.ps1 now that CheckID v2.22.1 publishes identity-semantic frameworks.cmmc.profiles upstream. 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 ends tracked separately. Counts and chip behaviour unchanged (#732)
  • CheckID registry synced to v3.0.0 (additive frameworks.X.source field 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-M365Assessment saves baselines with the tenant domain as the folder-name suffix. Build-SectionHtml.ps1 now prefers the tenant's DefaultDomain from 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.json uniformly tagged every CMMC-mapped check with profiles=[L1,L2]. Build-ReportData now derives profiles from the controlId string. 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

Choose a tag to compare

@Daren9m Daren9m released this 23 Apr 04:03
c55cea3

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 beforeprint listener, 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: 0 CSS then pins it to the top of the main column (#693)
  • Left sidebar: DOMAINS section collapsed by default with a + toggle; DETAILS renamed to Findings & action with 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-active sticky 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 invalid actionType = '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)