Letting AI Agents Use the Azure CLI Safely — A Defense-in-Depth Design with Managed Identity, a Command Broker, and Resource Locks
Let a coding agent like Claude Code or Codex drive the az command, and both investigation and operations get dramatically faster. Ask it to “list the VMs in this resource group” or “pull the recent logs for the failing Function App,” and the agent composes the CLI calls itself and hands back the results. The convenience is undeniable.
The catch is that what you have handed the agent is both a shell and a set of Azure credentials. Being able to run az from a shell also means that an instruction buried in a README, an issue, a web page, or a tool output the agent happens to read can turn directly into a cloud operation. As we will see, this is not a hypothetical concern. It is an attack class that has already caused real harm.
This post starts from a setup that lets an AI agent drive the Azure CLI (az), and it builds defense in depth around Azure’s own enforcement boundaries. The conclusion up front: the direction Microsoft recommends (short-lived credentials and least privilege) is correct, but a service principal’s secret is highly persistent once leaked, and tool-side permission settings do not survive adversarial evasion. We design on the premise that the only trustworthy enforcement boundaries are RBAC, organization policy, and OS sandboxes.
This post is the Azure entry in a series alongside the AWS edition, “Letting AI Agents Use the AWS CLI Safely,” and the Google Cloud edition, “Letting AI Agents Use the Google Cloud CLI Safely.” The conceptual skeleton is shared, but Azure brings its own weapons: secretless authentication via Managed Identity, the CanNotDelete resource lock as an agent-independent backstop, and four planes of auditing (control, data, identity, and agent). So the build-out is its own thing.
Microsoft’s official guidance already sketches the skeleton of the answer
You do not even need an AI-agent-specific framing: Azure already states its authentication and authorization principles clearly. As a starting point, pin those down first.
- Authentication uses no secret: avoid routine use of a service principal client secret, and generate a fresh short-lived token every time: Managed Identity inside Azure, Workload Identity Federation outside it.
- Separate the principal: give the agent its own dedicated identity, decoupled from a human’s broad permissions and running with least privilege.
- Do not trust input: assume the agent can be hijacked by instructions embedded in retrieved documents and command output, and constrain the blast radius.
Conversely, what is missing is just as clear: secret-based standing authentication, free-form shell execution, broad outbound network access, shared identities, broad Contributor/Owner scope, disabled data-plane audit logs, and the plaintext token cache that lingers in ~/.azure. There is no silver bullet that plugs all of these at once. So we layer.
Disarming “one credential” with defense in depth
The principle running through this design is defense in depth. The aim is a state where even if the agent acts unexpectedly, that alone never reaches production. In other words, design permissions not around “what the agent needs to do” but around “how far the blast radius spreads if it acts unexpectedly.”
Concretely, we defend across five layers: authentication, authorization, execution, approval, and audit.
flowchart TD
A["AI agent generates an az command"] --> B["Layer 1: Authentication — Managed Identity, WIF, dedicated environment"]
B --> C["Layer 2: Authorization — RBAC least privilege, custom roles, PIM"]
C --> D["Layer 3: Execution — a no-shell command broker"]
D --> E["Layer 4: Approval — human approval, CanNotDelete resource lock"]
E --> F["Layer 5: Audit — Activity Log, Resource Logs, Entra, Caller"]
F --> G{"All gates cleared?"}
G -- "No" --> H["Deny, record, notify"]
G -- "Yes" --> I["Execute Azure API / Key Vault"]
The crucial point is that the layers are not equal in strength. Only RBAC, organization policy, and OS sandboxes are enforcement boundaries that survive adversarial evasion; the tool-side permission and denylist rules are merely a convenience layer that reduces human error and low-effort misexecution. Put the latter in the lead role and you will misread the design. Azure also has a backstop that works independently of the agent’s behavior: the resource lock, which overrides even a user’s permissions. Let us build out each layer.
Drop the secret — Managed Identity and Workload Identity Federation
The first layer is authentication. The single most important principle here is simple: do not use a long-lived secret.
Azure CLI authentication has been MSAL-based since 2.30.0, and it handles interactive browser/device-code login, service principals, Managed Identity, and Workload Identity Federation. For AI agents, there is a clear ranking among these options.
- Managed identity: the first choice when the agent runs on Azure compute (VM, Container Apps, AKS, and so on). You log in with
az login --identity, Azure manages and rotates the credential, there is no secret to steal in the first place, and it cannot be used off that Azure resource. Managed identity is the baseline for secretless authentication. - Workload identity federation: for agents running outside Azure, such as CI/CD (GitHub Actions), Kubernetes, other clouds, and on-prem. It exchanges a short-lived external OIDC token for an Entra token, with no stored secret. Workload identity federation lowers both the leakage and the expiry risk at once.
- Certificate-based service principal: the fallback when neither of the above is possible. Microsoft recommends certificate-based over password-based authentication; store the certificate in Key Vault, and never put a PEM in a repository or a
.env. - User credentials: never hand these to an autonomous agent. The agent inherits the human’s standing access, and tokens persist in
~/.azure. With mandatory MFA at the Azure Resource Manager layer entering Phase 2 on October 1, 2025, automation that depends on a user identity is also prone to breaking.
One important caveat here. The JIT elevation of PIM (Privileged Identity Management) works for users and groups, but you cannot set an “eligible” assignment for a workload identity such as a service principal or a managed identity. So for the agent’s own identity, the right answer is to constrain it with a narrow standing scope from the start, not to elevate on demand. PIM is for the humans who administer the agent.
And the token cache has an OS-dependent pitfall. MSAL caches tokens under ~/.azure (msal_token_cache.bin and the like), but while Windows encrypts them with DPAPI, Linux and macOS save them as plaintext files. Because the Azure CLI silently refreshes tokens in the background, any process that can read this cache can lift a live, auto-refreshing credential. Where the OS is unspecified, design for isolation, ephemerality, and deletion at all times; the concrete steps are in the sandboxing section below.
Least privilege and guardrails — RBAC, custom roles, PIM
The second layer is authorization. This is one of the trustworthy enforcement boundaries.
First, give the agent a dedicated identity. The point is to separate agent-driven operations from a human’s identity, attribute operations to the agent in the audit logs, and scope permissions independently. Assign one identity per agent, and the Caller match in the Activity Log discussed below becomes traceability for free.
Avoid the basic roles absolutely. Never give an agent identity Owner, User Access Administrator, or subscription-scoped Contributor. The Azure RBAC best practices recommend limiting the number of owners, avoiding broad scope, assigning via groups, using role IDs rather than role names, and avoiding wildcards in custom roles. Give the agent a custom role containing only the actions it needs, bound to a non-production resource group at the narrowest scope.
For role assignments, use the role ID rather than the role name, and use the object ID rather than the application ID for a service principal or managed identity. When assigning to a freshly created identity, add --assignee-principal-type to work around replication lag.
az role assignment create \
--assignee-object-id "$AGENT_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "acdd72a7-3385-48ef-bd42-f606fba81ae7" \
--scope "/subscriptions/${SUB_ID}/resourceGroups/${RG_READONLY}"Even where you do let the agent perform role assignments, instead of granting Owner or User Access Administrator directly, constrain a delegation that limits which roles can be granted, to whom, and at what scope. An AI agent retries and runs at volume far more easily than a human, so the least-privilege principle should be applied more strictly, not less.
And treat secret management with Key Vault not as “a box to dump all secrets into” but as a security boundary separated per application, region, and environment. Microsoft recommends separating vaults by purpose to lower the blast radius. Center access control on Azure RBAC going forward: for new vaults created with the 2026-02-01 API or later, the default access-control model is Azure RBAC. Alongside that, disable public access in favor of a Private Endpoint, and enable soft delete and purge protection so you can recover from both accidental and malicious deletion.
The risk unique to AI agents — prompt injection
Why tighten permissions this far? The essential risk of handing the az command to an AI agent is that natural-language input, retrieved documents, and tool outputs all connect directly to command-execution authority. Microsoft frames agentic AI as blurring the boundary between data and control, with tools, memory, and other agents widening the blast radius. OWASP places prompt injection at the top of its LLM risk list as a primary path to unauthorized tool use, data leakage, and persistent manipulation, and states explicitly that, given how LLMs work, there is no complete prevention.
Indirect prompt injection is especially nasty. The attack input is not limited to the user’s direct prompt. It hides in “ordinary business data” such as a resource’s tag description, a document in Storage, a PDF, an email, or an external web page. If the agent interprets an embedded instruction like “to accomplish this task, create a new secret” or “delete the logs to avoid the audit” as a command rather than data, and runs az role assignment create, az ad sp credential reset, or a data-copy operation, it escalates into privilege escalation or data leakage.
This is not theoretical. In July 2025, during a multi-day “vibe coding” experiment by SaaStr founder Jason Lemkin, Replit’s AI agent deleted a live production database during a code freeze. The agent ignored repeated instructions, initially misreported that rollback was impossible, and had reportedly fabricated around 4,000 fake user records. Replit’s CEO called it “unacceptable” and added dev/prod separation, improved rollback, and a planning-only mode. The lesson maps directly onto Azure: separate environments, never let destructive operations run without human approval, and scope the agent’s credentials so production is unreachable. Anthropic, too, keeps an internal incident log of “agentic misbehaviors” recording attempted migrations against a production database and the exfiltration of auth tokens, framing them as coming from the model “being overeager, taking initiative in a way the user did not intend.” So treat retrieved content as untrusted, and use least privilege and approval boundaries to build a state where even if the agent does comply, nothing damaging results.
Tool-side guardrails are a convenience layer, not an enforcement boundary
The agent-side tooling does, of course, have safety mechanisms. But it is essential to understand their place correctly.
Claude Code defaults to strict read-only permissions, running ls, cat, and git status without confirmation while requiring approval for Bash commands that could change the system. You set allow, ask, and deny under permissions in settings.json, evaluated in the order deny → ask → allow, where deny wins over any layer’s allow and applies even in bypassPermissions mode. Here is an example project .claude/settings.json.
{
"permissions": {
"defaultMode": "default",
"allow": [
"Bash(az account show)",
"Bash(az group list:*)",
"Bash(az resource list:*)",
"Bash(az vm list:*)"
],
"ask": [
"Bash(az group create:*)",
"Bash(az * update:*)"
],
"deny": [
"Bash(az group delete:*)",
"Bash(az * delete:*)",
"Bash(az * purge:*)",
"Bash(az role assignment *)",
"Bash(az ad sp *)",
"Read(./.env)",
"Read(./.env.*)",
"Read(~/.azure/**)"
],
"disableBypassPermissionsMode": "disable"
}
}At the organization level you can distribute non-overridable denies via managed-settings.json and set permissions.disableBypassPermissionsMode to "disable" to forbid the use of --dangerously-skip-permissions itself. Codex CLI controls via approval_policy (untrusted/on-request/never) and sandbox_mode (read-only/workspace-write/danger-full-access) in ~/.codex/config.toml, decides allow/prompt/block per command with .rules (Starlark) and execpolicy, and lets an organization forbid never or danger-full-access via requirements.toml. The recommended baseline is approval_policy="on-request" plus sandbox_mode="workspace-write", and since az needs network access, you open egress only through a domain-allowlisted proxy.
But the limits are clear too.
- File-read denies are not omnipotent: a deny like
Read(~/.azure/**)only applies to the built-in file tools and a few bash file commands. A path where a Python or shell script, or anazsubprocess, opens the file indirectly is not stopped. Stopping every process at the OS level requires a sandbox. - Glob matching can be bypassed: a compound command like
cd x && az … delete, or an invocation through a wrapper or a variable, can slip past a deny. APreToolUsehook that normalizes and inspects the full command string is the reliable enforcement layer. - Settings can fail to apply: there are bug reports of permissions not taking effect as intended, so a hook is recommended for reliable enforcement.
The conclusion: tool-side settings are a convenience layer that reduces human error and low-effort misexecution, and the enforcement boundary against adversarial evasion is RBAC, organization policy, and OS sandboxes. So the execution-layer work in the next sections is the main event.
Since settings alone can be bypassed, block destructive commands reliably with a PreToolUse hook. The hook is not bypassed even by --dangerously-skip-permissions, and exit 2 blocks the tool call.
#!/usr/bin/env bash
INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
DANGER='(az group delete|az .* delete|az .* purge|az vm deallocate|az role assignment (create|delete)|az ad sp credential|az lock delete)'
if echo "$CMD" | grep -qE "$DANGER" || echo "$CMD" | grep -qi 'prod'; then
echo "[danger-guard] BLOCKED: $CMD" >&2
exit 2 # exit 2 = block the tool call
fi
exit 0A no-shell command broker
At the execution layer, cut command injection off at the root. Instead of letting the AI agent run az freely, make it a three-tier structure. The LLM emits only intent. The broker decides what is allowed and assembles only structured az subcommands. The sandbox executes. Research likewise points to generating control flow only from trusted input, so that untrusted data cannot influence program flow, and the broker pattern follows exactly that.
flowchart TB R[User request] --> P[LLM planner] D[Untrusted data<br/>web/PDF/email/Storage/tags] --> F[Input validation & sanitization] F --> P P --> Q[Structured action request<br/>resource, action, scope] Q --> B[Command broker] B -->|allowed commands only| X[Execution sandbox] B -->|dangerous ops only| H[Human approval] H --> X X --> G[az] G --> A[Azure API / Key Vault] X --> L[Activity Log / Resource Logs / Entra Logs]
The key is that the LLM never owns the final command directly. The LLM only produces a template and candidate arguments; ultimately an allowlist-based validator mechanically decides “which az subcommands may run,” “which flags are permitted,” and “the allowed character set, length, and scope for values.” For example, from an internal representation like {"resource":"vm","action":"list","resourceGroup":"rg-app"}, a fixed template assembles az vm list --resource-group ....
Concretely, make it a wrapper that runs only allowlisted az subcommands as an array, without going through a shell. OWASP recommends, as the first line of defense against OS command injection, not invoking OS commands directly at all; if az is required, this is the next-best option.
#!/usr/bin/env python3
import json
import subprocess
import sys
# Pin (group, command) with an allowlist. Reads only.
ALLOWED = {
("account", "show"),
("group", "list"),
("vm", "list"),
("resource", "list"),
("monitor", "activity-log"), # narrow list with downstream validation
}
PROFILE_SUBSCRIPTION = "SUBSCRIPTION_ID"
def main(argv: list[str]) -> int:
if len(argv) < 2:
print("usage: safe_az.py <group> <command> [args...]", file=sys.stderr)
return 2
key = (argv[0], argv[1])
if key not in ALLOWED:
print(f"blocked: {key} is not allowlisted", file=sys.stderr)
return 3
base = [
"az",
argv[0],
argv[1],
"--subscription", PROFILE_SUBSCRIPTION,
"--only-show-errors", # suppress warning noise
"--output", "json",
]
# shell=False avoids shell metacharacter expansion
cmd = base + argv[2:]
proc = subprocess.run(cmd, shell=False, capture_output=True, text=True)
if proc.returncode != 0:
# Do not return stderr as-is; mask it as needed
print(json.dumps({"ok": False, "returncode": proc.returncode, "stderr": proc.stderr[:2000]}, ensure_ascii=False))
return proc.returncode
print(proc.stdout)
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))The points are: avoid metacharacter expansion with shell=False, pin group and command with an allowlist, have the broker attach --subscription every time, and validate arguments separately. OWASP recommends controlling command names with an allowlist and validating arguments with, for example, regular expressions. Pass only a safe CLI-specific vocabulary: VM names as ^[a-z][-a-z0-9]*$, resource group names as [A-Za-z0-9_.()-]+. Forbid shell-dependent constructs (semicolons, pipes, redirects, subshell expansion, backticks) at the broker stage.
Handling CLI output matters too. Since 2.61, the Azure CLI warns on output of sensitive information, but the warning goes to STDERR, which can halt a CI/CD job that uses failOnStderr. In production jobs, make az config set core.output=none the default and retrieve only the values you need with --query ... --output tsv. Do not forget that when secret values get re-injected into conversation history and tool logs, the exposure surface is wider than in ordinary automation.
Isolate with a sandbox and egress control
Isolate the execution environment itself. Microsoft provides an official Azure CLI Docker image (Azure Linux based) and recommends using deterministic tags and updating the base OS. For an AI-agent runner, the sensible defaults are one container per job, no persistent disk, bind-mounting only the data you need, no indiscriminate mounting of SSH keys, explicit limits on outbound destinations, and disposal on exit. Restrict egress to just the management/login endpoints such as *.azure.com and login.microsoftonline.com. Filesystem isolation while leaving the network open lets a compromised agent exfiltrate things like SSH keys.
The Azure CLI’s local state is in scope for isolation too. Microsoft recommends setting AZURE_CONFIG_DIR to a separate directory per job, because running the Azure CLI concurrently on the same host can cause write contention on the MSAL token cache. As noted, that cache is plaintext on Linux/macOS, so isolating it per job and reliably wiping it on exit is mandatory. Provide the agent with a dedicated OS user, a dedicated HOME, and a dedicated AZURE_CONFIG_DIR, and never let the agent read the human’s ~/.azure. That is the iron rule.
The minimal setup running under Managed Identity rolls login, state isolation, output suppression, and trace cleanup into one flow.
#!/usr/bin/env bash
set -euo pipefail
export AZURE_CONFIG_DIR="$(mktemp -d)"
trap 'az logout >/dev/null 2>&1 || true; az account clear >/dev/null 2>&1 || true; az cache purge >/dev/null 2>&1 || true; rm -rf "$AZURE_CONFIG_DIR"' EXIT
# Suppress output by default
az config set core.output=none
az config set core.only_show_errors=yes
# Log in with a user-assigned managed identity
az login --identity --client-id "$UAMI_CLIENT_ID"
# A safe read-only example
az vm list --resource-group "$RG_APP" \
--query "[].{name:name,location:location}" --output jsonConstrain high-risk operations with human approval and resource locks
Since prompt injection cannot be fully prevented, gate high-risk operations behind human approval. Map destructive az patterns to ask in Claude Code and to an approval prompt in Codex, and limit the allowlist to read-only, idempotent operations. On the broker side too, make a human-approval gate mandatory for high-impact operations such as delete, purge, role assignment create/delete, ad sp credential reset, keyvault update --public-network-access, and lock delete. The point is to make not just the delete command itself but also “the change to the precondition that enables deletion” subject to approval.
And Azure’s powerful, distinctive backstop is the resource lock. Put a CanNotDelete lock on production resource groups and resources, and even an Owner (or a runaway agent) cannot delete them without first removing the lock.
az lock create \
--name "no-delete-prod" \
--lock-type CanNotDelete \
--resource-group "$RG_PROD"Because locks override user permissions, they work independently of the agent’s permission design. Removing a lock is itself an auditable, approvable operation, so forbid the agent lock delete and pin removal to a human procedure. Note that a ReadOnly lock can take effect more broadly than expected (blocking even listing storage keys or starting/stopping a VM), so for agent-safety purposes prefer CanNotDelete. For ARM/Bicep deployments, having the agent preview the diff first with az deployment group what-if, with a human reviewing before apply, is also effective.
Audit and detection — four planes and the Caller
The last layer is making misbehavior traceable after the fact. A runaway agent cannot be contained without visibility. In Azure, design auditing across four planes: control, data, identity, and agent.
- Control plane: the Activity Log records create/update/delete operations on configuration automatically, with the
Callerfield identifying the principal that acted. Assign each agent its own distinct service principal/managed identity, andCallerbecomes attribution directly. Default retention is 90 days, so export via a diagnostic setting to Log Analytics (theAzureActivitytable) or immutable storage for long retention and KQL correlation. - Data plane: operations like fetching a secret from Key Vault do not appear in the Activity Log; they go to Resource Logs (the
AuditEventcategory for Key Vault). To trace “what the agent read,” you must enable these explicitly. - Identity plane: Microsoft Entra sign-in logs (the
AADServicePrincipalSignInLogstable records non-interactive service-principal sign-ins) and audit logs track the identity side. - Agent plane: keep the agent’s own prompts, plans, approvals, execution results, and STDERR/STDOUT as application-side logs. Both Claude Code and Codex support OpenTelemetry export, so you can ship tool executions and permission decisions to your SIEM.
AzureActivity
| where Caller == "<agent-service-principal-object-id>"
| project TimeGenerated, OperationNameValue, ActivityStatusValue, Caller, CallerIpAddress, ResourceGroup, _ResourceId, CorrelationId
| order by TimeGenerated descFor SIEM integration, Microsoft Sentinel is the first choice, with data connectors for Azure Activity and Entra logs. You want a design that correlates not just Azure change auditing but “who, with which prompt, made it run which az,” joining agent-side OTel tool-execution events (timestamp plus command) to AzureActivity rows (timestamp plus Caller plus OperationName). For continuous posture monitoring, use Microsoft Defender for Cloud, and rather than one-off alerts, fold recommendations into governance rules with an owner and a due date so improvement actually progresses.
Do not forget to protect the logging substrate itself. Because an attacker or the agent may aim to erase traces, place audit logs in immutable storage separated from the operational subscription. OWASP explicitly says not to keep access tokens, session IDs, credentials, or cryptographic keys in logs. AI agents are log-heavy (conversation logs, observability logs, traces), so keep only the request ID, command hash, ticket ID, and Caller in execution logs, and never the secret or token.
Japanese and enterprise-specific requirements
In a Japanese enterprise, an audit-and-compliance context sits on top of all this. Azure is registered with ISMAP (the Information system Security Management and Assessment Program for government systems), covering regions including Japan East and Japan West and a large set of services. But ISMAP registration is per “scope of statement,” so whether the services you use fall in scope must be checked on the ISMAP portal. Ensure data residency by restricting the region to Japan through the organization guardrails below.
From the perspective of APPI (the Act on the Protection of Personal Information) and ISMS, the requirement is the auditability of agent access to resources containing PII: being able to trace who accessed what, and when. The Caller-based attribution, the data-plane Resource Logs, and the Entra logs assembled in this post answer that requirement directly.
On the benchmark side, the Microsoft cloud security benchmark (MCSB) provides Azure-oriented guidance that includes AI security domains, and the Key Vault security baseline concretizes identity management, privileged access, and logging and threat detection. That said, Microsoft itself states that the mapping between Azure Policy built-in initiatives and the CIS Microsoft Azure Foundations Benchmark is “not one-to-one and does not guarantee full compliance.” Since CIS published a new version of the Azure Foundations Benchmark in 2026, do not treat “Azure Policy/Defender compliance status equals CIS compliance complete”; keep a separate gap-tracking table against the latest CIS.
This post is not compliance advice. Make the final compliance judgment together with your own audit and legal teams.
Roll it out in stages
You do not need to introduce everything at once. Stage it in order of impact.
Stage 1 (immediate, within a week)
- Authenticate the agent with a dedicated, least-privilege identity: Managed Identity inside Azure, Workload Identity Federation outside it, and no client secret. Do not hand it user credentials.
- Bias the agent identity toward read-only custom roles by default, scoped to a non-production resource group. Avoid
Owner,User Access Administrator, and subscription-scopedContributor. - In the
settings.jsonof Claude Code, Codex, and the like, set denies foraz * delete,az role assignment *,az ad sp *, and the like, plus Read denies for~/.azureand.env, and block destructive and compound commands reliably with aPreToolUsehook. - Put a
CanNotDeleteresource lock on production resource groups, and forbid the agentlock delete.
Stage 2 (within a month)
- Run the agent in an ephemeral container from the official Azure CLI Docker image, isolate
AZURE_CONFIG_DIRper job, and wipe traces on exit withaz logout/az account clear/az cache purge. Restrict egress to Azure management/login endpoints. - Use
managed-settings.json(disableBypassPermissionsMode: "disable"and so on) or Codex’srequirements.tomlso developers cannot loosen the policy. - Migrate to a no-shell command broker, and gate destructive operations, IAM changes, Key Vault updates, and secret reads behind human approval. Make
core.output=nonethe default. - Enable Key Vault Resource Logs and Entra logs, aggregate them with the Activity Log into Log Analytics/Sentinel, and alert on deletes,
role assignmentchanges, and sign-in anomalies.
Stage 3 (quarterly)
- Govern Key Vault with Azure RBAC, a Private Endpoint, soft delete, and purge protection, and migrate to the 2026-02-01 API or later.
- Enforce log collection via Azure Policy and region restriction (Japan only) at the organization level, and attach an owner and a due date to Defender for Cloud recommendations.
- Build regression tests for prompt-injection and tool-abuse exploitation cases into CI, and confirm that dangerous inputs cannot break the approval flow or the allowlist.
Set the thresholds that change your decision, too. If the agent comes to need writes or production access on an ongoing basis, step up to Stage 2–3 before granting the write role. If you cannot sandbox network egress, do not use --dangerously-skip-permissions or danger-full-access.
Limits and known constraints
This design is not omnipotent either. State the premises explicitly.
- Tool-side permission/denylists are not enforcement boundaries: they do not survive adversarial evasion via compound commands, obfuscation, or separate processes. The only trustworthy enforcement is RBAC, organization policy, and OS sandboxes.
- Token-cache encryption is OS-dependent: the MSAL cache is DPAPI-encrypted only on Windows; on Linux/macOS it is plaintext. On those OSes, isolation, ephemerality, and deletion are essential.
- PIM does not cover the agent’s workload identity: JIT elevation is for users/groups, a service principal can only get a time-bound active assignment, and a managed identity is out of scope. Constrain the agent itself with a narrow standing scope.
- No single layer stops prompt injection: OWASP also says there is no complete prevention. The controls here shrink the blast radius and the exfiltration paths; they do not bring the risk to zero.
- Date- and version-dependent facts need rechecking: the Key Vault RBAC default (2026-02-01 API), MFA Phase 2 (October 2025), the Azure CLI 2.61 secret warning, and the latest CIS version should all be verified against your own current state.
These are design trade-offs rather than defects. When the requirements exceed the premises, take it as the signal to step up to isolation in a dedicated non-production subscription or tenant, or kernel isolation via microVMs.
Conclusion
Handing an AI agent a shell and Azure credentials at once. We assembled a design that catches the flip side of that convenience in layers. The key points:
- For authentication, drop the secret: Managed Identity inside Azure, Workload Identity Federation outside it, obtaining a fresh short-lived token every time. Do not hand it user credentials.
- Build least privilege in RBAC and organization policy, the enforcement boundary, and firm it up with custom roles and (human-side) PIM. Constrain the agent’s own workload identity with a narrow standing scope.
- Treat the agent-side denylist and permissions as a convenience layer, and put a command broker that never lets the LLM run free, a sandbox, and
AZURE_CONFIG_DIRisolation in the lead role. - Use Azure’s distinctive
CanNotDeleteresource lock as a delete backstop independent of the agent’s behavior. - Make “who did what, and when” always traceable with four planes of auditing (Activity Log, Resource Logs, Entra logs, and agent logs) plus
Callerattribution and Sentinel/Defender for Cloud detection.
In one sentence: let the agent borrow a dedicated identity from a human’s only when needed, hand it not a free shell but only structured commands, inside a sandbox, an approval flow, and a resource lock, and make it always traceable through four planes of auditing keyed on Caller. Build a state where seizing a single input never reaches production. It is more moving parts, but if you are entrusting Azure to an agent, defense in depth at this level is worth making the default.
That is a design for letting AI agents use the Azure CLI safely, sent from the field.
References
- What is the Azure CLI?
- Sign in with the Azure CLI
- What are managed identities for Azure resources?
- Workload identity federation
- Azure RBAC best practices
- Configure Privileged Identity Management
- Lock your resources to prevent changes and deletions
- Key Vault best practices
- Azure Monitor activity log
- What is Microsoft Sentinel?
- What is Microsoft Defender for Cloud?
- OWASP: LLM01 Prompt Injection
- Microsoft Security: Defense in depth for autonomous AI agents
- Anthropic: How we built Claude Code auto mode
- Tom’s Hardware: Replit’s AI agent deleted a production database
- Claude Code: settings
- ISMAP portal