Steps to Reproduce
In an M365 tenant where Entra ID inbound provisioning (from any HR system) creates user accounts ahead of their start date and populates the standard Microsoft Graph employeeHireDate attribute, run:
prowler m365 --check entra_users_mfa_capable
Expected behavior
Users whose employeeHireDate is in the future should be skipped, in the same way the check already skips guests (user_type == "Guest") and disabled accounts (not account_enabled). Pre-provisioned future-start-date accounts have not been signed in to, MFA enrolment is part of day-1 onboarding, and a FAIL on these is operationally indistinguishable from a real "active user without MFA" finding in dashboards.
Actual Result with Screenshots or Logs
The check evaluates pre-provisioned future-start-date accounts and reports them as FAIL. The noise scales linearly with hiring cadence, and the only available mitigation today (per-user mute rule) has to be added pre-hire and removed post-hire, which is not sustainable in tenants with regular onboarding.
Suggested fix
employeeHireDate is a top-level documented property on the Microsoft Graph user resource (https://learn.microsoft.com/en-us/graph/api/resources/user), in the same group as employeeId, employeeType, employeeLeaveDateTime, and accountEnabled. Server-side filtering ($filter=employeeHireDate ne null) returns Request_UnsupportedQuery from Graph, so the check evaluates it client-side after _get_users enumeration. Two small additions:
1. prowler/providers/m365/services/entra/entra_service.py — surface the attribute
# in _get_users(), $select list:
select=[
"id", "displayName", "userType",
"accountEnabled", "onPremisesSyncEnabled",
"employeeHireDate", # +
],
...
# in the User(...) constructor:
users[user.id] = User(
...
employee_hire_date=getattr(user, "employee_hire_date", None), # +
)
2. User model
employee_hire_date: Optional[datetime] = None
3. prowler/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable.py — skip future hires
from datetime import datetime, timezone
...
for user in entra_client.users.values():
if user.user_type == "Guest" or not user.account_enabled:
continue
if user.employee_hire_date and user.employee_hire_date > datetime.now(timezone.utc):
continue # pre-provisioned, not yet onboarded — MFA enrolment happens on day-1
...
This matches the existing skip pattern for guests and disabled accounts (see PRs #10785, #11002, #8545 for precedent) and the check's docstring already documents the "scope to active members only" intent.
Happy to send a PR.
How did you install Prowler?
Docker (prowlercloud/prowler-api:5.27.0).
Prowler version
prowler 5.27.0 — also present on main (prowler/providers/m365/services/entra/entra_service.py _get_users + entra_users_mfa_capable/entra_users_mfa_capable.py:execute).
Steps to Reproduce
In an M365 tenant where Entra ID inbound provisioning (from any HR system) creates user accounts ahead of their start date and populates the standard Microsoft Graph
employeeHireDateattribute, run:Expected behavior
Users whose
employeeHireDateis in the future should be skipped, in the same way the check already skips guests (user_type == "Guest") and disabled accounts (not account_enabled). Pre-provisioned future-start-date accounts have not been signed in to, MFA enrolment is part of day-1 onboarding, and a FAIL on these is operationally indistinguishable from a real "active user without MFA" finding in dashboards.Actual Result with Screenshots or Logs
The check evaluates pre-provisioned future-start-date accounts and reports them as FAIL. The noise scales linearly with hiring cadence, and the only available mitigation today (per-user mute rule) has to be added pre-hire and removed post-hire, which is not sustainable in tenants with regular onboarding.
Suggested fix
employeeHireDateis a top-level documented property on the Microsoft Graphuserresource (https://learn.microsoft.com/en-us/graph/api/resources/user), in the same group asemployeeId,employeeType,employeeLeaveDateTime, andaccountEnabled. Server-side filtering ($filter=employeeHireDate ne null) returnsRequest_UnsupportedQueryfrom Graph, so the check evaluates it client-side after_get_usersenumeration. Two small additions:1.
prowler/providers/m365/services/entra/entra_service.py— surface the attribute2.
Usermodel3.
prowler/providers/m365/services/entra/entra_users_mfa_capable/entra_users_mfa_capable.py— skip future hiresThis matches the existing skip pattern for guests and disabled accounts (see PRs #10785, #11002, #8545 for precedent) and the check's docstring already documents the "scope to active members only" intent.
Happy to send a PR.
How did you install Prowler?
Docker (
prowlercloud/prowler-api:5.27.0).Prowler version
prowler5.27.0 — also present onmain(prowler/providers/m365/services/entra/entra_service.py_get_users+entra_users_mfa_capable/entra_users_mfa_capable.py:execute).