SCENARIO 15 AI Agent NHI OAuth2 Splunk

AI Agent Identity Governance

An internal AI assistant was deployed with a broad admin token stored as an environment variable — no audit trail, no permission scoping, no decommission path. Governed the agent as a non-human identity: Entra app registration with four scoped Graph API permissions, OAuth2 client credentials flow, JWT claim validation, Splunk audit pipeline logging every tool call, and a fully validated decommission sequence. Capstone of the NHI thread across SCN-06, SCN-09, and SCN-14.

Entra ID OAuth2 Client Credentials Microsoft Graph API Anthropic API Splunk HEC SOC 2 CC6.1/CC6.3/CC6.6/CC6.8

Business Problem

IDSentinel Solutions deployed an internal AI assistant to automate identity reporting tasks — user profile lookups, group membership queries, and access summary generation. The assistant was initially granted broad delegated permissions using an admin account token stored as an environment variable. There was no audit trail of what the agent called, no mechanism to scope its access to the minimum required, and no decommission procedure if the agent needed to be retired or compromised.

The security team flagged this as a governance gap: an autonomous agent with broad API access and no identity controls is indistinguishable from a compromised service account. SOC 2 CC6.1 requires logical access controls for all access to organization data — the existing deployment had none.

Risk

  • Overprivileged agent — admin-scoped token granted access to all Graph API resources, far beyond what the agent required to perform its function
  • No audit trail — API calls made by the agent were not distinguishable in logs from human admin activity; attribution to the agent was impossible
  • Static credential exposure — access token stored as environment variable, extractable from the host and rotated only manually on a best-effort basis
  • No decommission path — no process existed to revoke agent access on retirement, compromise, or ownership change
  • SOC 2 exposure — CC6.1, CC6.3, and CC6.8 controls could not be evidenced for AI-driven API access under the existing deployment model

Scope

FieldDetail
Agent identityEntra ID app registration — IDS-AIAgent-GraphReporter
Auth flowOAuth2 client credentials (application identity, no delegated user context)
API permissionsFour scoped Graph API application permissions with documented justification and explicit denial of all others
Agent implementationPython agent using Anthropic API for tool selection, governed bearer token for Graph API execution
Audit pipelineSplunk HEC log ingest + Entra service principal sign-in logs — agent API activity surfaced in dedicated dashboard
DecommissionSecret revocation, app registration disable, 401 confirmation, sign-in log evidence — full sequence validated in lab
Compliance targetSOC 2 Type II — CC6.1, CC6.3, CC6.6, CC6.8

Solution Design — 5 Phases

PH 1

App Registration and Permission Scoping

Register IDS-AIAgent-GraphReporter in Entra ID with four application permissions. Document justification for every permission granted and denial rationale for every permission not granted. Generate a client secret with 90-day expiration tracked in Bitwarden.

PH 2

OAuth2 Token Acquisition and JWT Validation

Implement client credentials token acquisition in Python. Decode the JWT and confirm appid, roles, and tid claims match expectations. Document the break/fix on the /.default scope omission.

PH 3

Agent Tool Implementation

Three tool functions using the governed bearer token. Anthropic API receives a natural language prompt, returns a structured tool selection, agent executes with the governed token. Token not held in memory beyond the function call.

PH 4

Splunk Audit Pipeline

Log every agent API call to Splunk HEC with timestamp, app_id, operation, Graph endpoint, HTTP status, and response time. Forward Entra service principal sign-in logs as a second audit source. Build "AI Agent API Activity" dashboard.

PH 5

Decommission Validation

Exercise the full decommission sequence: revoke client secret, confirm 401 on token acquisition, disable app registration, confirm final failed auth event in Entra sign-in log. Registration disabled rather than deleted to preserve audit trail.

Permission justification: User.Read.All (user profile attributes), Group.Read.All (group membership queries), IdentityRiskyUser.Read.All (risky user surface for reporting), AuditLog.Read.All (sign-in log anomaly summary). Permissions explicitly denied: Directory.ReadWrite.All, User.ReadWrite.All, RoleManagement.ReadWrite.Directory, Mail.*, Files.* — each documented with denial rationale at registration.

// diagrams/ai-agent-identity-flow.png — AI agent identity governance architecture expand
AI agent identity governance architecture — app registration through decommission

Implementation

Phase 1 — App Registration and Permission Scoping

  • 01

    Create the App Registration

    App registration IDS-AIAgent-GraphReporter created in Entra ID. Application permissions selected — not delegated — to reflect that no human user context exists during agent execution. Admin consent granted for four scoped permissions only. Register-AgentAppRegistration.ps1 automates registration, permission assignment, and admin consent grant via Microsoft.Graph PowerShell.

    // 01-app-registration-overview expand
    IDS-AIAgent-GraphReporter app registration overview
  • 02

    Configure API Permissions

    Four application permissions granted with admin consent, each documented with a business justification. Permissions panel screenshot captured as SOC 2 CC6.1 evidence confirming least-privilege configuration at time of deployment.

    // 02-api-permissions-granted expand
    API permissions — four scoped permissions with admin consent granted
  • 03

    Generate Client Secret

    Client secret generated with 90-day expiration. Recorded in Bitwarden with owner, creation date, expiration date, and scenario reference (SCN-15). Secret was never stored in code or environment variables.

    // 03-client-secret-created expand
    Client secret — 90-day expiration, stored in Bitwarden

