Back to skill

Security audit

TopMediai TTS

Security checks for vulnerabilities and agentic risk

Overview

This TopMediai text-to-speech skill is coherent, but it can send the configured API key and user text to an environment-controlled API host without validating that host.

Install only if you trust the skill publisher and your local .env configuration. Keep TOPMEDIAI_BASE_URL at https://api.topmediai.com unless you deliberately use a trusted test endpoint, avoid submitting sensitive text unless approved for TopMediai processing, and prefer pinned dependencies before production use.

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
scripts/topmediai_tts_api.py:20
Finding
Unvalidated API Base URL Can Redirect Credentials and TTS Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/topmediai_tts_api.py:20`, `scripts/topmediai_tts_api.py:63-67`, and `scripts/topmediai_tts_api.py:109-124` **Vulnerability Type**: Unvalidated credential-bearing outbound request destination **Risk Level**: Medium ### Vulnerable Code ```python BASE_URL = os.environ.get("TOPMEDIAI_BASE_URL", "https://api.topmediai.com") ``` ```python def _headers(api_key: Optional[str] = None) -> Dict[str, str]: key = api_key or DEFAULT_KEY if not key: raise RuntimeError( "TOPMEDIAI_API_KEY not configured. Edit: {} and set TOPMEDIAI_API_KEY=YOUR_KEY.".format(_ENV_PATH) ) return {"x-api-key": key, "Content-Type": "application/json"} ``` ```python def text_to_speech(text: str, speaker: str, emotion: Optional[str] = None, api_key: Optional[str] = None) -> Dict[str, Any]: url = f"{BASE_URL}/v1/text2speech" headers = _headers(api_key) payload: Dict[str, Any] = { "text": text, "speaker": speaker, } if emotion: payload["emotion"] = emotion _debug_request("POST", url, headers=headers, payload=payload) try: r = requests.post(url, json=payload, headers=headers, timeout=120) r.raise_for_status() return r.json() except Exception as e: _raise_as_runtime_error(e, "POST", url) ``` The same base URL and credential-bearing headers are also used by the account-information and voice-list requests. ### Technical Analysis `TOPMEDIAI_BASE_URL` is accepted directly from the process environment or project `.env` file without validating its scheme, hostname, port, or embedded credentials. The application subsequently attaches the TopMediai API key as an `x-api-key` header to requests sent to that destination. Consequently, anyone able to alter the process environment or `.env` configuration can redirect requests to an attacker-controlled endpoint. For TTS operations, both the API key and the user-supplied tex ...[truncated 1712 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict production requests to the documented TopMediai endpoint: - Allowlist `api.topmediai.com`. - Require the `https` scheme. - Reject embedded credentials, unexpected ports, fragments, and unapproved hostnames. 2. Parse and validate the URL with `urllib.parse.urlparse` before constructing any request. 3. If custom endpoints are required for testing, make them an explicit development-only option and require a separate non-production API key. 4. Fail closed when validation fails instead of sending a request. 5. Consider disabling redirects or validating every redirect target so credentials cannot be forwarded to an unexpected host. 6. Document that anyone able to modify `.env` can control the destination of sensitive requests. 7. Add automated tests covering HTTP URLs, lookalike domains, embedded credentials, unexpected ports, and redirect behavior. Example hardening approach: ```python from urllib.parse import urlparse ALLOWED_API_HOSTS = {"api.topmediai.com"} def validate_base_url(value: str) -> str: parsed = urlparse(value) if parsed.scheme != "https": raise RuntimeError("TOPMEDIAI_BASE_URL must use HTTPS") if parsed.hostname not in ALLOWED_API_HOSTS: raise RuntimeError("TOPMEDIAI_BASE_URL host is not approved") if parsed.username or parsed.password or parsed.fragment: raise RuntimeError("TOPMEDIAI_BASE_URL contains unsupported components") if parsed.port not in (None, 443): raise RuntimeError("TOPMEDIAI_BASE_URL uses an unapproved port") return value.rstrip("/") BASE_URL = validate_base_url( os.environ.get("TOPMEDIAI_BASE_URL", "https://api.topmediai.com") ) ``` ]]>

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding
Third-Party Dependencies Are Not Reproducibly Pinned<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-2` **Vulnerability Type**: Unbounded dependency resolution and missing integrity verification **Risk Level**: Low ### Vulnerable Code ```text requests>=2.31.0 python-dotenv>=1.0.1 ``` The documented installation procedure in `README.md:12` installs these open-ended constraints directly: ```text pip install -r requirements.txt ``` ### Technical Analysis The dependencies use minimum-version constraints without upper bounds, exact pins, a lock file, or package hashes. Each installation can therefore resolve to a different future release. This prevents reproducible builds and means that a newly published, compromised, or incompatible dependency version can enter the environment without a corresponding review of this project. The package names are established packages and there is no evidence in the audited files that either dependency is currently malicious. The risk arises from unconstrained future dependency resolution and the absence of integrity verification. ### Attack Path 1. A future dependency release is compromised, maliciously published, or otherwise introduces unsafe behavior. 2. A user follows the documented command `pip install -r requirements.txt`. 3. The package resolver selects that newer release because it satisfies the `>=` constraint. 4. The dependency is installed without comparison against an approved hash or lock file. 5. The package's code executes during import or when the skill performs HTTP requests or loads environment configuration. ### Impact Assessment Dependency code runs with the privileges of the Python process executing the skill. A compromised dependency could potentially: - Read the TopMediai API key and other environment variables. - Read files accessible to the skill process. - Alter or intercept network requests and TTS content. - Execute arbitrary Python behavior under the invoking user's account. The practical risk is reduced because exploi ...[truncated 141 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin dependencies to exact versions that have been reviewed and tested, for example: ```text requests==<reviewed-version> python-dotenv==<reviewed-version> ``` 2. Generate a lock file using a dependency-management tool such as `pip-tools`, Poetry, or uv. 3. Record and verify package hashes. For pip-based deployments, use a fully resolved requirements file and install it with: ```text pip install --require-hashes -r requirements.lock ``` 4. Include transitive dependencies in the lock file rather than pinning only direct dependencies. 5. Run dependency vulnerability scanning in continuous integration using tools such as `pip-audit`. 6. Use a trusted package index and prevent unreviewed fallback to alternative indexes. 7. Establish a controlled update process that reviews release notes, security advisories, and test results before changing locked versions. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (15)

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

