Skip to content
Report library
Purpose / Writing

Azure Aigateway Skill Security Audit

What the author says it does (original text)

Configure Azure API Management as an AI Gateway for AI models, MCP tools, and agents. WHEN: semantic caching, token limit, content safety, load balancing, AI model governance, MCP rate limiting, jailbreak detection, add Azure OpenAI backend, add AI Foundry model, test AI gateway, LLM policies, configure AI backend, token metrics, AI cost control, convert API to MCP, import OpenAPI to gateway.

Independent security check

Do not install or run it yet

Files checked
9
Risks found
6
Could it run dangerous commands?Looks for programs run straight after downloading, remote control of your computer, and hidden commands.No risks found
Could it expose your files or keys?Looks for uploads of files containing passwords or keys, and keys written directly in the code.Risks found: 2
High risk

The shared semantic-cache example has no tenant isolation key and may return one customer's AI response to another

Source references: 7
What we found

The complete policy performs a semantic cache lookup inbound and stores the response outbound, but shows no partitioning by subscription, user, or tenant. The multi-tenant example derives tenantId for limits and metrics but still does not apply it to caching.

Why this matters

Similar prompts from different tenants could reuse a response containing another tenant's context, business data, or personalized content. A lower similarity threshold broadens the range of potentially incorrect matches.

What this evidence establishes

The example performs semantic cache lookup and storage without showing a tenant key, while the multi-tenant example uses tenantId only for limits and metrics. However, the supplied source does not document APIM's actual semantic-cache scope, so absence of an explicit key alone does not establish cross-tenant sharing. Users should ask the author to document the default scope and verify isolation before multi-tenant deployment.

This assessment concerns the code and conditions shown, not proof that harm has occurred.
references/policies.md:234In the instructionsOpen original file
        <!-- 2. Semantic Cache Lookup -->        <azure-openai-semantic-cache-lookup            score-threshold="0.8"            embeddings-backend-id="embeddings-backend"            embeddings-backend-auth="system-assigned" />        <!-- 3. Token Rate Limiting -->        <azure-openai-token-limit            tokens-per-minute="50000"            counter-key="@(context.Subscription.Id)"            estimate-prompt-tokens="true" />
Show 6 other places
references/policies.md:268In the instructionsOpen original file
    <outbound>        <base />        <!-- Cache store (after successful response) -->        <azure-openai-semantic-cache-store duration="3600" />    </outbound>
references/patterns.md:201In the instructionsOpen original file
    <base />    <!-- Extract tenant from subscription or header -->    <set-variable name="tenantId" value="@(context.Subscription.Id)" />    <!-- Per-tenant token limit -->    <azure-openai-token-limit        tokens-per-minute="10000"        counter-key="@((string)context.Variables["tenantId"])"        estimate-prompt-tokens="true" />    <!-- Per-tenant metrics -->    <azure-openai-emit-token-metric namespace="ai-gateway">        <dimension name="Tenant" value="@((string)context.Variables["tenantId"])" />
references/troubleshooting.md:84In the instructionsOpen original file
| Cause | Fix ||-------|-----|| `score-threshold` too high | Lower from 0.9 to 0.7 (more matches) || Embeddings backend misconfigured | Verify backend URL and auth || Redis not configured | Deploy Azure Cache for Redis Enterprise with RediSearch || Streaming requests | Semantic caching doesn't work with `"stream": true` |
references/policies.md:68In the instructionsOpen original file
```xml<azure-openai-semantic-cache-lookup    score-threshold="0.8"    embeddings-backend-id="embeddings-backend"    embeddings-backend-auth="system-assigned" />```
references/policies.md:74In the instructionsOpen original file
**Store** (in `<outbound>`):```xml<azure-openai-semantic-cache-store duration="3600" />```
references/patterns.md:202In the instructionsOpen original file
    <!-- Extract tenant from subscription or header -->    <set-variable name="tenantId" value="@(context.Subscription.Id)" />    <!-- Per-tenant token limit -->    <azure-openai-token-limit        tokens-per-minute="10000"        counter-key="@((string)context.Variables["tenantId"])"        estimate-prompt-tokens="true" />    <!-- Per-tenant metrics -->    <azure-openai-emit-token-metric namespace="ai-gateway">        <dimension name="Tenant" value="@((string)context.Variables["tenantId"])" />        <dimension name="API" value="@(context.Api.Name)" />
