Back to skill

Security audit

sleep-story-voice

Security checks for vulnerabilities and agentic risk

Overview

This skill performs the described sleep-story voice workflow, but it also keeps broad account credentials and silently updates executable files, so it needs careful review before installation.

Install only if you are comfortable giving Beatra a shared local credential with spending-capable media permissions and allowing this package to update its own files automatically. Turn off automatic updates with the documented command before routine use if you want review before code changes, use cloning only with clear rights to the voice sample, and revoke the Beatra device from the console if you stop using the skill.

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:969
Finding
Silent unsigned self-update permits remote replacement of executable Skill code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1018` **Vulnerability Type**: Remote payload retrieval and execution **Risk Level**: High ### Technical Analysis The MCP client silently checks for and installs updates before ordinary commands. Automatic updates are enabled when the state file is missing or invalid: ```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 ``` The automatic update routine retrieves mutable discovery metadata and replaces package-owned files without interactive confirmation: ```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_C ...[truncated 3850 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default. Permit automatic checks, but require explicit confirmation before replacing executable files. 2. Digitally sign release metadata with an offline or otherwise independently protected release key. 3. Embed the trusted public key or a pinned root of trust in the audited package and verify signatures before accepting discovery metadata, manifests, or archives. 4. Use a signed metadata framework such as TUF to provide threshold signing, rollback protection, key rotation, and compromise recovery. 5. Do not treat SHA-256 values delivered by the same mutable server as publisher authentication. 6. Display the target version, release identity, changed executable files, and signer before installation. 7. Separate content-only updates from executable script updates; require stronger consent for Python file replacement. 8. Preserve the existing path traversal, archive size, ownership, transactional replacement, and rollback protections. 9. Surface update failures and completed executable updates in an auditable local log without recording credentials or user content. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/authorize.py:35
Finding
Device authorization requests capabilities beyond the declared sleep-story workflow<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:35-39` **Vulnerability Type**: Excessive authorization scope and missing client-side tool allowlist **Risk Level**: Medium ### Technical Analysis The Skill's declared functionality concerns speech synthesis, voice selection or cloning, asset upload, task results, and billing. Nevertheless, the Device Authorization requests generation rights for unrelated media categories: ```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" ) ``` The unrelated scopes include: - `images:generate` - `videos:generate` - `music:generate` The credential also permits financially significant operations through `wallet:spend`, as required for paid speech, and task cancellation through `tasks:cancel`. The direct client does not enforce a package-specific tool allowlist. It accepts any tool name supplied on the command line and sends it using the broad bearer token: ```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}, ) ``` The documentation discloses that one approval covers image, video, music, speech, upload, model, and task tools. Therefore, this is not a hidden permission requ ...[truncated 1473 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Request only the scopes required by this package: - Speech generation. - Voice reading and, only when cloning is requested, voice writing. - Asset upload and artifact reading. - Task reading and cancellation only if cancellation is genuinely supported. - Wallet spending and read-only billing access as required. 2. Remove image, video, and music generation scopes from this Skill. 3. If the service only supports a shared broad credential, introduce package-bound delegated tokens with narrower scopes. 4. Add a client-side allowlist for the tools used by this Skill, such as model listing, voice listing or cloning, speech synthesis, asset upload, task inspection, wallet inspection, and installation registration. 5. Reject arbitrary tool names before sending an MCP request. 6. Consider separate authorization elevation for voice cloning or other uncommon write operations. 7. Ensure authorization pages clearly enumerate financial and destructive permissions, not only media categories. ]]>

other

Note
Location
scripts/authorize.py:363
Finding
Authorization transmits the local hostname and agent-environment fingerprint<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:363-368` **Vulnerability Type**: Unnecessary environment information collection **Risk Level**: Low ### Technical Analysis The authorization helper reads the system hostname: ```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] ``` It also fingerprints the host agent using environment variables, including `CLAUDECODE`, `CLAUDE_CODE_ENTRYPOINT`, keys beginning with `CODEX_`, and `AI_AGENT`. The collected hostname is added to the Device Authorization request alongside the platform, package identity, version, and stable installation reference: ```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) ``` A display name can be useful in a connected-device console, but the operating-system hostname is not necessary for speech synthesis or Device Authorization. Hostnames commonly contain a person's name, employer, department, asset tag, internal naming convention, or project identifier. The platform and stable installation telemetry are documented, but the installation-and-authentication reference does not clearly disclose that the system hostname is transmitted. The behavior is limited reconnaissance rather than broad host enumeration: the code does not collect IP addresses, interfaces, users, processes, or files. ### Attack Path 1. The user runs `scripts/authorize.py`. 2. The hel ...[truncated 973 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not transmit the operating-system hostname by default. 2. Generate a non-identifying local display label, such as “Sleep Story Voice device,” or ask the user to choose a label. 3. Make hostname transmission opt-in and explain where it will appear and how long it will be retained. 4. Clearly disclose the platform, stable installation reference, and device-label telemetry before authorization. 5. Minimize retention and provide a mechanism to delete or rename the registered device. 6. Continue avoiding broader reconnaissance such as IP-address, interface, username, or process collection. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:231
Finding
Server-controlled upload grants may direct user files to any HTTPS host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:231-265` **Vulnerability Type**: Insufficient validation of a server-provided upload destination **Risk Level**: Medium ### Technical Analysis After requesting an upload grant from Beatra, the client validates that the returned URL uses HTTPS but does not constrain its hostname to an expected Beatra-controlled object-storage domain: ```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 a ...[truncated 2081 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Maintain an explicit allowlist of approved upload hostnames or hostname suffixes. 2. Require an exact expected scheme, port, and host pattern for the configured storage provider. 3. Reject IP-literal hosts, loopback addresses, link-local addresses, private network ranges, and unexpected ports. 4. Validate server-supplied header names against an allowlist and reject sensitive headers such as `Authorization`, `Cookie`, `Host`, and proxy-related headers unless specifically required. 5. Bind upload-grant responses cryptographically to the intended artifact request, destination host, content hash, size, MIME type, and expiration. 6. Document any third-party storage providers that legitimately receive uploaded content. 7. Preserve the existing no-redirect policy, regular-file checks, symlink defenses, size limit, and file-stability validation. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (8)

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill declares no permissions, yet its documented behavior requires shell execution, file access, network access, local state storage, and uploads. This is dangerous because it conceals the true trust boundary from users and reviewers, making sensitive operations like credential storage, file inspection/upload, and remote calls occur without transparent consent or capability scoping.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The skill advertises simple sleep-story voice generation but also performs substantially broader actions: OAuth login, bearer token storage, arbitrary Beatra MCP tool invocation, local file upload, installation registration/telemetry, automatic updates, and uninstall/token revocation workflows. This mismatch is dangerous because users may grant trust appropriate for a low-risk content skill while unknowingly enabling account-linked remote operations, data exfiltration paths, and persistent software modification.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The bundled client silently installs updates without separate confirmation, which introduces a remote code and behavior change channel after initial trust is granted. Even if updates are signed and pinned as described, automatic replacement of package-owned files expands supply-chain risk and can change security properties or capabilities without contemporaneous user review.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The document explicitly states that the client silently checks for updates and installs newer releases automatically without separate confirmation. Even though it describes integrity checks and rollback protections, silent self-modifying behavior is security-relevant and should be clearly disclosed because users may not expect software to replace local files during ordinary commands.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The workflow directs the operator to upload a local voice sample and use it for voice cloning and speech generation, but it does not require any explicit privacy notice, confirmation of data-transfer implications, retention terms, or consent record beyond a general authorization statement. Because voice samples are biometric and highly sensitive, omission of clear user-facing warnings and consent handling can lead to unauthorized processing, privacy violations, and downstream misuse of cloned voices.

