{
  "schema": "aaes/capability-pack/1",
  "provider": "entra",
  "version": "1.0.0",
  "description": "Microsoft Entra directory via Microsoft Graph v1.0: read users, groups, group memberships, activated directory roles and app registrations. Read-only identity pack; every write is excluded pending a dedicated review.",
  "notes": "GROUP_ID in the group-members endpoint path is a placeholder: the deployment must substitute the concrete group each capability is bound to before enabling the pack, and the pack must not be enabled with the literal in place — the review does not rely on Microsoft Graph rejecting a literal placeholder identifier as the control, and allowlisting graph.microsoft.com does not make an unsubstituted pack safe. The egress allowlist stays graph.microsoft.com, a real shared API host, not a placeholder.\n\nThis pack is deliberately distinct from packs/microsoft365.json, which owns mail, calendar and Teams: this pack registers only Entra directory reads, and no capability id or resource id overlaps. This pack registers only endpoints the official Microsoft Graph v1.0 documentation publishes: list users, list groups, list a group's direct members, list activated directory roles, and list applications (app registrations).\n\nTwo documented behaviours a reviewer must know: directoryRoles returns only roles activated in the tenant — a role that has never been activated is absent from the collection, so absence from the read is not evidence of absence of the role template (directoryRoleTemplates is not registered); and the v1.0 list-members API has a Microsoft-documented known issue in which service principals are not listed as group members, so the membership read is not a complete statement of service-principal membership.\n\nAll five capabilities are reads at the minimum R0 tier; users and group memberships register at the pii class because display names, mail addresses, phone numbers and member identities are personal data.\n\nDocumented exclusions, pending a dedicated identity-write review because membership- and privilege-changing writes are broad-impact: this pack does not register user creation (POST /users), user update or deletion (PATCH/DELETE /users/{id}), account enable/disable or password operations, group creation, update or deletion, group membership changes (POST/DELETE /groups/{id}/members/$ref and owners), directory role activation or role assignment, administrative-unit membership changes, and application or servicePrincipal creation, update, deletion, credential or permission-grant operations, and no write capability ships in this pack at all.\n\nUse an app registration whose Microsoft Graph permissions cover only what these reads need; the pack does not create one. Least-privileged application permissions per the dated permission tables in the referenced operation documentation: User.Read.All for list users, GroupMember.Read.All for list groups, GroupMember.ReadBasic.All (basic member information only) for list group members — the least-privileged permission the dated members permission table names — RoleManagement.Read.Directory for directory roles, and Application.Read.All for app registrations.\n\nThe broader Group.Read.All is not least-privileged for either group operation and is not recommended here. One documentation anomaly flagged for re-review: the dated list-groups page's permission table currently shows the newer, write-scoped Group-NestingSupport.ReadWrite.All entry in its least-privileged cell, with GroupMember.Read.All among the read permissions the table names for the operation; this pack consents the read-scoped permission. Application permissions are tenant-wide daemon-grade power consented by an admin, while delegated permissions act as one signed-in user within that user's own access — the choice is the deployment's.\n\nDocumentation review is complete as recorded in review.state, with dated operation, permission, and token-lifecycle sources in review.sources. Library review does not establish tenant connectivity or executor operation; those remain deployment checks.",
  "credential": {
    "secret_ref_id": "secret-ref-entra-graph-read-token",
    "custody": "customer_vault",
    "inject": "header",
    "inject_name": "Authorization",
    "note": "The value stored under this reference is the complete Authorization header value (scheme included): the scheme 'Bearer', one separating space, then an OAuth2 access token for Microsoft Graph — 'Bearer <token>'. For the client-credentials flow the token is minted by an external token process from the client's own app registration against login.microsoftonline.com — POST /{tenant}/oauth2/v2.0/token with scope https://graph.microsoft.com/.default — and placed in the client's own secret manager by that external process; this grant issues no refresh token, so there is no refresh path to review, and renewal ownership sits with the external process, which obtains a replacement access token before the current one expires (access tokens last about an hour) and stores the new complete header value. A delegated user-consent token is equally valid for these reads and acts as the signed-in user; the deployment chooses the flow, and the executor injects the stored value verbatim and performs no token lifecycle itself.\n\nThe stored value is never the client secret or certificate assertion material — those stay with the external token process, and a certificate credential is preferable to a client secret for the app registration. Distinguish the three revocation effects: rolling or removing the app registration's credentials shuts down future token issuance; removing the app's granted permissions or revoking the user's grant removes authorization for new tokens; neither invalidates an already-issued access token — Microsoft documents access tokens as valid until their own expiry (a default lifetime varying between 60 and 90 minutes, 75 on average), so plan any emergency revocation around that remaining-validity window.\n\nAn expired value fails closed.\n\nLeast privilege: with application permissions, consent only the read set named in the pack notes (User.Read.All, GroupMember.Read.All for groups, GroupMember.ReadBasic.All for group members, RoleManagement.Read.Directory, Application.Read.All) and no write or Directory.ReadWrite.* scope. customer_vault is the only generally-supported production custody model here — identity_federation and customer_broker are refused at startup and inline is lab-only.\n\nExecutor hygiene: the value must live only in executor memory for the lifetime of a call, must never be written to logs, traces or diagnostics, and must not appear in crash dumps; a deployment that cannot meet that bar must not enable the pack."
  },
  "capabilities": [
    {
      "capability_id": "entra.users.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "pii",
      "egress_allowlist": [
        "graph.microsoft.com"
      ],
      "endpoint": "https://graph.microsoft.com/v1.0/users",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-entra-graph-read-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "entra-directory-users",
      "description": "List the tenant's users: display names, userPrincipalNames, mail addresses, phone numbers and job titles are personal data, so the read is classified as pii (personal data).\n\nThe registered path covers the collection read only — single-user reads (GET /users/{id}) are not registered by this pack. Microsoft documents replication delay on recently changed users, so the collection can lag the directory.",
      "requires_review": true
    },
    {
      "capability_id": "entra.groups.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "graph.microsoft.com"
      ],
      "endpoint": "https://graph.microsoft.com/v1.0/groups",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-entra-graph-read-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "entra-directory-groups",
      "description": "List the tenant's groups (excluding dynamic distribution groups, which Microsoft documents as not returned by this API): names, descriptions and mail nicknames, which reveal org structure.\n\nThe registered path covers the collection read only — single-group reads (GET /groups/{id}) are not registered by this pack.",
      "requires_review": true
    },
    {
      "capability_id": "entra.groups.members.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "pii",
      "egress_allowlist": [
        "graph.microsoft.com"
      ],
      "endpoint": "https://graph.microsoft.com/v1.0/groups/GROUP_ID/members",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-entra-graph-read-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "entra-directory-users",
      "description": "List the direct members of one group; member identities (users, contacts, devices and groups) are personal data when the members are people. The operation is not transitive, and Microsoft documents a known issue in v1.0 where service principals are not listed as members, so this read does not establish the absence of service-principal membership.\n\nGROUP_ID is a placeholder the deployment binds to a concrete group before enabling.",
      "requires_review": true
    },
    {
      "capability_id": "entra.directoryroles.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "graph.microsoft.com"
      ],
      "endpoint": "https://graph.microsoft.com/v1.0/directoryRoles",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-entra-graph-read-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "entra-directory-roles",
      "description": "List the directory roles activated in the tenant (names and descriptions, which reveal the admin model). Microsoft documents that this collection contains only activated roles and recommends the unified RBAC API (roleDefinitions) for fuller coverage; roleTemplates, roleDefinitions and role assignments are not registered by this pack.\n\nThe registered path covers the collection read only.",
      "requires_review": true
    },
    {
      "capability_id": "entra.applications.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "graph.microsoft.com"
      ],
      "endpoint": "https://graph.microsoft.com/v1.0/applications",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-entra-graph-read-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "entra-app-registrations",
      "description": "List the tenant's app registrations: app ids, display names, identifier URIs, publisher domains and sign-in audiences, which reveal the tenant's integration estate.\n\nThe registered path covers the collection read only — single-application reads (GET /applications/{id}) and the servicePrincipals collection are not registered by this pack. Microsoft documents that keyCredential public keys are not returned by default on list operations.",
      "requires_review": true
    }
  ],
  "resources": [
    {
      "resource_id": "entra-directory-users",
      "kind": "api",
      "locator": "https://graph.microsoft.com/v1.0/users",
      "data_class": "pii",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    },
    {
      "resource_id": "entra-directory-groups",
      "kind": "api",
      "locator": "https://graph.microsoft.com/v1.0/groups",
      "data_class": "internal",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    },
    {
      "resource_id": "entra-directory-roles",
      "kind": "api",
      "locator": "https://graph.microsoft.com/v1.0/directoryRoles",
      "data_class": "internal",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    },
    {
      "resource_id": "entra-app-registrations",
      "kind": "api",
      "locator": "https://graph.microsoft.com/v1.0/applications",
      "data_class": "internal",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    }
  ],
  "review": {
    "state": "library",
    "date": "2026-09-22",
    "sources": [
      "https://learn.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0 (retrieved 2026-09-21)",
      "https://learn.microsoft.com/en-us/graph/api/group-list?view=graph-rest-1.0 (retrieved 2026-09-21)",
      "https://learn.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0 (retrieved 2026-09-21)",
      "https://learn.microsoft.com/en-us/graph/api/directoryrole-list?view=graph-rest-1.0 (retrieved 2026-09-21)",
      "https://learn.microsoft.com/en-us/graph/api/application-list?view=graph-rest-1.0 (retrieved 2026-09-21)",
      "https://learn.microsoft.com/en-us/graph/permissions-reference (retrieved 2026-09-21)",
      "https://learn.microsoft.com/en-us/entra/identity-platform/access-tokens (retrieved 2026-09-21)",
      "https://learn.microsoft.com/en-us/entra/identity/users/users-revoke-access (retrieved 2026-09-21)"
    ],
    "reviewer_id": "aaes-eng"
  }
}