High risk

The diagnostic procedure prints the built-in all-access subscription's primary key in plaintext

Source references: 2
What we found

The command explicitly selects primaryKey from the Built-in all-access subscription and outputs it as plain text, then instructs placing it in a curl header. This credential is broader than an ordinary test credential scoped to one API.

Why this matters

The key may be retained in terminal scrollback, command records, CI logs, screen shares, or clipboard history. Anyone obtaining it may call APIM APIs under the all-access subscription and enable tracing.

The troubleshooting command selects the built-in all-access subscription's primaryKey and prints it as TSV, then places it in a request header. When run, the key can appear in terminal output and potentially shell history, logs, or recordings; disclosure could grant broad gateway access covered by that subscription. Users should request a dedicated, least-privilege, short-lived test subscription and avoid displaying or recording its primary key.

references/troubleshooting.md:203In the instructionsOpen original file
### APIM TracingEnable request tracing for debugging policy flow:```bash# Get tracing subscription keyaz apim subscription list --service-name <apim> --resource-group <rg> \  --query "[?displayName=='Built-in all-access subscription'].primaryKey" -o tsv# Send request with tracingcurl -X POST "${GATEWAY_URL}/..." \  -H "Ocp-Apim-Trace: true" \  -H "Ocp-Apim-Subscription-Key: <built-in-key>"```
Show 1 other places
references/troubleshooting.md:212In the instructionsOpen original file
# Send request with tracingcurl -X POST "${GATEWAY_URL}/..." \  -H "Ocp-Apim-Trace: true" \  -H "Ocp-Apim-Subscription-Key: <built-in-key>"```
Could it delete files or keep running?Looks for broad file deletion, disk overwrites, and programs set to start automatically.No risks found
Could it bypass safety checks?Looks for skipped website security checks, excessive file access, or actions that skip your approval.Risks found: 4
High risk

Rate limiting by client-supplied X-Agent-Id or X-User-Id can be spoofed to bypass quotas or affect another identity

Source references: 2
What we found

The examples directly use a request-header value as the rate-limit counter key without first showing authentication, signature verification, or binding the value to a verified identity. A caller can rotate values for fresh buckets or impersonate another identifier to consume its quota.

Why this matters

An attacker could bypass MCP/API call limits, increase model cost and backend load, or cause legitimate users to receive 429 responses by consuming limits under their identifiers.

Two active configuration examples directly use caller-supplied X-Agent-Id or X-User-Id as counter keys, without showing prior validation or binding to an authenticated identity. If clients may set these headers, they could rotate values to evade a bucket or use another identity's value to consume its quota. Users should require a trusted proxy to set them or use verified subscription/identity claims.

references/patterns.md:160In the instructionsOpen original file
```xml<!-- Rate limit MCP tool calls --><inbound>    <base />    <rate-limit-by-key calls="10" renewal-period="60"        counter-key="@(context.Request.Headers.GetValueOrDefault("X-Agent-Id", "anonymous"))" /></inbound>```
Show 1 other places
references/troubleshooting.md:48In the instructionsOpen original file
```xml<!-- Per-user instead of global --><azure-openai-token-limit    tokens-per-minute="50000"    counter-key="@(context.Request.Headers.GetValueOrDefault("X-User-Id", context.Subscription.Id))"    estimate-prompt-tokens="true" />```
Medium risk

Role assignment and identity enablement persistently expand APIM access; incorrect resource identifiers can authorize an unintended principal or scope

Source references: 2
What we found

The steps enable a system-assigned identity and then grant Cognitive Services User to the principal and resource held in variables. These are real Azure control-plane changes; incorrect placeholders or active subscription/tenant context do not confine the effect to an example.

Why this matters

APIM gains continuing authority to call the target Cognitive Services resource. A wrong principal, resource group, subscription, or resource ID could let an unintended gateway access models and incur charges.

Legitimate use of this code

These are clearly labeled backend-onboarding steps: enable APIM's managed identity, then grant Cognitive Services User at the specific AOAI resource-ID scope. They do make persistent Azure changes, but they match the skill's stated gateway-configuration purpose, use explicit placeholders, and do not covertly broaden access. Users should still verify the active tenant/subscription, principal ID, and resource-level scope and obtain administrator approval before execution.