Credential Access

High
Category
Privilege Escalation
Content
},
  "mcp": {
    "authentication": "device-bearer",
    "credential_file": "~/.beatra/credentials.json",
    "name": "beatra",
    "transport": "streamable-http",
    "url": "https://mcp.beatra.ai/mcp"
Confidence
96% confidence
Finding
The manifest explicitly points the skill to a local bearer-token credential file, which indicates the skill can operate using stored account credentials against a remote MCP endpoint. If the skill or downstream instructions can cause unintended MCP actions, those actions would execute under the user's authenticated identity, expanding the blast radius from simple content generation to account-level API access.

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
68% confidence
Finding
The script persists a high-privilege bearer access token in plaintext JSON on disk under ~/.beatra/credentials.json. Although the code applies restrictive POSIX permissions, the token grants broad scopes including artifacts, media generation, and wallet spending, so compromise of the local account, backup, or insecure Windows ACL inheritance could expose reusable credentials.

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
80% confidence
Finding
This client includes a self-update mechanism that downloads code and replaces files in the local installation. Although it has several integrity checks, self-modifying package behavior materially expands the trust boundary: compromise of the discovery/manifest signing pipeline, CDN origin, or publisher infrastructure would let an attacker deliver new code into the agent environment, which is especially sensitive for a skill capable of networked MCP actions and handling credentials.

Static analysis

No suspicious patterns detected.