> For the complete documentation index, see [llms.txt](https://docs.forestall.io/fsprotect/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.forestall.io/fsprotect/edges/azure/az_role_scoped_to.md).

# AZ\_ROLE\_SCOPED\_TO

## Summary

|                               |                                                                                                                        |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **FSProtect ACL Alias**       | AZ\_ROLE\_SCOPED\_TO                                                                                                   |
| **Entra ID (Azure AD) Alias** | Scope                                                                                                                  |
| **Affected Object Types**     | App Registrations, Service Principals                                                                                  |
| **Exploitation Certainty**    | Certain                                                                                                                |
| **Graph Permission / Role**   | Application Administrator, Cloud Application Administrator, or custom Entra ID directory roles with scoped assignments |

## Description

`AZ_ROLE_SCOPED_TO` represents the scoping relationship for Entra ID directory role assignments. Roles like **Application Administrator** and **Cloud Application Administrator** can be assigned either tenant-wide or scoped to specific objects:

* **Tenant-scoped** (`/`): Control over **all** app registrations and service principals in the tenant.
* **Object-scoped** (`/<objectId>`): Control over **only** the specific app registration(s) or service principal(s) designated in the scope.

This distinction is critical for attack path analysis, as tenant-scoped assignments provide significantly broader control than object-scoped ones.

## Identification

### PowerShell

#### Enumerate Scoped Role Assignments

```powershell
Import-Module Microsoft.Graph.Identity.DirectoryManagement

Connect-MgGraph -Scopes "RoleManagement.Read.Directory", "Directory.Read.All"

function Get-ScopedRoleAssignments {
    $allAssignments = [System.Collections.Generic.List[PSCustomObject]]::new()

    # Build a role definition lookup (Graph only allows expanding one property per query)
    $roleDefinitions = @{}
    Get-MgRoleManagementDirectoryRoleDefinition -All | ForEach-Object {
        $roleDefinitions[$_.Id] = $_.DisplayName
    }

    # Get all directory role assignments with principal expanded
    $roleAssignments = Get-MgRoleManagementDirectoryRoleAssignment -All -ExpandProperty "principal"

    foreach ($ra in $roleAssignments) {
        $scope = $ra.DirectoryScopeId
        $isScoped = $scope -ne "/"

        $principalName = $ra.Principal.AdditionalProperties.displayName
        $principalType = if ($ra.Principal.AdditionalProperties.'@odata.type') {
            $ra.Principal.AdditionalProperties.'@odata.type' -replace '^#microsoft\.graph\.', ''
        } else { 'Unknown' }

        $roleName = $roleDefinitions[$ra.RoleDefinitionId]

        $allAssignments.Add([PSCustomObject]@{
            RoleName       = $roleName
            RoleId         = $ra.RoleDefinitionId
            PrincipalName  = $principalName
            PrincipalId    = $ra.PrincipalId
            PrincipalType  = $principalType
            Scope          = $scope
            IsScoped       = $isScoped
            AssignmentId   = $ra.Id
        })
    }

    $allAssignments
}

$assignments = Get-ScopedRoleAssignments

# Show all scoped (non-tenant-wide) assignments
Write-Host "`n=== Object-Scoped Role Assignments ===" -ForegroundColor Yellow
$assignments | Where-Object { $_.IsScoped -eq $true } |
    Sort-Object RoleName, PrincipalName |
    Format-Table RoleName, PrincipalName, PrincipalType, Scope -AutoSize

# Show tenant-wide assignments for scopeable roles
$scopeableRoles = @("Application Administrator", "Cloud Application Administrator")
Write-Host "`n=== Tenant-Wide Assignments for Scopeable Roles ===" -ForegroundColor Yellow
$assignments | Where-Object { $_.IsScoped -eq $false -and $_.RoleName -in $scopeableRoles } |
    Sort-Object RoleName, PrincipalName |
    Format-Table RoleName, PrincipalName, PrincipalType -AutoSize

# Export all
$assignments | Export-Csv -Path ".\ScopedRoleAssignments.csv" -NoTypeInformation -Encoding UTF8
Write-Host "`nTotal assignments: $($assignments.Count)"
Write-Host "Object-scoped assignments: $(($assignments | Where-Object IsScoped).Count)"
Write-Host "Tenant-wide assignments: $(($assignments | Where-Object { -not $_.IsScoped }).Count)"
```

#### Resolve Scoped Target Objects

```powershell
# For each scoped assignment, resolve the target object
function Resolve-ScopedTarget {
    param([string]$DirectoryScopeId)

    if ($DirectoryScopeId -eq "/") {
        return [PSCustomObject]@{ Type = "Tenant"; Name = "Entire Tenant"; Id = "/" }
    }

    # Scoped assignments use format: /<objectId> or /administrativeUnits/<auId>
    $objectId = $DirectoryScopeId.TrimStart('/')

    # Try as Application
    try {
        $app = Get-MgApplication -ApplicationId $objectId -ErrorAction Stop
        return [PSCustomObject]@{ Type = "Application"; Name = $app.DisplayName; Id = $app.Id }
    } catch {}

    # Try as Service Principal
    try {
        $sp = Get-MgServicePrincipal -ServicePrincipalId $objectId -ErrorAction Stop
        return [PSCustomObject]@{ Type = "ServicePrincipal"; Name = $sp.DisplayName; Id = $sp.Id }
    } catch {}

    # Try as Administrative Unit
    if ($DirectoryScopeId -match "/administrativeUnits/(.+)") {
        try {
            $au = Get-MgDirectoryAdministrativeUnit -AdministrativeUnitId $Matches[1] -ErrorAction Stop
            return [PSCustomObject]@{ Type = "AdministrativeUnit"; Name = $au.DisplayName; Id = $au.Id }
        } catch {}
    }

    return [PSCustomObject]@{ Type = "Unknown"; Name = $DirectoryScopeId; Id = $DirectoryScopeId }
}

# Example: Resolve all scoped targets
$scopedAssignments = $assignments | Where-Object { $_.IsScoped }
foreach ($sa in $scopedAssignments) {
    $target = Resolve-ScopedTarget -DirectoryScopeId $sa.Scope
    Write-Host "$($sa.RoleName) | $($sa.PrincipalName) -> $($target.Type): $($target.Name)" -ForegroundColor Cyan
}
```

### Azure GUI

#### View Scoped Role Assignments

* Open **Microsoft Entra admin center** -> **Roles and administrators**.
* Select a role (e.g., **Application Administrator**).
* In the **Assignments** tab, observe the **Scope** column:
  * **Directory** = tenant-wide assignment.
  * A specific **object name** = scoped to that app registration or service principal.

## Exploitation

> **Related Attack Paths:**
>
> * [AZ\_APP\_ADMIN](https://docs.forestall.io/fsprotect/edges/azure/az_app_admin) - Application Administrator role; if tenant-scoped, applies to all apps. If object-scoped, only applies to specific apps.
> * [AZ\_CLOUD\_APP\_ADMIN](https://docs.forestall.io/fsprotect/edges/azure/az_cloud_app_admin) - Cloud Application Administrator role; same scoping behavior as Application Administrator.
> * [AZ\_ADD\_SECRET](https://docs.forestall.io/fsprotect/edges/azure/az_add_secret) - Principals with scoped Application/Cloud App Admin roles can add secrets to the scoped app.
> * [AZ\_ADD\_OWNER](https://docs.forestall.io/fsprotect/edges/azure/az_add_owner) - Scoped role holders can add owners to the target app/SP.

The abuse potential depends entirely on **whether the role is scoped to the tenant or to individual objects**:

| Scope                             | Impact                                                                                  | Example                                                                                                                               |
| --------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **Tenant-wide** (`/`)             | Full control over **all** app registrations and service principals in the tenant        | Application Administrator scoped to the directory can add credentials to any app, modify any SP, and escalate via high-privilege apps |
| **Object-scoped** (`/<objectId>`) | Control over **only** the specific app registration(s) or service principal(s) in scope | Application Administrator scoped to a single app can only add credentials/modify that one app                                         |

## Mitigation

This is a structural relationship (not directly abusable) indicating role assignment scope. Mitigation:

* **Prefer object-scoped over tenant-wide assignments** (least privilege principle).
* **Audit tenant-wide assignments** and narrow to specific objects where possible.
* **Regularly review and remove unnecessary assignments**.

## Detection

`AZ_ROLE_SCOPED_TO` is a structural relationship -- there is no direct abuse to detect for this edge itself. Detection should focus on the role assignment changes that create or modify scoped relationships:

* Go to **Microsoft Entra ID** -> **Audit logs**.
* Filter by activities:
  * **Add member to role**
  * **Add eligible member to role**
  * **Remove member from role**

## References

* <https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/custom-assign-graph>
* <https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#application-administrator>
* <https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#cloud-application-administrator>
* <https://learn.microsoft.com/en-us/graph/api/rbacapplication-post-roleassignments>
* <https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/custom-create>