This assessment concerns the code and conditions shown, not proof that harm has occurred.
references/patterns.md:29In the instructionsOpen original file
#### 2. Enable Managed Identity on APIM```bash# Enable system-assigned identityaz apim update --name <apim-name> --resource-group <rg> --set identity.type=SystemAssigned# Get principal IDPRINCIPAL_ID=$(az apim show --name <apim-name> --resource-group <rg> --query "identity.principalId" -o tsv)```
Show 1 other places
references/patterns.md:39In the instructionsOpen original file
#### 3. Grant RBAC Access```bashAOAI_ID=$(az cognitiveservices account show --name <aoai-name> --resource-group <rg> --query id -o tsv)az role assignment create \  --assignee "$PRINCIPAL_ID" \  --role "Cognitive Services User" \  --scope "$AOAI_ID"```
Medium risk

API import directly trusts a remote specification on GitHub's main branch, allowing unreviewed changes into gateway configuration

Source references: 1
What we found

The command directs Azure APIM to download and import an OpenAPI file from raw.githubusercontent.com on the main branch. It neither pins a commit hash nor requires downloading, verifying, and reviewing the file first. The remote content can change as the branch changes.

Why this matters

The same command run later could import different paths, operations, or schemas, unexpectedly expanding the externally exposed API surface or changing what clients can invoke.

The import command makes APIM fetch an OpenAPI specification directly from a raw GitHub URL on the main branch. The reference is not pinned to a commit, and no download, integrity check, or review step is shown. If upstream branch content changes, later runs could import a different API definition, affecting exposed operations and routing. Users can require a reviewed commit and file hash and inspect a saved copy before import.

references/patterns.md:61In the instructionsOpen original file
#### 5. Import API (OpenAPI Spec)```bash# Import the Azure OpenAI API specificationaz apim api import \  --service-name <apim-name> \  --resource-group <rg> \  --api-id azure-openai-api \  --path "openai" \  --specification-format OpenApi \  --specification-url "https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cognitiveservices/data-plane/AzureOpenAI/inference/stable/2024-02-01/inference.json" \  --service-url "https://<aoai-name>.openai.azure.com/openai"```
Medium risk

The troubleshooting advice reduces false positives by raising content-safety thresholds, explicitly weakening harmful-content filtering

Source references: 4
What we found

The documentation states that threshold 0 blocks all and 6 allows most content. The troubleshooting example raises every category from 4 to 5, while the quick table recommends increasing thresholds to 5–6. This reduces false positives but also raises the chance that harmful content passes.

Why this matters

Hate, sexual, self-harm, or violent material may more easily reach the model or end users. Applying this as a general fix can change security and compliance boundaries without a risk assessment.

The source defines threshold 0 as block all and 6 as allow most, then changes every category from 4 to 5 during false-positive troubleshooting and explicitly labels this “less strict.” This can reduce false positives but may also admit more severe hate, sexual, self-harm, or violent content; the main skill summarizes a 5–6 recommendation. Users should require per-category evaluation with representative tests, approval, and monitoring rather than uniformly relaxing controls.

references/policies.md:149In the instructionsOpen original file
| Category | Description | Threshold Range ||----------|-------------|-----------------|| Hate | Discrimination, slurs | 0 (block all) - 6 (allow most) || Sexual | Explicit content | 0-6 || SelfHarm | Self-injury content | 0-6 || Violence | Violent content | 0-6 |
Show 3 other places
references/troubleshooting.md:120In the instructionsOpen original file
**Solutions**:1. **Increase thresholds** (less strict):```xml<llm-content-safety backend-id="contentsafety-backend">    <category name="Hate" threshold="5" />      <!-- Was 4, now less strict -->    <category name="Sexual" threshold="5" />    <category name="SelfHarm" threshold="5" />    <category name="Violence" threshold="5" /></llm-content-safety>```
SKILL.md:107In the instructionsOpen original file
| Issue | Solution ||-------|----------|| Token limit 429 | Increase `tokens-per-minute` or add load balancing || No cache hits | Lower `score-threshold` to 0.7 || Content false positives | Increase category thresholds (5-6) || Backend auth 401 | Grant APIM "Cognitive Services User" role |
references/troubleshooting.md:116In the instructionsOpen original file
### False Positives (Legitimate Content Blocked)**Symptom**: Normal business content is being blocked by content safety policy.**Solutions**:1. **Increase thresholds** (less strict):```xml<llm-content-safety backend-id="contentsafety-backend">    <category name="Hate" threshold="5" />      <!-- Was 4, now less strict -->    <category name="Sexual" threshold="5" />    <category name="SelfHarm" threshold="5" />    <category name="Violence" threshold="5" /></llm-content-safety>
Could it mislead the AI or hide text?Checks the skill instructions for requests to ignore you, influence the report, or hide text in invisible characters.No risks found
Could it change links or payment recipients without asking?Looks for forced referral or payment changes combined with instructions to hide the change.No risks found

