Back to skill

Security audit

Azure Speech Tts

Security checks for vulnerabilities and agentic risk

Overview

This Azure text-to-speech skill is mostly coherent, but a real validation flaw could send your Azure key and speech text to a non-Azure host if the region value is tampered with.

Review or patch the region validation before installing. Use only known Azure region values, keep the Azure Speech key least-privileged and rotatable, avoid sending sensitive text unless cloud processing is approved, and choose output paths carefully because explicit paths can be overwritten.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/azure_tts.py:240
Finding

Unvalidated Azure Region Enables Credential and Speech-Content Exfiltration

Content
View full analysis
str: url = TOKEN_URL_TEMPLATE.format(region=region) req = request.Request( url, method="POST", headers={"Ocp-Apim-Subscription-Key": key}, ) try: with request.urlopen(req, timeout=timeout) as resp: return resp.read().decode("utf-8") except error.HTTPError as exc: body = exc.read().decode("utf-8", errors="replace") raise SystemExit(f"Token request failed: {exc.code} {exc.reason}: {body}") from exc def synthesize( region: str, token: str, ssml: str, audio_format: str, timeout: int, ) -> bytes: url = SYNTHESIS_URL_TEMPLATE.format(region=region) req = request.Request( url, data=ssml.encode("utf-8"), method="POST", headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/ssml+xml", "X-Microsoft-OutputFormat": audio_format, "User-Agent": "openclaw-azure-speech-tts", }, ) try: with request.urlopen(req, timeout=timeout) as resp: return resp.read() except error.HTTPError as exc: body = exc.read().decode("utf-8", errors="replace") raise SystemExit(f"Synthesis failed: {exc.code} {exc.reason}: {body}") from exc ``` ```pyth ...[truncated 3059 chars]
Remediation
View remediation
str: normalized = region.strip().lower() if not REGION_PATTERN.fullmatch(normalized): raise SystemExit("Invalid Azure region.") return normalized ``` 2. **Prefer an explicit allowlist** of supported Azure Speech regions rather than relying only on a character pattern. Reject dots, slashes, backslashes, colons, `@`, whitespace, percent-encoding, and all other URL metacharacters. 3. **Verify the parsed destination before attaching credentials:** ```python from urllib.parse import urlsplit region = validate_region(region) url = TOKEN_URL_TEMPLATE.format(region=region) parsed = urlsplit(url) expected_host = f"{region}.api.cognitive.microsoft.com" if parsed.scheme != "https" or parsed.hostname != expected_host: raise SystemExit("Refusing to send credentials to an unexpected endpoint.") ``` Apply an equivalent exact-host check to: ```text {region}.tts.speech.microsoft.com ``` 4. **Do not attach the subscription key or bearer token until endpoint validation succeeds.** Redirect handling should also be restricted or disabled for credential-bearing requests so redirects cannot forward sensitive headers to another origin. 5. **Rotate any Azure key that may have been used with an untrusted region value.** Review Azure usage and billing logs for anomalous requests. 6. **Add regression tests** covering malicious values such as: ```text attacker.example/path eastus@example.com eastus:443 eastus/anything eastus%2Fanything ../eastus ``` Each value must be rejected before any network request occurs. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (10)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The README requires Azure Speech credentials but does not clearly disclose that provided text or SSML will be transmitted to Microsoft's cloud service for synthesis. This can lead users to unknowingly send sensitive or regulated content to a third party, creating privacy, compliance, and data-handling risks in contexts where local-only processing is assumed.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill describes capabilities that require environment access, local file read/write, and network access to Azure, but it does not declare any explicit tool scope such as permissions or allowed-tools. This creates an authorization ambiguity where an agent may invoke broader capabilities than the skill contract makes clear, reducing reviewability and increasing the chance of unintended secret access or data exfiltration.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill instructs users to send text or SSML to Azure Speech but does not clearly warn that provided content leaves the local environment for cloud processing. Users may unknowingly submit sensitive prompts, personal data, or proprietary SSML/text to a third party, creating privacy, compliance, and data handling risks.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The config sets default_voice to a zh-CN voice as the default behavior, which indicates a language/locale constraint. Under the policy, forcing a specific language or locale without user opt-in is a natural-language policy violation unless clearly justified or optional.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

The trigger phrase “生成一个 mp3 / wav 语音文件” is generic and could match many unrelated audio-generation requests, not specifically this Azure Speech skill. The file does not provide exclusion conditions or scope constraints to distinguish when this skill should activate versus other audio or TTS tools.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
82% confidence
Finding

The phrase “帮我写个 SSML 并合成” is broad because it does not constrain the provider, workflow, or context, and could apply to multiple speech-synthesis tools. Without negative examples or narrower activation guidance, it risks unintended invocation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The markdown file states that the skill saves audio to a local file under download/, which is a user-data-affecting operation. Under the markdown criteria for SQP-2, the description should disclose behaviors that affect local files or system state, but no warning is given about file creation or possible overwrite effects.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
74% confidence
Finding

The README defaults to a zh-CN voice and demonstrates only Chinese-language usage, which can amount to a language/locale preference being imposed without explicit user opt-in. Because no language-choice guidance or justification is provided in this file, it may conflict with the policy against forcing a specific language or locale.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The documented default voice is zh-CN-Yunqi:DragonHDOmniLatestNeural, and the sample output also defaults language to zh-CN, which creates an implicit locale preference. The file does not explain why Chinese is the default or present it as an opt-in choice, which may conflict with language/locale policy expectations.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

The default configuration sets default_voice to zh-CN-Yunqi:DragonHDOmniLatestNeural, and the common defaults repeat that same Chinese locale voice. The document does not indicate that users can choose their preferred language by default, nor does it justify a Chinese-only default as region-specific behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.