> 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>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.forestall.io/fsprotect/edges/azure/az_role_scoped_to.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