Critical
Category
Data Flow
Content
headers = _headers(api_key)
    _debug_request("GET", url, headers=headers)
    try:
        r = requests.get(url, headers=headers, timeout=30)
        r.raise_for_status()
        return r.json()
    except Exception as e:
Confidence
95% confidence
Finding
The request URL is derived from TOPMEDIAI_BASE_URL, an environment-controlled value, and the code sends the x-api-key header to whatever host that value points to. If an attacker can influence the environment or bundled .env file, they can redirect requests to an attacker-controlled server and exfiltrate the API key and user content.

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

Critical
Category
Data Flow
Content
headers = _headers(api_key)
    _debug_request("GET", url, headers=headers)
    try:
        r = requests.get(url, headers=headers, timeout=60)
        r.raise_for_status()
        return r.json()
    except Exception as e:
Confidence
95% confidence
Finding
This GET call uses a URL built from the environment-supplied BASE_URL and includes the API key header. A malicious or compromised environment configuration can cause credential-bearing requests to be sent to an unintended destination, enabling secret disclosure and request hijacking.

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

Critical
Category
Data Flow
Content
headers = _headers(api_key)
    _debug_request("GET", url, headers=headers)
    try:
        r = requests.get(url, headers=headers, timeout=60)
        r.raise_for_status()
        return r.json()
    except Exception as e:
Confidence
95% confidence
Finding
Like the other API methods, this function trusts an environment-derived base URL and transmits the x-api-key to that endpoint. In a skill context, local configuration is often easier to tamper with than application code, so this becomes a realistic credential exfiltration vector.

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

Critical
Category
Data Flow
Content
payload["emotion"] = emotion
    _debug_request("POST", url, headers=headers, payload=payload)
    try:
        r = requests.post(url, json=payload, headers=headers, timeout=120)
        r.raise_for_status()
        return r.json()
    except Exception as e:
Confidence
97% confidence
Finding
The POST target is environment-controlled and receives both the API key and the user's TTS text payload. If BASE_URL is altered, the skill can leak sensitive user-provided text and credentials to an attacker-controlled endpoint, which is especially relevant because this function handles the most sensitive content.

Credential Access

High
Category
Privilege Escalation
Content
# Copy to .env and fill your key
TOPMEDIAI_API_KEY=YOUR_KEY
TOPMEDIAI_BASE_URL=https://api.topmediai.com
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
from pathlib import Path

_SKILL_ROOT = Path(__file__).resolve().parents[1]
_ENV_PATH = _SKILL_ROOT / ".env"
if _ENV_PATH.exists():
    load_dotenv(_ENV_PATH.as_posix())
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The README explicitly describes sending user-provided text to TopMediai's remote TTS API but does not warn users that their prompts may leave the local environment and be processed by a third party. This can lead to accidental disclosure of sensitive, proprietary, or regulated data if users assume the skill operates locally or do not understand the privacy implications.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill requires environment access for `TOPMEDIAI_API_KEY` and performs outbound network requests, but it does not declare any tool scope such as permissions or allowed-tools. This weakens security review and user consent because the skill can access secrets and contact an external service without explicit capability declaration in the manifest.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill sends user-provided text and an API-key-authenticated request to an external TopMediai service, but the description does not clearly warn users about this data flow. This creates a privacy and transparency risk because users may unknowingly transmit sensitive prompts or content to a third party.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
When TOPMEDIAI_DEBUG is enabled, _debug_request logs the full payload, including user text submitted for speech generation. This can expose sensitive prompts or personal data in console logs, CI logs, or agent telemetry without any explicit user notice or redaction.

External Transmission

Medium
Category
Data Exfiltration
Content
payload["emotion"] = emotion
    _debug_request("POST", url, headers=headers, payload=payload)
    try:
        r = requests.post(url, json=payload, headers=headers, timeout=120)
        r.raise_for_status()
        return r.json()
    except Exception as e:
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.31.0
python-dotenv>=1.0.1
Confidence
97% confidence
Finding
The dependency is specified with a lower bound only (`requests>=2.31.0`), which allows future releases to be installed without review and prevents reproducible builds. This creates supply-chain risk and makes it unclear whether a vulnerable or breaking version could be resolved in different environments.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding
`requests` has known advisories, and because the manifest does not pin an exact version, there is no way to verify from this file alone whether the installed version includes fixes. In a skill that likely makes outbound API calls, this uncertainty increases supply-chain and runtime risk, even though the file does not prove a specific vulnerable release is installed.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.31.0
python-dotenv>=1.0.1
Confidence
97% confidence
Finding
The dependency is not pinned to an exact version (`python-dotenv>=1.0.1`), so installations may resolve to different releases over time. That weakens reproducibility and can silently introduce vulnerable or incompatible versions into the skill.

Unverifiable Dependency: python-dotenv has 2 known advisory(ies) (CVE-2026-28684 (python-dotenv: Symlink following in set_key allows arbitrary file overwrite via ); CVE-2026-28684 (python-dotenv reads key-value pairs from a .env file and can set them as environ)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
88% confidence
Finding
`python-dotenv` has advisories, but the unpinned requirement prevents verifying whether the resolved package version is affected. While this package is typically used for local configuration loading, leaving the version unconstrained still exposes the project to avoidable supply-chain uncertainty.

Static analysis

No suspicious patterns detected.