Back to skill

Security audit

Credit Rights Voice Pack

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its voice-generation purpose, but it stores a broad shared Beatra token and silently self-updates package code by default.

Review before installing. This is not classified as malicious, but users should be comfortable granting Beatra a shared local bearer token for multiple media and account operations, allowing selected local voice samples to be uploaded, sending limited device metadata, and accepting default-on silent package updates. In stricter environments, disable automatic updates first and prefer a package-specific least-privilege credential before use.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/authorize.py:33
Finding
Overprivileged Shared Authorization with Unrestricted MCP Tool Dispatch## Vulnerability Details **File Location**: `scripts/authorize.py:33-37`; `scripts/mcp_client.py:1463-1483` **Vulnerability Type**: Excessive OAuth scope and unrestricted privileged tool 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 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}, ) ``` ### Technical Analysis The declared workflow primarily requires text-to-speech generation, voice and model discovery, artifact access, and task-result retrieval. Authorization nevertheless requests unrelated capabilities for image, video, and music generation, voice mutation, general wallet spending, and task cancellation. The command-line client also accepts an arbitrary `tool_name` and forwards it to the remote MCP endpoint without enforcing a package-specific allowlist. Consequently, the local client does not constrain use of the bearer credential to the operations documented by this Skill. The credential is shared across installed Beatra Skills. This increases the security boundary affected by compromise: inj ...[truncated 1355 chars]
Remediation
## Remediation Suggestions 1. Replace the shared full-scope token with a package-specific, least-privilege credential. 2. Remove image, video, music, broad wallet-spend, voice-write, and task-cancel scopes unless a documented workflow requires them. 3. Implement a strict local allowlist for this Skill, such as model listing, voice listing, speech synthesis, authorized asset upload, task retrieval, and explicitly requested wallet reads. 4. Separate read-only, paid, and destructive tools into distinct authorization levels. 5. Require explicit user confirmation immediately before every paid, destructive, or account-mutating call. 6. Bind server-side authorization to the package identity rather than trusting client-provided source metadata. 7. Do not allow one compromised Skill to reuse a credential issued to unrelated Skills.

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Silent Remote Replacement of Executable Skill Code Without Cryptographic Release Signatures## Vulnerability Details **File Location**: `scripts/mcp_client.py:520-522`, `scripts/mcp_client.py:969-1019`, `scripts/mcp_client.py:1539-1544`; `SKILL.md:183-202` **Vulnerability Type**: Automatic remote payload retrieval and subsequent execution **Risk Level**: High ### Vulnerable Code ```python def _read_update_state(update_home: Path) -> dict[str, Any]: path = update_home / "state.json" try: value = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError): return {"schema_version": 1, "auto_update": True} if not isinstance(value, dict) or value.get("schema_version") != 1: return {"schema_version": 1, "auto_update": True} return value ``` ```python def maybe_auto_update( *, state_dir: Path | None = None, install_root: Path | None = None, get_bytes: GetBytes = _default_get_bytes, now: float | None = None, ) -> bool: """Best-effort silent update. Never block the requested MCP command.""" resolved_state = state_dir or Path.home() / ".beatra" try: resolved_root = (install_root or _current_install_root()).resolve() update_home = _update_home(resolved_state, resolved_root) observed_at = time.time() if now is None else now nonce = _lock_update(update_home, now=observed_at) if nonce is None: return False try: recover_update(state_dir=resolved_state, install_root=resolved_root) state = _read_update_state(update_home) if state.get("auto_update", True) is False: return False last_checked = state.get("last_checked_at") if ( isinstance(last_checked, (int, float)) and observed_at - float(last_checked) < UPDATE_CHECK_MAX_AGE_SECONDS ): return False state["last_checked_at"] = ...[truncated 3306 chars]
Remediation
## Remediation Suggestions 1. Disable automatic installation by default and require an explicit opt-in. 2. Notify the user of the current version, proposed version, source, and changes before replacing files. 3. Sign release manifests using an offline or separately controlled signing key. 4. Embed or securely provision a pinned public verification key in the client. 5. Verify the signature before trusting archive hashes, manifest hashes, version information, or file lists. 6. Use key rotation with threshold signatures or an auditable trusted-root update mechanism. 7. Preserve the existing archive/path/ownership/rollback controls because they remain useful defense-in-depth. 8. Consider distributing updates through a platform package manager with authenticated, transparent releases rather than implementing silent self-modification.

