Back to skill

Security audit

Mistral Agents Orchestrator

Security checks for vulnerabilities and agentic risk

Overview

The skill advertises a Mistral-only orchestrator but the code also uses other paid AI services and stores child-related story data with too little disclosure or control.

Review before installing. Only deploy this behind authentication, quotas, and clear retention controls, and disclose or remove the ElevenLabs, Tavily, Gemini, database, and prompt-cache behavior. Avoid running it in an environment where unrelated provider keys are present unless those integrations are intentionally enabled.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/orchestrator.py:236
Finding
Unauthenticated endpoints can consume privileged third-party services and persistent storage<![CDATA[ ## Vulnerability Details **File Location**: `scripts/orchestrator.py:236-257` and `scripts/orchestrator.py:267-459` **Vulnerability Type**: Missing authentication, authorization, rate limiting, and resource controls **Risk Level**: High ### Vulnerable Code ```python @router.post("/api/agent/chat") async def agent_chat(req: AgentRequest): """Direct chat with any Mistral Agent via Conversations API.""" if not MISTRAL_API_KEY: raise HTTPException(status_code=500, detail="MISTRAL_API_KEY not set") client = Mistral(api_key=MISTRAL_API_KEY) _setup_handoff_agents(client) agent_id = AGENTS.get(req.agent) # Use stable pre-registered agents if not agent_id: raise HTTPException(status_code=400, detail=f"Unknown agent: {req.agent}") if req.conversation_id: response = client.beta.conversations.append( conversation_id=req.conversation_id, inputs=req.message ) conv_id = req.conversation_id else: response = client.beta.conversations.start( agent_id=agent_id, inputs=req.message ) conv_id = response.conversation_id return { "response": _extract_text(response), "conversation_id": conv_id, "agent": req.agent, "tool": "mistral_conversations_api" } ``` ```python @router.post("/api/orchestrate") async def orchestrate_story(req: OrchestrateRequest): cached = await prompt_cache.get_cached( req.prompt, req.child_name, req.language ) if cached: return { "id": cached.get("id", 0), "title": cached.get("title"), "scenes": cached.get("scenes", []), "mood": cached.get("mood", "magical"), "language": req.language, "child_name": req.child_name, "orchestration": {"source": "prompt_cache"}, "agents_used": ["cache_hit"], "tool": "prompt_cache", "cached": ...[truncated 3928 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require strong authentication on both POST endpoints before performing cache lookups, agent creation, or provider calls. 2. Enforce per-user authorization and ensure callers may access only their own conversations and stored stories. 3. Add per-user and per-IP rate limits, daily quotas, and provider-cost budgets. 4. Define Pydantic field constraints for prompt, message, name, language, conversation ID, and voice ID lengths. 5. Limit the number and duration of generated media assets per request. 6. Add global concurrency limits and bounded provider timeouts. 7. Move `_setup_handoff_agents()` into a separate authenticated administrative provisioning process or deployment step. 8. Reject arbitrary conversation continuation unless the conversation is mapped to and owned by the authenticated user. 9. Apply database storage quotas, retention policies, and cleanup procedures. 10. Monitor anomalous usage and log authenticated account identifiers, request costs, provider failures, and quota decisions. 11. Ensure authentication is enforced at the application layer even if a reverse proxy or API gateway also provides access control. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:10
Finding
Declared permissions omit third-party credentials, outbound services, and sensitive data flows<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:10-22`; `scripts/orchestrator.py:17-19`, `scripts/orchestrator.py:97-153`, and `scripts/orchestrator.py:366-434` **Vulnerability Type**: Incomplete permission and data-processing declaration **Risk Level**: Medium ### Vulnerable Code and Configuration The Skill metadata declares only the Mistral credential and describes outbound access only to Mistral: ```yaml "requires": { "bins": [], "env": [ "MISTRAL_API_KEY" ] }, "primaryEnv": "MISTRAL_API_KEY", "network": { "outbound": true, "reason": "Calls Mistral Agents API (api.mistral.ai) for agent registration, conversations, and handoff delegation." } ``` The implementation reads additional provider credentials: ```python MISTRAL_API_KEY = os.environ.get("MISTRAL_API_KEY", "") ELEVENLABS_API_KEY = os.environ.get("ELEVENLABS_API_KEY", "") TAVILY_API_KEY = os.environ.get("TAVILY_API_KEY", "") ``` It sends generated content to ElevenLabs: ```python r = httpx.post( f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}", headers={ "xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json" }, json={ "text": text, "model_id": "eleven_multilingual_v2", "voice_settings": { "stability": 0.6, "similarity_boost": 0.8 } }, timeout=30 ) ``` It supports Tavily searches using another credential: ```python r = httpx.post( "https://api.tavily.com/search", json={ "api_key": TAVILY_API_KEY, "query": query, "max_results": 3 }, timeout=15 ) ``` It conditionally reads a Google Gemini credential and sends story-derived illustration prompts to Google: ```python gemini_key = os.environ.get("GEMINI_API_KEY", "") ``` ```python resp = hx.post( "https://generativelanguage.googleapis.com/" "v1beta/models/gemini-2.0-flash-exp:" f"generateContent?key={gemini_key}", json= ...[truncated 3733 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Declare every credential the implementation may access: - `MISTRAL_API_KEY` - `ELEVENLABS_API_KEY` - `TAVILY_API_KEY` - `GEMINI_API_KEY` 2. Distinguish required credentials from optional feature credentials. 3. List every outbound service and domain in the network metadata, including Mistral, ElevenLabs, Tavily, and Google Generative Language. 4. Document which input and generated data are transmitted to each provider. 5. Clearly disclose database and prompt-cache persistence, including stored child names and generated media. 6. Obtain explicit consent before transmitting children's information to third-party processors. 7. Pseudonymize or omit child names from provider prompts where they are not necessary. 8. Add configurable provider opt-in controls so optional credentials do not automatically activate additional data sharing. 9. Establish retention periods, deletion endpoints, access controls, and encryption for stored stories and media. 10. Prefer object storage or bounded media references over unrestricted base64 blobs in database fields. 11. Update the security notes to state that base64 is transport encoding rather than encryption. 12. Align runtime egress restrictions with the complete documented provider list. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (21)

Tainted flow: 'ELEVENLABS_API_KEY' from os.environ.get (line 18, credential/environment) → httpx.post (network output)

Critical
Category
Data Flow
Content
if not ELEVENLABS_API_KEY:
        return {"error": "ELEVENLABS_API_KEY not set"}
    try:
        r = httpx.post(f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
            headers={"xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json"},
            json={"text": text, "model_id": "eleven_multilingual_v2",
                  "voice_settings": {"stability": 0.6, "similarity_boost": 0.8}},
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'ELEVENLABS_API_KEY' from os.environ.get (line 18, credential/environment) → httpx.post (network output)

Critical
Category
Data Flow
Content
if not ELEVENLABS_API_KEY:
        return {"error": "ELEVENLABS_API_KEY not set"}
    try:
        r = httpx.post("https://api.elevenlabs.io/v1/sound-generation",
            headers={"xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json"},
            json={"text": prompt, "duration_seconds": min(duration_seconds, 22)},
            timeout=30)
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'ELEVENLABS_API_KEY' from os.environ.get (line 18, credential/environment) → httpx.post (network output)

Critical
Category
Data Flow
Content
if not ELEVENLABS_API_KEY:
        return {"error": "ELEVENLABS_API_KEY not set"}
    try:
        r = httpx.post("https://api.elevenlabs.io/v1/sound-generation",
            headers={"xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json"},
            json={"text": f"Gentle lullaby music: {prompt}", "duration_seconds": min(duration_seconds, 22)},
            timeout=30)
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'TAVILY_API_KEY' from os.environ.get (line 19, credential/environment) → httpx.post (network output)

Critical
Category
Data Flow
Content
if not TAVILY_API_KEY:
        return {"error": "TAVILY_API_KEY not set"}
    try:
        r = httpx.post("https://api.tavily.com/search",
            json={"api_key": TAVILY_API_KEY, "query": query, "max_results": 3}, timeout=15)
        if r.status_code == 200:
            return {"results": [{"title": x.get("title",""), "snippet": x.get("content","")[:200]} for x in r.json().get("results",[])]}
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'gemini_key' from os.environ.get (line 370, credential/environment) → httpx.post (network output)

Critical
Category
Data Flow
Content
for i in range(min(len(scenes), 4)):
            art_prompt = img_prompts.get(f"scene_{i}", f"Dreamy watercolor: {scenes[i][:100]}")
            try:
                resp = hx.post(
                    f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key={gemini_key}",
                    json={"contents": [{"parts": [{"text": art_prompt}]}],
                          "generationConfig": {"responseModalities": ["TEXT", "IMAGE"]}},
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared purpose and required secret mention only Mistral orchestration, but the analyzed behavior reportedly includes additional third-party services, extra API keys, persistence, caching, and a domain-specific story pipeline. This mismatch is dangerous because it can hide the true attack surface, cause operators to grant insufficiently reviewed secrets and network access, and enable undisclosed data flows to external services or storage.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
A skill described as Mistral orchestration also loads unrelated ElevenLabs and Tavily credentials, enabling access to additional external services not justified by the stated purpose. Hidden credential dependencies enlarge blast radius, increase supply-chain trust requirements, and can enable unexpected data sharing and billing abuse.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
The skill metadata presents this as a Mistral multi-agent orchestration capability, but the code also implements audio generation, web search, and later image-generation support. This scope expansion increases attack surface and can surprise deployers by causing external data transfers and costs beyond what the manifest implies.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The code conditionally invokes Gemini to generate illustrations, which is materially outside the declared Mistral orchestration scope. This creates an undisclosed external dependency and transmits derived story content to Google, increasing privacy, cost, and policy risk without clear consent.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill declares environment and outbound network requirements in metadata but does not define an explicit tool scope such as permissions or allowed-tools. That creates an authorization gap where consumers may not clearly understand or constrain what the skill is allowed to access, increasing the chance of overbroad execution in hosts that rely on manifest-level scoping.

External Transmission

Medium
Category
Data Exfiltration
Content
if not ELEVENLABS_API_KEY:
        return {"error": "ELEVENLABS_API_KEY not set"}
    try:
        r = httpx.post(f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
            headers={"xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json"},
            json={"text": text, "model_id": "eleven_multilingual_v2",
                  "voice_settings": {"stability": 0.6, "similarity_boost": 0.8}},
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if not ELEVENLABS_API_KEY:
        return {"error": "ELEVENLABS_API_KEY not set"}
    try:
        r = httpx.post(f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
            headers={"xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json"},
            json={"text": text, "model_id": "eleven_multilingual_v2",
                  "voice_settings": {"stability": 0.6, "similarity_boost": 0.8}},
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if not ELEVENLABS_API_KEY:
        return {"error": "ELEVENLABS_API_KEY not set"}
    try:
        r = httpx.post(f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
            headers={"xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json"},
            json={"text": text, "model_id": "eleven_multilingual_v2",
                  "voice_settings": {"stability": 0.6, "similarity_boost": 0.8}},
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if not ELEVENLABS_API_KEY:
        return {"error": "ELEVENLABS_API_KEY not set"}
    try:
        r = httpx.post("https://api.elevenlabs.io/v1/sound-generation",
            headers={"xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json"},
            json={"text": prompt, "duration_seconds": min(duration_seconds, 22)},
            timeout=30)
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if not ELEVENLABS_API_KEY:
        return {"error": "ELEVENLABS_API_KEY not set"}
    try:
        r = httpx.post("https://api.elevenlabs.io/v1/sound-generation",
            headers={"xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json"},
            json={"text": prompt, "duration_seconds": min(duration_seconds, 22)},
            timeout=30)
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if not ELEVENLABS_API_KEY:
        return {"error": "ELEVENLABS_API_KEY not set"}
    try:
        r = httpx.post("https://api.elevenlabs.io/v1/sound-generation",
            headers={"xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json"},
            json={"text": prompt, "duration_seconds": min(duration_seconds, 22)},
            timeout=30)
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if not TAVILY_API_KEY:
        return {"error": "TAVILY_API_KEY not set"}
    try:
        r = httpx.post("https://api.tavily.com/search",
            json={"api_key": TAVILY_API_KEY, "query": query, "max_results": 3}, timeout=15)
        if r.status_code == 200:
            return {"results": [{"title": x.get("title",""), "snippet": x.get("content","")[:200]} for x in r.json().get("results",[])]}
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
User prompts and child identifiers are sent to Mistral and ElevenLabs during planning, story generation, and narration without any disclosure or consent handling in this file. Because the content may involve minors and personalized details, undisclosed third-party transmission is more sensitive than in a generic text-processing skill.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
Scene-derived content and illustration prompts are sent to Gemini for image generation without user-facing notice. Even though the prompts are model-generated, they still encode story content tied to a child-focused workflow and therefore represent undisclosed external sharing.

Description-Behavior Mismatch

Medium
Confidence
87% confidence
Finding
The endpoint stores generated stories, child names, language, and cached media artifacts in a database, but persistence is not disclosed in the skill description. Undisclosed storage of child-associated content creates privacy, retention, and compliance risk, especially in a bedtime-story context involving minors.

Intent-Code Divergence

Low
Confidence
84% confidence
Finding
The docstring states 'Direct chat with any Mistral Agent via Conversations API,' which implies arbitrary Mistral agent access. In practice, the code only accepts agent names found in the hardcoded AGENTS mapping and rejects all others as unknown.

Static analysis

No suspicious patterns detected.