Back to skill

Security audit

fishaudio-tts

Security checks for vulnerabilities and agentic risk

Overview

This FishAudio TTS skill mostly matches its stated purpose, but it needs review because it automatically reads a shared TOOLS.md file for credentials and proxy settings before sending requests to a cloud API.

Review before installing. Prefer setting FISH_AUDIO_API_KEY explicitly and avoid storing secrets in a shared TOOLS.md file. Do not submit confidential, regulated, or internal text unless you are comfortable sending it to FishAudio, and consider pinning dependencies in a virtual environment.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T09 · Insecure Skill Coding Practices

Warning
Location
fish_tts.py:31
Finding
Broad plaintext API-key discovery from shared Agent configuration<![CDATA[ ## Vulnerability Details **File Location**: `fish_tts.py:31-49` **Related Documentation**: `SKILL.md:22-23` **Vulnerability Type**: Plaintext credential handling and imprecise secret selection **Risk Level**: Medium ### Vulnerable Code ```python def get_api_key(): """Get API key from environment variable or TOOLS.md""" api_key = os.environ.get('FISH_AUDIO_API_KEY') if api_key: return api_key # Try to read from TOOLS.md tools_path = os.path.expanduser('~/.openclaw/workspace/TOOLS.md') if os.path.exists(tools_path): with open(tools_path, 'r', encoding='utf-8') as f: content = f.read() # Look for FishAudio API key for line in content.split('\n'): if 'fish' in line.lower() and 'api' in line.lower() and 'key' in line.lower(): # Extract key after colon if ':' in line: candidate = line.split(':', 1)[1].strip() if candidate: return candidate return None ``` The documentation encourages this storage method: ```markdown 1. Get your API key from https://fish.audio/ 2. Add API key to your `TOOLS.md` or environment variable `FISH_AUDIO_API_KEY` ``` ### Technical Analysis The Skill reads the entire shared `~/.openclaw/workspace/TOOLS.md` file to locate a credential. It identifies the credential through a loose substring test requiring only the words `fish`, `api`, and `key` on the same line, then treats everything after the first colon as a Bearer token. This design has two security weaknesses: 1. It encourages storage of an API credential in a general-purpose plaintext Agent configuration file. 2. It may select an unintended value from a comment, example, malformed configuration entry, or attacker-controlled line. The selected value is subsequently placed in the HTTP `Authorization` header and sent to `https://api.fish.audio/v1/tts`. ...[truncated 1596 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove fallback credential discovery from `TOOLS.md`. 2. Accept credentials only through `FISH_AUDIO_API_KEY` or an operating-system-backed secret manager. 3. If configuration-file compatibility is essential, use a dedicated file with restrictive permissions and parse an exact field such as `FISH_AUDIO_API_KEY`, rather than applying substring matching. 4. Validate the credential format before placing it in an authorization header. 5. Document that input text and the FishAudio API credential are transmitted to the FishAudio cloud service. 6. Update `SKILL.md` so it no longer recommends storing API keys in a shared general-purpose configuration file. 7. Avoid recommending `--api-key` for routine use because command-line arguments may be visible to other local processes or retained in shell history. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:24
Finding
Unpinned third-party dependency installation<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:24` **Vulnerability Type**: Unpinned supply-chain dependency **Risk Level**: Low ### Vulnerable Code ```markdown 3. `pip install requests` (usually already installed) ``` ### Technical Analysis The installation instructions install `requests` without specifying an audited version, lock file, or integrity hash. Consequently, the installed package and its transitive dependencies depend on the package index and repository state at installation time. This does not establish that the legitimate `requests` package is malicious. It is a supply-chain hardening weakness: installations are not reproducible, and a future compromised release, dependency, package index, or index configuration could introduce unintended code. ### Attack Path 1. A user follows the documented `pip install requests` instruction. 2. `pip` resolves the package from the user's configured package index and selects the currently eligible version. 3. If that index, package release, or a transitive dependency has been compromised, the affected package is downloaded. 4. Package-controlled installation or runtime behavior executes with the privileges of the user running `pip` or the TTS script. 5. The compromised dependency could access files, environment variables, and network resources available to that user, including `FISH_AUDIO_API_KEY`. Exploitation is conditional on compromise or malicious configuration of the dependency supply chain; no malicious dependency was found in the audited project itself. ### Impact Assessment A compromised dependency would generally execute with the privileges of the installing or invoking user. This could expose user-accessible files and environment variables, alter the Python environment, or make unauthorized network requests. The project does not request administrator privileges, so the code alone does not provide elevated system access. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `requests` and its transitive dependencies to reviewed versions in a requirements or lock file. 2. Generate and verify cryptographic hashes, for example by using a hashed requirements file with `pip install --require-hashes`. 3. Install exclusively from a trusted, explicitly configured package index. 4. Periodically scan pinned dependencies for published vulnerabilities and update them through a controlled review process. 5. Recommend installation in a dedicated virtual environment with ordinary user privileges. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (8)

Tainted flow: 'headers' from os.environ.get (line 79, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
if proxies:
            print(f"Using proxy: {proxies['https']}")
        
        response = requests.post(url, json=payload, headers=headers, proxies=proxies)
        response.raise_for_status()
        
        # Save the audio
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Credential Access

High
Category
Privilege Escalation
Content
}

def get_api_key():
    """Get API key from environment variable or TOOLS.md"""
    api_key = os.environ.get('FISH_AUDIO_API_KEY')
    if api_key:
        return api_key
Confidence
86% confidence
Finding
The function explicitly accesses credentials from both an environment variable and a local workspace file, giving the skill credential-access capability beyond what is minimally necessary. In this context, environment-variable access alone is normal, but combining it with opportunistic secret discovery from TOOLS.md makes the behavior materially more dangerous because it broadens the set of secrets the skill may consume without clear consent.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill documentation instructs use of an external Python script that requires environment/API-key access, local file output, and network communication, but the skill declares no explicit tool scope or permissions. This creates an authorization and transparency gap: an agent or user may invoke capabilities beyond what is visibly declared, increasing the risk of secret exposure or unintended external communication.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill reads ~/.openclaw/workspace/TOOLS.md to discover proxy settings and API credentials, even though a TTS utility does not need to inspect an unrelated workspace notes file to function. This expands the skill's access to local sensitive data and can unintentionally harvest secrets or route traffic through attacker-controlled proxy settings embedded in that file.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script silently searches TOOLS.md for lines resembling a FishAudio API key and uses any extracted value. Pulling credentials from a general-purpose local document without explicit user awareness creates a secret-discovery behavior that is unnecessary for the stated purpose and may cause accidental use or disclosure of credentials stored for other tools.

External Transmission

Medium
Category
Data Exfiltration
Content
voice_id = get_voice_id(voice_name)
    
    # FishAudio API endpoint
    url = "https://api.fish.audio/v1/tts"
    
    headers = {
        "Authorization": f"Bearer {api_key}",
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 proxies:
            print(f"Using proxy: {proxies['https']}")
        
        response = requests.post(url, json=payload, headers=headers, proxies=proxies)
        response.raise_for_status()
        
        # Save the audio
Confidence
80% 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
93% confidence
Finding
The skill sends user-supplied text to api.fish.audio, but it does not clearly warn that all provided text leaves the local environment and is processed by an external service. In a skill context, users may pass sensitive prompts or internal content, so the lack of a prominent disclosure increases privacy and data-handling risk.

Static analysis

No suspicious patterns detected.