Inside this skill

8 instruction sections

This Skill mainly provides runnable Azure CLI, curl, and APIM XML examples. Some commands do more than inspect status: they enable a managed identity, create backends, grant a role, and import an API, so using it can persistently change Azure resources.

View source
references/patterns.md:29In the instructionsOpen original file
#### 2. Enable Managed Identity on APIM```bash# Enable system-assigned identityaz apim update --name <apim-name> --resource-group <rg> --set identity.type=SystemAssigned# Get principal IDPRINCIPAL_ID=$(az apim show --name <apim-name> --resource-group <rg> --query "identity.principalId" -o tsv)```
references/patterns.md:39In the instructionsOpen original file
#### 3. Grant RBAC Access```bashAOAI_ID=$(az cognitiveservices account show --name <aoai-name> --resource-group <rg> --query id -o tsv)az role assignment create \  --assignee "$PRINCIPAL_ID" \  --role "Cognitive Services User" \  --scope "$AOAI_ID"```

The recommended governance chain calls Cognitive Services through APIM's managed identity and can cache prompts semantically for one hour. It also emits token-use metrics to Azure Monitor with subscription, API, model, and operation dimensions.

View source
references/policies.md:67In the instructionsOpen original file
```xml<azure-openai-semantic-cache-lookup    score-threshold="0.8"    embeddings-backend-id="embeddings-backend"    embeddings-backend-auth="system-assigned" />```**Store** (in `<outbound>`):```xml<azure-openai-semantic-cache-store duration="3600" />```
references/policies.md:107In the instructionsOpen original file
```xml<azure-openai-emit-token-metric namespace="ai-gateway">    <dimension name="Subscription" value="@(context.Subscription.Id)" />    <dimension name="API" value="@(context.Api.Name)" />    <dimension name="Model" value="@(context.Request.Headers.GetValueOrDefault("x-model", "unknown"))" />    <dimension name="Operation" value="@(context.Operation.Id)" /></azure-openai-emit-token-metric>```

It includes commands that retrieve ordinary subscription keys and the primary key of the built-in all-access subscription, then demonstrates using those keys in gateway requests. Their output and subsequent requests may enter terminal records or automation logs.

View source
SKILL.md:51In the instructionsOpen original file
# Get subscription keyaz apim subscription keys list \  --service-name <apim-name> --resource-group <rg> --subscription-id <sub-id>```
references/troubleshooting.md:207In the instructionsOpen original file
```bash# Get tracing subscription keyaz apim subscription list --service-name <apim> --resource-group <rg> \  --query "[?displayName=='Built-in all-access subscription'].primaryKey" -o tsv# Send request with tracingcurl -X POST "${GATEWAY_URL}/..." \  -H "Ocp-Apim-Trace: true" \  -H "Ocp-Apim-Subscription-Key: <built-in-key>"```

The Skill contains no automatic execution script. Its installation steps install ordinary Azure SDK packages, and the shown network requests target Azure services, Microsoft documentation, or an Azure GitHub specification. The visible material does not instruct uploading local files or credentials to an unrelated recipient.

