Back to skill

Security audit

IVR Voice Pack

Security checks for vulnerabilities and agentic risk

Overview

The voice-pack workflow is legitimate, but the skill requests broad account and spending permissions and silently self-updates local code by default, so it needs Review before installation.

Install only if you are comfortable granting Beatra a shared device credential that can spend credits and access more media tools than this IVR workflow requires. Consider disabling silent updates with python3 scripts/mcp_client.py update --auto off, using a low-balance or separate account, and only uploading voice samples when you have explicit speaker consent.

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
Authorization Requests Permissions Beyond the IVR Workflow's Requirements<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:31-34` **Vulnerability Type**: Excessive OAuth scope and violation of least privilege **Risk Level**: High ### Evidence ```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" ) ``` ### Technical Analysis The Device Authorization flow requests a single bearer credential with permissions for image generation, video generation, music generation, wallet spending, artifact access, task cancellation, speech generation, and voice management. The declared IVR voice-pack workflow legitimately requires speech generation, voice discovery or cloning, task-result access, and—in the cloning workflow—artifact upload. Image, video, and music generation are unrelated to IVR prompt production. General wallet-spending authority and unrestricted task cancellation also exceed the narrowest privileges needed for the normal workflow. Because the bearer token is shared by the bundled MCP client, any code capable of reading the credential or issuing an authenticated MCP request inherits all granted scopes. The credential is therefore more valuable and damaging if compromised than a task-specific token would be. ### Attack Path 1. The user runs `scripts/authorize.py`. 2. The authorization page requests the complete scope set defined in `SCOPE`. 3. After approval, the broad bearer token is stored in `~/.beatra/credentials.json`. 4. An attacker compromises the package, a future package update, or another process running as the same user. 5. The attacker reads or uses the bearer credential through the client. 6. The attacker invokes unrelated image, video, or music generation operations, spends wallet credits, reads accessible artifacts or tasks, or cancels tasks. No direct token theft mechanism was found in the audited code; exploitation depends on compromise of a ...[truncated 503 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the broad scope set with a least-privilege set limited to the operations required by this Skill. 2. Remove `images:generate`, `videos:generate`, and `music:generate`. 3. Avoid a general `wallet:spend` permission where the service can authorize only speech or voice-cloning purchases. 4. Request `tasks:cancel` only when the user explicitly initiates a cancellation workflow, preferably through incremental authorization. 5. Restrict artifact permissions to upload and read operations associated with this package's own voice samples and outputs. 6. Use package-scoped or operation-scoped tokens where supported. 7. Display the requested permissions and their purposes before opening the authorization page. 8. Add automated tests that fail if unrelated scopes are added to the IVR package. ]]>

other