Phase 2 — OAuth2 Token Acquisition and JWT Validation

  • 04

    Acquire Token and Validate JWT Claims

    agent_token.py acquires a token via the client credentials flow targeting https://graph.microsoft.com/.default. Break/fix: initial token acquisition returned 401 on Graph API calls because the /.default suffix was omitted from the resource URI. Corrected and re-acquired — 200 on all subsequent calls. JWT decoded to confirm appid, roles (four permissions only), and tid claims.

    // 07-token-401-breakfix expand
    401 on token acquisition — /.default scope omitted
    // 04-token-acquisition-response expand
    Token acquisition — 200, bearer token returned after scope fix
    // 05-jwt-decoded-claims expand
    JWT decoded — appid, roles (four permissions only), tid confirmed

Phase 3 — Agent Tool Implementation

  • 05

    Governed Tool Functions and LLM Tool Selection

    Three tool functions in agent_tools.py, each making a single targeted Graph API call via the governed bearer token: get_user_profile, get_group_members, list_risky_users. agent_runner.py passes a natural language prompt and tool definitions to the Anthropic API — model returns a structured tool selection, agent executes with the governed token. Token not held in memory beyond the function call; re-acquired on expiration via token cache.

    // 06-graph-api-response-scoped expand
    Graph API response via scoped bearer token
    // 08-agent-tool-selection expand
    LLM tool selection — Anthropic API returns structured tool call
    // 09-agent-graph-response expand
    Agent Graph API response — governed token, scoped result

Phase 4 — Splunk Audit Pipeline

  • 06

    HEC Ingest and AI Agent API Activity Dashboard

    Agent API calls logged to Splunk HEC — each event includes timestamp, app_id, operation (tool name), Graph endpoint, HTTP status, and response time. Entra service principal sign-in logs forwarded as a second audit source, showing the same app_id performing token acquisitions with correlated timestamps. Dashboard deployed with panels for calls per hour, operations by tool, error rate, and top endpoints.

    // 10-splunk-hec-ingest expand
    Splunk HEC ingest — agent events confirmed in index
    // 11-splunk-dashboard-activity expand
    AI Agent API Activity Dashboard — calls per hour, operations by tool, top endpoints
    // 12-entra-signin-log-agent expand
    Entra sign-in log — agent service principal token acquisitions with correlated app_id

Phase 5 — Decommission Validation

  • 07

    Full Decommission Sequence

    Invoke-AgentDecommission.ps1 executed to validate the complete decommission path. Four steps validated in sequence:

    • Step 1 — Revoke client secret: Deleted from Certificates & Secrets panel. Panel confirmed empty after deletion
    • Step 2 — Confirm 401: agent_token.py returned 401 invalid_client immediately. No cached token survived beyond expiration window
    • Step 3 — Disable app registration: Status set to Disabled — token issuance blocked. Registration retained (not deleted) to preserve audit trail
    • Step 4 — Confirm sign-in log: Final failed auth event confirmed in Entra service principal sign-in log with error code invalid_client and timestamp
    // 13-secret-revocation expand
    Client secret revoked — Certificates and Secrets panel empty
    // 14-post-revocation-401 expand
    401 invalid_client confirmed — no standing access after secret revocation
    // 15-entra-signin-failure-detail expand
    Entra sign-in log — final failed auth event, invalid_client error code
    // 16-app-registration-deactivated expand
    App registration disabled — audit trail preserved, token issuance blocked

Outcome

Agent registered as a named Entra principal with four scoped application permissions — admin-scoped token eliminated
JWT claim validation confirmed roles array contains only the four granted permissions — no scope creep
Three governed tool functions executing Graph API calls with scoped bearer token — no credentials in code
Every agent API call logged to Splunk HEC with full attribution — agent activity distinguishable from human admin activity
Decommission sequence validated end-to-end — 401 confirmed on revoked secret, final sign-in failure captured in Entra log
SOC 2 CC6.1, CC6.3, CC6.6, CC6.8 evidence produced — closes all four controls for AI-driven API access

Implementation Results

MetricValue
App registrations created1 (IDS-AIAgent-GraphReporter)
Application permissions granted4 (User.Read.All, Group.Read.All, IdentityRiskyUser.Read.All, AuditLog.Read.All)
Permissions explicitly deniedAll write, mail, files, and role management permissions
OAuth2 flowClient credentials — application identity, no delegated user context
Token lifetime3,600 seconds; re-acquired on expiration via token cache
Client secret rotation90-day expiration; tracked in Bitwarden
Agent tools implemented3 (get_user_profile, get_group_members, list_risky_users)
Splunk log sources2 (HEC agent events + Entra service principal sign-in logs)
Decommission steps validated4 (revoke secret, confirm 401, disable registration, confirm sign-in log)
Long-lived admin credentials used0

SOC 2 Compliance Mapping

ControlRequirementEvidence
CC6.1 — Logical Access ControlsAccess to org data requires logical access controlsApp registration with four scoped permissions; admin consent; no admin credential in execution path
CC6.3 — Access RemovalAccess removed when no longer requiredDecommission sequence: secret revocation, 401 confirmation, disabled registration, sign-in log final auth event
CC6.6 — Logical Access BoundariesLogical boundaries enforced at platform layerJWT decoded claims confirming roles scoped to four permissions; no delegated context; no user escalation path
CC6.8 — Unauthorized Access PreventionPrevent unauthorized access to org resources401 on revoked secret confirmed by Splunk HEC log and Entra sign-in log; no residual standing access

Non-Human Identity Notes

  • Application vs. delegated permissions for agentic workloads: An AI agent operating without a logged-in user must use application permissions, not delegated. Delegated permissions inherit the user's access context, which introduces privilege escalation risk if the agent is invoked by a high-privilege user. Application permissions enforce a fixed, documented scope regardless of who invokes the agent.
  • Decommission path is not optional: Static service accounts are often decommissioned when someone notices they are no longer used. AI agents can persist in an active state indefinitely without a defined retirement trigger. The decommission runbook and validated 401 confirmation are the control that closes this gap.

Files