Back to skill

Security audit

Shift Handoff Voice Pack

Security checks for vulnerabilities and agentic risk

Overview

This voice-generation skill is not plainly malicious, but it needs Review because it uses a broad shared Beatra token, can dispatch arbitrary MCP tools, sends device/agent identifiers, and silently self-updates executable files by default.

Install only if you are comfortable giving this package a shared Beatra device credential with broad account capabilities and allowing default silent package updates. Before use in a sensitive environment, disable auto-update with python3 scripts/mcp_client.py update --auto off, review what ~/.beatra stores, and be aware that hostname/platform attribution and a stable installation reference may be sent to Beatra.

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:31
Finding
Overprivileged Shared Device Token and Unrestricted MCP Tool Dispatch<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:31-35`; `scripts/mcp_client.py:1463-1482` **Vulnerability Type**: Excessive authorization scope and unrestricted privileged tool dispatch **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 Skill's declared function is to synthesize shift-handoff speech and, optionally, clone an authorized voice sample. Its authorization request nevertheless grants image, video, and music generation in addition to speech, voice, artifact, task, cancellation, and wallet-spending capabilities. The resulting full-scope token is stored as a shared Beatra device credential. The bundled client then accepts an arbitrary MCP tool name from the command line and forwards it through `tools/call` without a local allowlist restricting the operation to the tools needed by this Skill. Server-side authorization may still restrict the exact tools available, but the local implementation places no narrower boundary around this package. The combination of a bro ...[truncated 1435 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the full shared scope with a package-specific least-privilege scope containing only: - Text-to-speech generation. - Voice listing and optional voice cloning. - Required artifact upload/read operations. - Model discovery. - Task creation and task-status reads. - Wallet reads only when explicitly requested. 2. Do not grant image, video, or music generation to this Skill. 3. Separate wallet-spending authority from read-only wallet access where the service supports it. 4. Add a hardcoded local allowlist for this package, for example: - `beatra.models.list` - `beatra.voices.list` - `beatra.voices.clone` - `beatra.speech.synthesize` - `beatra.assets.upload` - `beatra.tasks.get` - `beatra.tasks.list` - `beatra.tasks.cancel` - Explicitly required wallet-read tools 5. Reject every other tool name before establishing or using an authenticated session. 6. Prefer per-package tokens over a single full-scope credential shared among unrelated Skills. 7. Require explicit user confirmation immediately before wallet-spending or task-cancellation operations. ]]>

other

Warning
Location
scripts/authorize.py:337
Finding
Hostname and Agent-Environment Fingerprinting Is Transmitted as Telemetry<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:337-365`, `scripts/authorize.py:434-443`; `scripts/mcp_client.py:1219-1228`, `scripts/mcp_client.py:1357-1382` **Vulnerability Type**: Environment reconnaissance and persistent 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" 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 ``` ```python if method == "tools/call": arguments = params.get("arguments") if isinstance(arguments, ...[truncated 2645 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not collect or transmit the hostname by default. 2. Make device naming an explicit opt-in field entered by the user rather than deriving it from the operating environment. 3. Default the source platform to a generic value such as `unknown` unless platform telemetry is necessary and the user has consented. 4. Add a persistent telemetry-disable option covering: - Installation registration. - Source package attribution. - Source platform attribution. - Device display names. 5. Clearly disclose every transmitted telemetry field before authorization. 6. Use a rotating, service-scoped pseudonymous identifier instead of a stable cross-package installation reference where feasible. 7. Minimize retention and provide controls to inspect and delete registered-device telemetry. 8. Keep telemetry separate from fields required to complete authentication so that declining telemetry does not block speech generation. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Silent Default Auto-Update Creates a Mutable Remote Code-Execution Channel<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:25-26`, `scripts/mcp_client.py:969-1018`, `scripts/mcp_client.py:1542-1544` **Vulnerability Type**: Remote retrieval and replacement of executable package code **Risk Level**: High ### Vulnerable Code ```python PACKAGE_DISCOVERY_URL = "https://beatra.ai/skills/shift-handoff-voice/channels/clawhub/install.json" PACKAGE_CDN_BASE_TEMPLATE = "https://cdn.beatra.ai/agent-packages/shift-handoff-voice/channels/clawhub/v{version}" ``` ```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"] = observed_at _write_private_json(update_home / "state.json", state) checked = check_update(get_bytes=get_bytes) if not checked["update_available"]: return False _ensure_owned_baseline( install_root=resolved_root, ...[truncated 3273 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default. 2. Permit silent update checks, but require explicit user confirmation before replacing executable files. 3. Sign release manifests with an offline-controlled publisher key and pin the corresponding public key in the audited client. 4. Verify signatures before trusting versions, archive hashes, manifests, or file hashes. 5. Consider transparency-log verification or reproducible package attestations to provide an independently auditable release history. 6. Separate updating from credential-bearing operations: - Run the updater without loading the bearer credential. - Complete updates before opening local user files. - Restart into the verified version only after successful installation. 7. Present the target version, changed executable files, publisher identity, and signature status before installation. 8. Preserve the existing archive-validation, path-safety, ownership, locking, backup, and rollback controls. 9. Provide an enterprise option to pin an exact version and completely disable network update checks. 10. Re-audit updated code before enabling it in security-sensitive environments. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill declares no permissions, yet its instructions require shell execution, file reads/writes, network access, environment use, and package self-modification via the bundled client. This creates a transparency and trust-boundary problem: users and hosting systems are not clearly informed that the skill can access local resources and invoke remote operations.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The stated purpose is narrow audio generation, but the skill also introduces authentication flows, persistent credential storage, remote tool invocation, file upload, telemetry/registration, uninstall behavior, and self-updating package replacement. That mismatch is dangerous because users may authorize a simple voice skill while unknowingly granting a much broader operational footprint with credential, privacy, and supply-chain risk.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill states that the bundled client silently installs newer releases automatically without separate confirmation. Silent self-update is a significant system-modifying behavior and supply-chain risk, especially when not prominently disclosed up front, because code executed under the skill can change after review or user approval.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The document explicitly states that the client performs silent, default-enabled automatic update checks and installs newer versions without separate confirmation. Even though it describes integrity protections such as fixed update origins, checksum verification, rollback, and refusal of redirects, this still grants the vendor unilateral ability to replace installed code automatically, which is a meaningful integrity and supply-chain risk if the update infrastructure, signing process, or release pipeline is ever compromised.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The documentation states that the bundled client automatically performs a network registration call on first use and writes a local cache file, but it does not prominently warn users about this telemetry-like behavior or the filesystem modification. Even if the transmitted fields are described as non-secret and non-billable, undisclosed automatic outbound communication and local persistence can violate user expectations, privacy requirements, or deployment policies in enterprise or regulated environments.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The client performs silent automatic self-updates during normal command execution via maybe_auto_update(), downloading remote manifests and archives and then replacing local package files. Even though the updater includes several integrity and path-safety checks, it still grants remote infrastructure the ability to change executable local code without a user-facing prompt at runtime, which is a material supply-chain and trust-boundary risk for an agent skill.

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
95% confidence
Finding
The package includes a self-update capability that can download and replace its own installed files, which is inherently risky for agent-executed code because it enables runtime self-modification outside normal deployment review. In this skill context, that is more dangerous because the tool may run automatically in user environments and process remote content, so compromise of the update channel or release process could propagate code changes broadly.

Static analysis

No suspicious patterns detected.