Issue search
Which component is affected?
Prowler CLI/SDK
Cloud Provider
Microsoft 365
Steps to Reproduce
- Register an M365 service principal with all required permissions except
AuditLog.Read.All
- Run a scan against the M365 provider (API or CLI)
- Observe
entra_users_mfa_capable reports every enabled user as FAIL
- The scan completes as
completed with 100% progress — no warnings visible
Expected behavior
A clear signal that the check could not run due to insufficient permissions — either a single descriptive FAIL or a scan-level warning.
Actual behavior
_get_user_registration_details() in entra_service.py catches the 403 ODataError, logs it, and returns an empty dict. Every user then gets is_mfa_capable=False by default. The check dutifully reports all users as "not MFA capable" — completely indistinguishable from genuine findings.
In our environment this showed all users as false-positive critical findings that masked the real number.
Root cause
# entra_service.py, _get_user_registration_details()
except Exception as error:
if error.__class__.__name__ == "ODataError" and \
error.__dict__.get("response_status_code", None) == 403:
logger.error(...) # logs it, but...
return registration_details # ...returns empty dict → all users default to is_mfa_capable=False
This is not limited to one method. At least 5-6 other M365 Entra service methods have the same pattern — catch exception, log, return empty default. Downstream checks cannot distinguish "API returned no data" from "API call was denied".
The existing _get_directory_sync_settings() method already solves this correctly by returning a (data, error_message) tuple, and entra_seamless_sso_disabled checks for directory_sync_error before consuming data. But this pattern is not applied consistently.
Scope of the problem
Service layer — 5-6 Entra methods silently swallow 403 errors:
_get_user_registration_details → affects entra_users_mfa_capable, entra_break_glass_account_fido2_security_key_registered
_get_authentication_method_configurations → affects entra_authentication_method_sms_voice_disabled
_get_oauth_apps → already returns None (check handles it), but error not tracked
_get_authorization_policy, _get_conditional_access_policies, _get_groups, _get_organization, _get_admin_consent_policy, _get_default_app_management_policy
Scan layer — no concept of "completed with warnings". A scan that couldn't run checks due to missing permissions shows the same green completed badge as a fully successful scan.
Suggested fix
We have a working implementation on a fork that:
- Adds generic error tracking to
M365Service base class (_api_errors dict, api_error_for(), _collect_api_error(), _is_permission_error())
- Updates all 11 Entra
_get_* methods to detect permission errors and record them via the base class
- Updates 3 affected checks to call
api_error_for() and emit a single descriptive FAIL instead of false positives
- Includes tests (342 passed)
Branch: fix/m365-silent-permission-errors
Happy to open a PR if the team agrees with the approach. We can also discuss the scan-level warnings separately.
Operating System
Alpine Linux (container: prowlercloud/prowler-api:5.22.0)
Prowler version
5.22.0
Python version
3.12
Pip version
N/A (poetry-managed)
Context
Tested live on an AKS cluster running the Prowler API. The SP had a wrong role ID in the IaC config (AppRoleAssignment.ReadWrite.All instead of AuditLog.Read.All), which went undetected because Prowler reported false positives instead of flagging the permission gap.
Issue search
Which component is affected?
Prowler CLI/SDK
Cloud Provider
Microsoft 365
Steps to Reproduce
AuditLog.Read.Allentra_users_mfa_capablereports every enabled user as FAILcompletedwith 100% progress — no warnings visibleExpected behavior
A clear signal that the check could not run due to insufficient permissions — either a single descriptive FAIL or a scan-level warning.
Actual behavior
_get_user_registration_details()inentra_service.pycatches the 403 ODataError, logs it, and returns an empty dict. Every user then getsis_mfa_capable=Falseby default. The check dutifully reports all users as "not MFA capable" — completely indistinguishable from genuine findings.In our environment this showed all users as false-positive critical findings that masked the real number.
Root cause
This is not limited to one method. At least 5-6 other M365 Entra service methods have the same pattern — catch exception, log, return empty default. Downstream checks cannot distinguish "API returned no data" from "API call was denied".
The existing
_get_directory_sync_settings()method already solves this correctly by returning a(data, error_message)tuple, andentra_seamless_sso_disabledchecks fordirectory_sync_errorbefore consuming data. But this pattern is not applied consistently.Scope of the problem
Service layer — 5-6 Entra methods silently swallow 403 errors:
_get_user_registration_details→ affectsentra_users_mfa_capable,entra_break_glass_account_fido2_security_key_registered_get_authentication_method_configurations→ affectsentra_authentication_method_sms_voice_disabled_get_oauth_apps→ already returnsNone(check handles it), but error not tracked_get_authorization_policy,_get_conditional_access_policies,_get_groups,_get_organization,_get_admin_consent_policy,_get_default_app_management_policyScan layer — no concept of "completed with warnings". A scan that couldn't run checks due to missing permissions shows the same green
completedbadge as a fully successful scan.Suggested fix
We have a working implementation on a fork that:
M365Servicebase class (_api_errorsdict,api_error_for(),_collect_api_error(),_is_permission_error())_get_*methods to detect permission errors and record them via the base classapi_error_for()and emit a single descriptive FAIL instead of false positivesBranch:
fix/m365-silent-permission-errorsHappy to open a PR if the team agrees with the approach. We can also discuss the scan-level warnings separately.
Operating System
Alpine Linux (container:
prowlercloud/prowler-api:5.22.0)Prowler version
5.22.0
Python version
3.12
Pip version
N/A (poetry-managed)
Context
Tested live on an AKS cluster running the Prowler API. The SP had a wrong role ID in the IaC config (
AppRoleAssignment.ReadWrite.Allinstead ofAuditLog.Read.All), which went undetected because Prowler reported false positives instead of flagging the permission gap.