Skip to content

fix(m365): entra_users_mfa_capable produces mass false positives when AuditLog.Read.All is missing #10893

@b-abderrahmane

Description

@b-abderrahmane

Issue search

  • I have searched the existing issues and this bug has not been reported yet

Which component is affected?

Prowler CLI/SDK

Cloud Provider

Microsoft 365

Steps to Reproduce

  1. Register an M365 service principal with all required permissions except AuditLog.Read.All
  2. Run a scan against the M365 provider (API or CLI)
  3. Observe entra_users_mfa_capable reports every enabled user as FAIL
  4. 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:

  1. Adds generic error tracking to M365Service base class (_api_errors dict, api_error_for(), _collect_api_error(), _is_permission_error())
  2. Updates all 11 Entra _get_* methods to detect permission errors and record them via the base class
  3. Updates 3 affected checks to call api_error_for() and emit a single descriptive FAIL instead of false positives
  4. 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.

Metadata

Metadata

Assignees

Labels

provider/m365Issues/PRs related with the M365 providerseverity/mediumResults in some unexpected or undesired behavior.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions