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