other

Note
Location
scripts/authorize.py:340
Finding
Collection and Transmission of Hostname, Agent Platform, and Persistent Installation Metadata## Vulnerability Details **File Location**: `scripts/authorize.py:340-369`, `scripts/authorize.py:450-467`; `scripts/mcp_client.py:1215-1229`, `scripts/mcp_client.py:1381-1404` **Vulnerability Type**: Unnecessary environment and installation telemetry **Risk Level**: Low ### 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" 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 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_ ...[truncated 2282 chars]
Remediation
## Remediation Suggestions 1. Make device and installation telemetry explicitly opt-in. 2. Omit the hostname by default or request a user-selected device label. 3. Clearly disclose each transmitted field before authorization. 4. Replace the stable installation reference with a rotating, purpose-limited pseudonymous identifier where persistent identity is unnecessary. 5. Avoid attaching platform telemetry to every business call. 6. Define and disclose retention, deletion, and correlation policies. 7. Ensure authorization and speech generation continue to work when telemetry is declined.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill declares no permissions while instructing use of shell execution, local file access, network communication, credential handling, and package self-update behavior. This under-declaration is dangerous because it prevents users and policy systems from accurately understanding the real trust boundary and can lead to unintended execution of sensitive operations.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The published description presents a simple voice-generation workflow, but the skill also performs materially broader actions: authentication, credential storage, arbitrary remote tool invocation, file upload, telemetry/registration, uninstall-side revocation, and automatic downloading and self-replacement. This mismatch is dangerous because users may authorize the skill for low-risk media work without realizing it introduces persistent credentials, outbound data transfer, and code-update/supply-chain risk.

Natural-Language Policy Violations

Medium
Confidence
72% confidence
Finding
The instruction to translate certain provider messages while preserving only the URL forces language transformation without explicit user choice. In a finance-adjacent workflow, this can alter meaning of billing or insufficiency notices and create compliance, user-understanding, or dispute-resolution issues if the translated text is not verbatim authoritative content.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The document states that the client performs silent automatic update checks by default and will automatically install newer versions without separate confirmation. Even though it describes integrity checks and rollback protections, silently replacing executable/package files is system-modifying behavior that can materially change what code runs on the user's machine without contemporaneous user awareness, increasing supply-chain and trust-boundary risk.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The auto-update path can silently download and replace installed package files during normal command execution, without an interactive prompt or prominent user-visible notice. Although the code includes strong integrity checks, this still expands trust in a remote update channel and allows code changes to land unexpectedly, which is risky in agent tooling because future runs execute the new code automatically.

Credential Access

High
Category
Privilege Escalation
Content
scope = _required_string(polled, "scope")
            if set(scope.split()) != set(SCOPE.split()):
                raise RuntimeError("Beatra authorization returned an unsupported scope")
            credential_path = state_dir / "credentials.json"
            _atomic_json(
                credential_path,
                {
Confidence
70% confidence
Finding
The script persists a long-lived bearer access token in plaintext JSON on disk under ~/.beatra/credentials.json. Although it attempts to set restrictive filesystem permissions, local plaintext token storage remains a valuable target: malware, other processes running as the same user, backups, or misconfigured environments can recover the token and use the very broad granted scopes, including wallet spending and artifact/task operations.

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
94% confidence
Finding
This client contains self-modification capability that downloads and replaces its own installed files, and also invokes silent auto-update on ordinary command paths. Even with checksum and path validation, self-updating code in an agent skill is dangerous because compromise of the vendor update infrastructure, signing process, or package publication workflow can directly turn into remote code execution on subsequent runs.

Static analysis

No suspicious patterns detected.