Back to skill

Security audit

Class Duty Voice Pack

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed Beatra voice-generation integration, but it asks for broad account authority and silently updates its own package files by default.

Review this before installing if you are comfortable granting a shared Beatra device credential with broad media, wallet, task, artifact, and voice permissions. Disable automatic updates with `python3 scripts/mcp_client.py update --auto off` if you do not want the package replacing its own files silently, and avoid uploading voice samples unless you have explicit rights and consent for that sample.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
Findings (4)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:899
Finding
Silent Self-Update Can Retrieve and Install Remotely Controlled Code Without Independent Signature Verification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:31-32, 296-359, 469-490, 899-950, 1527-1529`; `SKILL.md:180-200`; `references/automatic-updates-and-safety.md:3-19` **Vulnerability Type**: Remote payload retrieval and execution through an insufficiently authenticated update channel **Risk Level**: High ### Vulnerable Code ```python PACKAGE_DISCOVERY_URL = "https://beatra.ai/skills/class-duty-voice/channels/clawhub/install.json" PACKAGE_CDN_BASE_TEMPLATE = ( "https://cdn.beatra.ai/agent-packages/" "class-duty-voice/channels/clawhub/v{version}" ) ``` ```python def check_update( *, get_bytes: GetBytes = _default_get_bytes, ) -> dict[str, Any]: discovery = _json_object( get_bytes( _discovery_url(), UPDATE_DISCOVERY_TIMEOUT_SECONDS, MAX_UPDATE_DISCOVERY_BYTES, ), "Beatra update discovery", ) current = _semver(PACKAGE_VERSION) available = _semver(discovery.get("version")) _release_urls(discovery) if available < current: raise RuntimeError("Beatra update discovery attempted a version downgrade") return { "current_version": PACKAGE_VERSION, "available_version": discovery["version"], "update_available": available > current, "discovery": discovery, } ``` ```python def download_update( discovery: dict[str, Any], *, get_bytes: GetBytes = _default_get_bytes, ) -> tuple[dict[str, Any], dict[str, bytes]]: archive_url, manifest_url = _release_urls(discovery) manifest_content = get_bytes( manifest_url, UPDATE_DOWNLOAD_TIMEOUT_SECONDS, MAX_UPDATE_MANIFEST_BYTES, ) if _sha256(manifest_content) != discovery["manifest_sha256"]: raise RuntimeError("Beatra update manifest checksum does not match discovery") manifest = _json_object(manifest_content, "Beatra update manifest") manifest_files = _manifest_files(manifest, discovery=discovery) ...[truncated 4822 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Sign release metadata with a dedicated offline-controlled signing key. 2. Embed or securely provision the corresponding public key in the audited client. 3. Verify a signature covering package identity, version, channel, locale, expiry, manifest digest, and archive digest. 4. Treat TLS and SHA-256 checks as transport and integrity controls, not as sufficient publisher authentication. 5. Disable automatic installation by default. Notify the user and obtain explicit approval before replacing executable files. 6. Separate update operations from credential-bearing business commands. 7. Support key rotation through a signed root-metadata mechanism rather than mutable remote configuration. 8. Record the verified signer and release digest in local update state for auditability. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/authorize.py:34
Finding
Full-Scope Shared Token and Unrestricted Tool Names Violate Least Privilege<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:34-36, 455-468`; `scripts/mcp_client.py:1467-1488`; `references/installation-and-auth.md:73-74`; `references/mcp-connection.md:9-10` **Vulnerability Type**: Excessive authorization scopes and unrestricted privileged API invocation **Risk Level**: High ### Vulnerable Code ```python SCOPE = ( "mcp:tools artifacts:write images:generate videos:generate music:generate " "speech:generate voices:read voices:write wallet:spend tasks:read artifacts:read tasks:cancel" ) ``` ```python form: dict[str, str] = { "client_id": CLIENT_ID, "resource": MCP_URL, "scope": SCOPE, "platform": host_platform, "client_name": PACKAGE_DISPLAY_NAME, "external_installation_ref": external_reference, "package_version": PACKAGE_VERSION, "package_slug": PACKAGE_SLUG, } if device_name: form["device_name"] = device_name status, created = post_form(DEVICE_AUTHORIZATION_URL, form) ``` ```python def _run_command(command: str, tool_name: str | None = None) -> dict[str, Any]: session = _session_with_registration( state_dir=Path.home() / ".beatra", post_json=_default_post_json, ) if command == "tools": return session.request(2, "tools/list", {}) try: arguments = json.load(os.sys.stdin) except json.JSONDecodeError as exc: raise RuntimeError("Tool arguments on stdin must be one JSON object") from exc if not isinstance(arguments, dict): raise RuntimeError("Tool arguments on stdin must be one JSON object") assert tool_name is not None return session.request( 2, "tools/call", {"name": tool_name, "arguments": arguments}, ) ``` ```python call = subparsers.add_parser("call", help="Call one tool with a JSON object on stdin") call.add_argument("tool_name") ``` ### Technical Analysis The declared function is roster-to-speech generation with optional voice cloning. Necessary privileg ...[truncated 1816 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the shared full-scope token with a package-specific least-privilege token. 2. Request only the exact scopes needed for: - text-to-speech model and voice discovery; - speech generation; - optional voice cloning after explicit consent; - upload of an explicitly selected clone sample; - reads of tasks and generated artifacts. 3. Remove unrelated image, video, music, broad wallet, and cancellation scopes. 4. Add a hardcoded package-specific allowlist of MCP tool names in `mcp_client.py`. 5. Reject any tool not required by this Skill before transmitting a request. 6. Separate read-only operations from paid or state-changing operations. 7. Require explicit user confirmation immediately before wallet spending, cancellation, voice writes, and generation. 8. Bind server-issued authorization to the package slug and enforce the same boundary server-side. ]]>

other

Warning
Location
scripts/authorize.py:340
Finding
Authorization Collects and Transmits the Local Hostname Without Clear Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:340-383, 444-468, 563-568`; `references/installation-registration.md:16-20` **Vulnerability Type**: Environment reconnaissance and undisclosed device telemetry **Risk Level**: Medium ### Vulnerable Code ```python def detect_host_platform(explicit: str | None = None) -> str: """The agent environment this process runs inside (docs/device-model.md). Order: explicit agent self-report > environment signatures > unknown. Detection reads the process environment only — nothing else runs, nothing reaches the network. """ if explicit: candidate = explicit.strip().lower().replace(" ", "-") if _PLATFORM_VALUE.fullmatch(candidate): return candidate env = os.environ if env.get("CLAUDECODE") == "1" or "CLAUDE_CODE_ENTRYPOINT" in env: return "claude-code" if any(key.startswith("CODEX_") for key in env): return "codex" ai_agent = env.get("AI_AGENT", "").lower() matched = re.match(r"([a-z0-9-]+)_", ai_agent) if matched and _PLATFORM_VALUE.fullmatch(matched.group(1)): return matched.group(1) return "unknown" ``` ```python def device_display_name() -> str | None: """A hostname the user will recognise in the console device list.""" try: name = socket.gethostname().strip() except OSError: return None if not name or not name.isprintable(): return None return name[:120] ``` ```python def write_host_config(state_dir: Path, *, platform: str, device_name: str | None) -> None: try: payload: dict[str, Any] = {"platform": platform} if device_name: payload["device_name"] = device_name (state_dir / "host.json").write_text( json.dumps(payload, ensure_ascii=False, separators=(",", ":")) + "\n", encoding="utf-8", ) except OSError: pass ``` ```python form: dict[str, str] = { "client_i ...[truncated 2632 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not collect or transmit the hostname by default. 2. Generate a random, non-identifying device label or ask the user to provide an optional label. 3. Make platform and device telemetry opt-in. 4. Display all fields that will be transmitted before authorization. 5. Explain retention, correlation, and deletion behavior in the authentication documentation. 6. Avoid storing `device_name` in `host.json` unless required. 7. Provide a configuration option that forces `platform` to `unknown` and omits `device_name`. 8. Minimize server retention and allow users to delete installation telemetry from the console. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:225
Finding
Server-Provided Upload Grants Can Direct Sensitive Files to Any HTTPS Host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:225-260, 1428-1465` **Vulnerability Type**: Insufficient validation of a remotely supplied upload destination **Risk Level**: Medium ### Vulnerable Code ```python def _complete_upload( result: dict[str, Any], *, mime_type: str, content: bytes, put_bytes: PutBytes, ) -> dict[str, str]: structured = result.get("structuredContent") instruction = structured.get("upload") if isinstance(structured, dict) else None if not isinstance(instruction, dict) or instruction.get("method") != "PUT": raise RuntimeError("Beatra upload instructions are invalid") url = instruction.get("url") headers = instruction.get("headers") if not isinstance(url, str) or not isinstance(headers, dict): raise RuntimeError("Beatra upload instructions are invalid") parsed = urllib.parse.urlsplit(url) if ( parsed.scheme != "https" or not parsed.hostname or parsed.username is not None or parsed.password is not None or parsed.fragment ): raise RuntimeError("Beatra upload instructions are invalid") if not all(isinstance(key, str) and isinstance(value, str) for key, value in headers.items()): raise RuntimeError("Beatra upload instructions are invalid") content_type = _header_value(headers, "Content-Type") content_length = _header_value(headers, "Content-Length") if content_type != mime_type or content_length != str(len(content)): raise RuntimeError("Beatra upload instructions are invalid") response = put_bytes(url, dict(headers), content) artifact_id = response.get("artifact_id") if not isinstance(artifact_id, str) or not artifact_id: raise RuntimeError("Beatra upload returned an invalid response") return {"type": "artifact", "artifact_id": artifact_id} ``` ```python def upload( path: Path, *, mime_type: str, state_dir: Path | None = No ...[truncated 3042 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Maintain an exact allowlist of approved upload hostnames. 2. Validate the expected port, path prefix, query structure, and storage-provider domain. 3. Reject IP-literal hosts, private-network destinations, unusual ports, and unrecognized subdomains. 4. Restrict server-provided headers to an explicit allowlist and reject sensitive headers not required by the upload protocol. 5. Cryptographically bind the upload grant to the artifact request, file digest, MIME type, length, expiry time, and approved destination. 6. Consider calculating the local SHA-256 digest before requesting the grant and include it in the signed grant. 7. Display the destination domain before uploading highly sensitive voice or likeness data. 8. Require renewed user confirmation if the destination is not the canonical documented storage host. ]]>
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
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (22)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill exposes broad capabilities including shell, network, file read/write, and environment access without declaring permissions, which breaks least-privilege expectations and prevents meaningful user consent. In this skill's context, those capabilities are especially risky because the document also instructs invoking a generic MCP client and handling local files, credentials, and remote services, creating a path for unintended command execution, data access, or exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared purpose is narrow audio generation, but the documented behavior includes credential storage, browser-based auth, generic remote tool invocation, file upload, telemetry/registration, uninstall-side revocation, and self-update behavior. That mismatch is dangerous because users may authorize the skill expecting simple media generation while it actually introduces account, filesystem, and software lifecycle changes far beyond the stated scope.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The skill presents itself as a roster-to-voice converter, but later discloses that the bundled client can automatically replace package-owned files. Even if update verification exists, this is a sensitive software-modification capability that materially changes the trust model and should be disclosed up front rather than buried in runtime details.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
Automatic download and installation of updates is unnecessary for the core function of generating class-duty voice clips and introduces a supply-chain and integrity risk. If the update channel, signing flow, or trust roots are compromised, the skill gains a built-in path to modify local software without separate approval.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The documentation describes an automatic external registration call that sends package metadata, platform, and a stable external installation reference, which is telemetry-like behavior unrelated to the advertised classroom voice-roster function. Even if described as non-billable and non-secret, this creates undeclared outbound data flow and installation tracking that users would not reasonably expect from an audio clip generation skill.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The file explicitly ties package registration to platform resolution from environment signatures or a host file, which amounts to environment fingerprinting. In the context of a simple classroom voice-pack skill, collecting and transmitting host-environment characteristics is not justified by the stated purpose and increases privacy and tracking risk across installations.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The skill requests a very broad OAuth scope set including artifacts, images, videos, music, speech, voice management, wallet spending, and task control, while the declared skill purpose is narrowly converting a class duty roster into voice clips. This violates least privilege and means a compromise or misuse of the granted token could affect far more account capabilities than users would reasonably expect from this skill.

Context-Inappropriate Capability

Critical
Confidence
94% confidence
Finding
The helper requests voices:read and voices:write in addition to speech generation, but the advertised workflow only needs turning existing text into audio clips. Voice-management permissions can expose or modify voice assets beyond the immediate task, creating unnecessary access to user-configured voice resources.

Context-Inappropriate Capability

High
Confidence
94% confidence
Finding
The helper requests voices:read and voices:write in addition to speech generation, but the advertised workflow only needs turning existing text into audio clips. Voice-management permissions can expose or modify voice assets beyond the immediate task, creating unnecessary access to user-configured voice resources.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The helper requests voices:read and voices:write in addition to speech generation, but the advertised workflow only needs turning existing text into audio clips. Voice-management permissions can expose or modify voice assets beyond the immediate task, creating unnecessary access to user-configured voice resources.

Description-Behavior Mismatch

High
Confidence
91% confidence
Finding
The skill is described as a roster-to-voice utility, but this client embeds a full remote update channel and telemetry/discovery infrastructure unrelated to that narrow purpose. In a skill context, hidden secondary capabilities materially increase attack surface and enable remote code replacement or behavioral drift beyond what a user would reasonably expect from the declared functionality.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
The code exposes generic remote MCP tool listing and arbitrary tool invocation from stdin, which goes well beyond a single-purpose class-duty voice skill. That turns the package into a general remote command proxy to the Beatra backend, increasing the chance of misuse, privilege expansion, or execution of server-side capabilities unrelated to the user’s intended task.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The client fingerprints the host environment by inspecting agent-specific environment variables and host state, then attaches platform attribution to requests. For a class-duty voice tool, this data collection is not necessary to perform the stated function and creates additional privacy and tracking concerns across installations and usage contexts.

Context-Inappropriate Capability

High
Confidence
94% confidence
Finding
The client records local skill inventory and sends installation registration telemetry that is unrelated to generating class-duty voice clips. This broadens local data collection and remote tracking, and in the skill context it is especially concerning because it is hidden behind best-effort background behavior rather than explicit user action.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
This uninstall script manipulates a shared global Beatra state directory and can revoke the device authorization used by other skills if its last-skill determination is wrong or the inventory is stale. That is dangerous because the skill’s declared purpose is roster-to-voice generation, yet it includes account/device connection teardown logic that affects resources outside the skill’s own package boundary.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The code issues an authenticated POST to a device revocation endpoint using a bearer token from local state, giving this skill the ability to invalidate the broader Beatra device authorization. In the context of a class-duty voice skill, that capability is unrelated to the advertised function and materially increases blast radius if the package is compromised or misused.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
Installing updates without separate confirmation while failing to clearly warn users near the primary description undermines informed consent and increases the chance that users run code that modifies package files without realizing it. In a skill that already has network and local file capabilities, weak disclosure materially increases operational and security risk.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The document states that the client silently checks for and automatically installs newer releases by default before ordinary commands, without separate confirmation. Even though the update path includes strong integrity and rollback controls, automatic file replacement and code changes without explicit opt-in or prominent user warning create a real security and trust risk, especially if users are unaware that local software may be modified during routine use.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The documentation states that the client performs an external registration call and writes a local cache file, but it does not present this as a user-facing warning or consent prompt. Hidden network transmission and filesystem writes reduce transparency and can violate user expectations, especially for a skill presented as a straightforward roster-to-audio utility.

Credential Access

High
Category
Privilege Escalation
Content
#: these and then removes the directory only if it is empty — the script
#: never recursively deletes a directory it does not fully understand.
_STATE_FILES = (
    "credentials.json",
    "installation.json",
    "host.json",
    "skills.json",
Confidence
91% confidence
Finding
Referencing and deleting credentials.json as part of the skill uninstall flow shows the package is designed to interact with shared credential material rather than only its own artifacts. Even without exfiltration, unnecessary access to credential-bearing files increases the chance of credential misuse, accidental deletion, or future expansion into more dangerous behavior.

Credential Access

High
Category
Privilege Escalation
Content
def _device_token(state_dir: Path) -> str | None:
    path = state_dir / "credentials.json"
    try:
        value = json.loads(path.read_text(encoding="utf-8"))
    except (OSError, ValueError):
Confidence
95% confidence
Finding
The _device_token function parses credentials.json and extracts an access token for direct use in authenticated revocation requests. Accessing bearer tokens inside an application skill is high risk because any flaw, modification, or abuse of the skill can turn that token access into account-impacting actions beyond the voice-generation use case.

Self-Modification

High
Category
Rogue Agent
Content
)
    update = subparsers.add_parser(
        "update",
        help="Check, install, or configure Beatra package self-updates",
    )
    update.add_argument(
        "--check",
Confidence
97% confidence
Finding
This package can self-update by downloading a manifest and archive from remote infrastructure and replacing local package files, including automatic background updates. Even though the implementation performs several integrity and path-safety checks, self-modifying behavior is high risk in an end-user skill because it enables remote code changes after installation and expands the consequences of any upstream compromise or trust failure.

Static analysis

No suspicious patterns detected.