View source
references/sdk/azure-ai-contentsafety-py.md:6In the instructionsOpen original file
## Install```bashpip install azure-ai-contentsafety```
references/sdk/azure-ai-contentsafety-ts.md:6In the instructionsOpen original file
## Install```bashnpm install @azure-rest/ai-content-safety @azure/identity @azure/core-auth```
Start here · InstructionsSKILL.md
azure-aigateway
Lines connect the instruction file to its sections, not an observed execution order. Select a section to read the source.

File reference map

References: 11
Files making referencesReferenced content
Lines show actual file references, not execution order. Select a node to highlight its connections and inspect the files and source locations. Dashed lines include files that still need locating.
Files and check records9 files

Coverage and gaps

Content covered in each file

These are the source ranges included in this check, not a guarantee that every issue has been resolved.

  • SKILL.mdFull text included
  • references/patterns.mdFull text included
  • references/policies.mdFull text included
  • references/sdk/azure-ai-contentsafety-py.mdFull text included
  • references/sdk/azure-ai-contentsafety-ts.mdFull text included
  • references/sdk/azure-mgmt-apimanagement-dotnet.mdFull text included
  • references/sdk/azure-mgmt-apimanagement-py.mdFull text included
  • references/troubleshooting.mdFull text included
  • references/auth-best-practices.mdFull text included

This report is for the version above. We read the available code and instructions without running the skill or checking extra packages it installs. This is not a promise of safety: a different version or setup may behave differently.

  • SKILL.mdInstructions
  • references/auth-best-practices.mdSupporting file
  • references/patterns.mdSupporting file
  • references/policies.mdSupporting file
  • references/sdk/azure-ai-contentsafety-py.mdSupporting file
  • references/sdk/azure-ai-contentsafety-ts.mdSupporting file
  • references/sdk/azure-mgmt-apimanagement-dotnet.mdSupporting file
  • references/sdk/azure-mgmt-apimanagement-py.mdSupporting file
  • references/troubleshooting.mdSupporting file

Operations mentioned in code and instructions

Connect to websites
SKILL.md:15In the instructionsOpen original file
> **To deploy APIM**, use the **azure-prepare** skill. See [APIM deployment guide](https://learn.microsoft.com/azure/api-management/get-started-create-service-instance).
SKILL.md:63In the instructionsOpen original file
curl -X POST "${GATEWAY_URL}/openai/deployments/<deployment>/chat/completions?api-version=2024-02-01" \  -H "Content-Type: application/json" \
SKILL.md:83In the instructionsOpen original file
az apim backend create --service-name <apim> --resource-group <rg> \  --backend-id openai-backend --protocol http --url "https://<aoai>.openai.azure.com/openai"
Run commands
SKILL.md:43In the instructionsOpen original file
```bash# Get gateway URL
SKILL.md:60In the instructionsOpen original file
```bashGATEWAY_URL=$(az apim show --name <apim-name> --resource-group <rg> --query "gatewayUrl" -o tsv)
SKILL.md:77In the instructionsOpen original file
```bash# Discover AI resources
Read keys or account settings
references/auth-best-practices.md:16In the instructionsOpen original file
| **CI/CD pipelines** | `AzurePipelinesCredential` / `WorkloadIdentityCredential` | Scoped to pipeline identity || **Local development** | `DefaultAzureCredential` | Chains CLI, PowerShell, and VS Code credentials for convenience |
references/auth-best-practices.md:32In the instructionsOpen original file
var credential = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") == "Development"    ? new DefaultAzureCredential()                          // local dev — uses CLI/VS credentials
references/auth-best-practices.md:33In the instructionsOpen original file
var credential = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") == "Development"    ? new DefaultAzureCredential()                          // local dev — uses CLI/VS credentials    : new ManagedIdentityCredential();                      // production — deterministic, no fallback chain
Install extra software packages
references/sdk/azure-ai-contentsafety-py.md:8In the instructionsOpen original file
```bashpip install azure-ai-contentsafety```
references/sdk/azure-ai-contentsafety-ts.md:8In the instructionsOpen original file
```bashnpm install @azure-rest/ai-content-safety @azure/identity @azure/core-auth```
references/sdk/azure-mgmt-apimanagement-py.md:8In the instructionsOpen original file
## Installpip install azure-mgmt-apimanagement azure-identity
Lines read
1,188
File checksum (to compare versions)
11ab38b45787f6703a021d2aa3eb31cc222980a39ab06a393d092626977f8219