Note
Location
scripts/authorize.py:339
Finding
Authorization Collects and Transmits Hostname and Agent-Environment Telemetry<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:339-447` **Vulnerability Type**: Environment reconnaissance and device-identifying telemetry **Risk Level**: Low ### Evidence ```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] def write_host_config(state_dir: Path, *, platform: str, device_name: str | None) -> None: """Persist detection results so mcp_client never re-detects per request and still has a truth source when its own env detection comes up empty. Best-effort: config failure must never block authorization.""" 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", ...[truncated 2653 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not collect or transmit the hostname by default. 2. Generate a random, non-identifying device alias or ask the user to choose a display name. 3. Make installation and source-attribution telemetry opt-in. 4. Clearly disclose all transmitted metadata before opening the authorization page. 5. Provide independent controls for hostname, platform attribution, and installation registration. 6. Retain only the minimum telemetry necessary for authentication and package operation. 7. Apply restrictive permissions to `host.json` consistently; use the existing private atomic-write helper rather than direct `write_text`. 8. Document server-side retention, correlation, and deletion policies for these identifiers. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Ordinary Commands Silently Retrieve and Install Updated Executable Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1018` **Vulnerability Type**: Default-enabled remote code update channel **Risk Level**: High ### Evidence ```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, update_home=update_home, get_bytes=get_bytes, ) discovery = checked["discovery"] manifest, new_files = download_update(discovery, get_bytes=get_bytes) _apply_update( install_root=resolved_root, update_home=update_home, ...[truncated 3565 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable silent automatic updates by default and require explicit user opt-in. 2. Require confirmation before replacing executable files, showing the current version, target version, source, and release identity. 3. Verify release metadata using a public key pinned in the audited package and signatures produced by an isolated release-signing system. 4. Use threshold signing or a transparency log so compromise of one publisher system cannot silently authorize a release. 5. Pin and validate the expected certificate or signing identity where operationally appropriate. 6. Separate update checking from update installation; an ordinary creative command should not modify executable code. 7. Preserve a user-accessible audit log of update checks, downloaded versions, verified signatures, and replaced files. 8. Consider requiring re-review or reauthorization when an update changes executable scripts or requested OAuth scopes. 9. Keep the existing archive, path, ownership, size, checksum, downgrade, rollback, and redirect protections as defense-in-depth controls. ]]>
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 (21)

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill declares no permissions while instructing use of a bundled Python client that can read and write files, access environment data, execute shell commands, and make network requests. This under-declaration prevents informed consent and hides meaningful execution capabilities from reviewers and users, increasing the chance that sensitive local data, credentials, or package files are accessed unexpectedly.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The stated purpose is narrow IVR prompt generation, but the skill also performs authentication flows, persistent credential storage, arbitrary file upload, telemetry/registration, uninstall cleanup, and package self-update. That mismatch is dangerous because users may authorize a simple media-generation skill without realizing it can modify the local installation, persist tokens, and send data to remote services.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The skill contains a self-updating mechanism unrelated to the core task of generating IVR voice packs. Any self-update path expands the trusted computing base and creates a supply-chain risk: if the update mechanism, signing process, or distribution channel is compromised, the skill can replace local files and change behavior without the user intending to run an installer.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The documented scope is IVR prompt creation, but the file also defines package replacement and update behavior. Even with integrity checks described, embedding software maintenance logic inside a content-generation skill violates least surprise and increases the blast radius from a prompt-generation tool to a package-management tool.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The requested OAuth scope bundle is far broader than the skill’s stated purpose of producing IVR voice prompts. In addition to speech and voice access, it requests images, videos, music, artifact/task control, and wallet spending, so a compromised or misused skill credential could be used for unrelated actions and monetary spend far outside user expectations.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The authorization flow explicitly includes spending and cross-media generation privileges unrelated to an IVR voice-pack skill. This creates unnecessary blast radius: if the token is abused, it can trigger billable operations and access features users did not intend to grant when installing a voice-menu package.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The client contains extensive self-update and installation-management logic unrelated to an IVR voice-pack skill, including network discovery, package download, archive validation, and replacement of local files. Even with several integrity checks present, embedding a self-modifying updater in a content-generation skill materially expands the attack surface and allows remote code changes on the host outside the user's immediate task scope.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The skill collects host/platform identity, installation references, and registration telemetry that are not necessary to generate or upload IVR prompts. For an audio-packaging skill, this extra device and installation tracking increases privacy risk and broadens data exposure to the remote service without a clear functional need.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The code fingerprints the execution environment using environment variables and host.json to classify the agent platform. In the context of an IVR voice-pack skill, this capability is unjustified and can be used for environment profiling, selective behavior, or telemetry enrichment unrelated to the stated purpose.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The skill writes and maintains a local inventory of installed skills and paths in ~/.beatra/skills.json, which exceeds the needs of generating IVR prompts. This creates unnecessary local surveillance/statefulness and could expose software inventory information or support broader package-management behavior from a nominally narrow-purpose skill.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
This uninstall script handles shared Beatra device credentials and makes decisions about revoking a shared authorization, which is unrelated to the advertised IVR voice-pack functionality. In the context of a content/voice-pack skill, bundling credential and shared connection lifecycle logic expands privilege unnecessarily and could disable other installed skills or affect account access if invoked by an agent or user who does not expect such behavior.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The code performs a remote OAuth/device token revocation request to Beatra services, a sensitive account-affecting action not justified by an IVR prompt creation skill. Even though the code tries to avoid revoking credentials when other skills remain, this still grants the skill package authority to alter shared authentication state and can cause denial of service or unexpected account/session disruption if the inventory is wrong or manipulated.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The script defines deletion of shared local Beatra state files including credentials, inventory, and registration data under ~/.beatra, which affects more than this skill's own package state. For an IVR voice-pack skill, touching shared platform state is over-privileged and dangerous because a mistaken or unexpected uninstall can break other skills or erase authentication and device metadata relied on by the broader system.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
Automatic update checks and installation are enabled by default without prominent up-front disclosure in the skill description. Silent default updates reduce user control and can introduce unreviewed code changes into an environment that may have access to credentials, files, and network resources.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
The skill hardcodes an update channel/package/locale path without presenting meaningful user choice or documenting opt-in. While not inherently malicious, this constrains trust decisions and can funnel all installs to a predefined update source, making policy review and user control weaker.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The documentation states that the client silently checks for and automatically installs updates before normal commands, without separate confirmation. Even though the text also describes strong integrity controls, silent system-modifying behavior changes the installed software without explicit user awareness at the time of change, which is a real security and trust concern because it expands the attack surface and can surprise users in sensitive environments.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The document states that the bundled client automatically makes a network registration call on first use and writes a local cache file, but it does not give an explicit user-facing warning or consent note. In a skill context, this creates a transparency and privacy problem because users may not expect telemetry-like transmission or persistent local state when invoking a creative voice-pack tool.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
maybe_auto_update() performs silent network checks and can download and apply package updates during normal command execution, explicitly designed to never block the requested command. For a user invoking an IVR tool, this means code can change in the background without contemporaneous notice or approval, increasing the risk of stealthy supply-chain compromise or unexpected behavior changes.

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
94% confidence
Finding
Referencing credentials.json as part of files targeted for deletion shows the skill is designed to manipulate shared credential material. In this skill context, access to and deletion of credential-bearing files is unnecessary for IVR voice-pack functionality and increases the risk of account/session disruption or abuse if the script is triggered unexpectedly.

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
96% confidence
Finding
The _device_token function reads an access token directly from ~/.beatra/credentials.json so the skill can use it for revocation. Direct token access by a voice-pack skill is over-privileged and dangerous because it enables account-affecting API calls and creates a path for credential misuse if the package is modified, compromised, or invoked in an unexpected environment.

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 exposed self-update command enables the package to modify its own installed code and files on disk. Self-modification is especially risky in a narrow-purpose IVR skill because it creates a path for remote content to become executable local code, magnifying any compromise of the update channel or backend trust assumptions.

Static analysis

No suspicious patterns detected.