Back to skill

Security audit

Civil Affairs Voice Pack

Security checks for vulnerabilities and agentic risk

Overview

This voice skill is mostly coherent, but it asks for broad account and spending authority and silently updates its own code, so it should be reviewed before installation.

Install only if you trust Beatra with a shared local device token and with permissions broader than speech generation, including spending-related authority and other media-generation scopes. Before use, review the authorization page, consider disabling silent updates with the documented update command, and avoid installing this on sensitive workstations where hostname or platform metadata should not be shared. I did not find artifact-backed deception, credential exfiltration, or destructive behavior, so this is Review rather than malicious.

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)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Silent automatic replacement of executable Skill code from a remote release channel<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:520-522`, `scripts/mcp_client.py:969-1015`, `scripts/mcp_client.py:1541-1544` **Vulnerability Type**: Remote payload retrieval and 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"] = observed_at _write_private_json(update_home / "state.json", state) checked = check_update(get_b ...[truncated 3160 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default. Missing or invalid update state should resolve to `"auto_update": false`. 2. Make update checks read-only unless the user explicitly requests installation. 3. Display the proposed version, source, and changed files, and require informed confirmation before replacement. 4. Sign release metadata and archives with a dedicated release key. Verify the signature using an embedded public key independent of the downloaded discovery document. 5. Apply key rotation through a separately authenticated process rather than trusting replacement package content to introduce arbitrary keys. 6. Consider distributing updates through the host platform's reviewed package mechanism instead of implementing mutable self-update behavior. 7. Preserve the existing path, ownership, size, rollback, and checksum defenses as defense-in-depth. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/authorize.py:34
Finding
Speech-focused Skill obtains broad unrelated generation and account privileges<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:34-36`, `scripts/mcp_client.py:1468-1490` **Vulnerability Type**: Excessive authorization scope and unrestricted remote 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}, ) ``` ```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 Skill function is speech synthesis with optional voice cloning and media upload. Legitimate capabilities include speech generation, voice lookup or creation, model discovery, artifact upload, task status access, and narrowly scoped billing information. The authorization request additionally requires image, video, and music generation, general wallet spending, artifact access, and task cancellation. These unrelated privileges are not necessary to create civil-affairs voice clips. The bundled command interface also accepts any tool name supplied on the command line and forwards it to `tools/call` ...[truncated 1403 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define a package-specific OAuth scope set limited to: - speech generation; - voice reading and optional voice creation; - required artifact upload; - model discovery; - task reads for tasks created by this package; - narrowly scoped cancellation when explicitly requested; - read-only balance and ledger operations where required. 2. Remove image, video, and music generation permissions from this Skill. 3. Replace general wallet-spending authority with a capability restricted to explicitly confirmed speech or cloning operations, if the service supports such a scope. 4. Add a strict local allowlist of permissible tool names to `mcp_client.py`; reject every unrecognized or unrelated tool before making a network request. 5. Use package-specific credentials or audience-restricted capability tokens instead of a single full-scope credential shared across all packages. 6. Bind task and artifact permissions to resources created by this package or installation where possible. 7. Show the exact requested permissions on the authorization page so the user can make an informed decision. ]]>

other

Warning
Location
scripts/authorize.py:361
Finding
Authorization helper collects, persists, and transmits the local hostname<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:361-382`, `scripts/authorize.py:466-467`, `scripts/authorize.py:564-568` **Vulnerability Type**: Environment reconnaissance and privacy telemetry **Risk Level**: Medium ### Vulnerable Code ```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] 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", encoding="utf-8", ) except OSError: pass ``` ```python if device_name: form["device_name"] = device_name ``` ```python state_dir = (state_dir or Path.home() / ".beatra").expanduser() _private_directory(state_dir) host_platform = detect_host_platform(platform) device_name = device_display_name() write_host_config(state_dir, platform=host_platform, device_name=device_name) ``` ### Technical Analysis The authorization helper calls `socket.gethostname()`, saves the result in `~/.beatra/host.json`, and includes it as `device_name` in the device-authorization request sent to Beatra. A hostname is not required to perform OAuth device authorization, upload an authorized sample, or synthesize speech. Hostnames can contain employee names, organization names, internal asset nu ...[truncated 1721 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not retrieve or transmit the operating-system hostname by default. 2. Generate a random, non-identifying device alias or use the existing random installation reference as the console label. 3. If a recognizable device label is desired, prompt the user to enter one explicitly and explain that it will be sent to Beatra. 4. Make hostname sharing opt-in and disabled by default. 5. Clearly document every telemetry field, its destination, retention period, and purpose before authorization. 6. Avoid persisting `device_name` in `host.json` unless it is necessary; ensure any retained file is created atomically with restrictive permissions. 7. Provide a command to inspect and delete locally stored telemetry without deleting unrelated credentials. ]]>
Vulnerability Patterns
  • 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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (20)

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill invokes a bundled Python client, performs uploads, reads referenced local files, uses networked MCP/OAuth flows, and supports package-owned file replacement via auto-update, yet no explicit permissions are declared. This creates hidden capability creep: a user expecting simple text-to-speech formatting may unknowingly grant a skill shell, file, and network behaviors that materially expand attack surface and reduce informed consent.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared purpose is narrowly framed as converting a written materials list into voice clips, but the documented behavior includes credential storage, browser-based OAuth, arbitrary MCP tool invocation, local file upload, telemetry/registration, auto-update, and uninstall/revocation logic. This mismatch is dangerous because it conceals privileged behaviors behind an innocuous description, increasing the chance users approve or run the skill without understanding that it can access credentials, network services, and local state.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill includes a self-updating mechanism that automatically downloads and installs newer releases without separate confirmation. Even with stated verification, embedding self-update inside a content-generation skill materially increases supply-chain risk: compromise of the update channel, signing process, or package distribution could turn a low-suspicion voice skill into a code execution vector with file/network access.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The file documents automatic installation registration, external metadata transmission, and local persistence that are unrelated to the advertised civil-affairs voice-clip function. That mismatch is a genuine security concern because hidden or unjustified telemetry expands the trust boundary and can expose deployment metadata without a clear user need or informed consent.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The documented behavior includes environment fingerprinting via platform resolution, host signatures, and stable installation references, which is not justified by a voice-studio workflow. In the context of a content-production skill, collecting and transmitting host/environment identity increases privacy and operational-security risk, especially in sensitive civil-affairs deployments where platform metadata may reveal infrastructure details.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The requested OAuth scope is far broader than the stated purpose of a civil-affairs voice-clip generator. In addition to speech and voice permissions, it asks for artifacts, images, videos, music, task control, and other unrelated capabilities, violating least-privilege and expanding the blast radius if the skill or its credential store is abused.

Context-Inappropriate Capability

Critical
Confidence
100% confidence
Finding
Including `wallet:spend` gives the skill authority to spend user funds even though the skill description only covers turning a materials list into voice clips. That mismatch is especially dangerous because any compromise, misuse, or hidden functionality could directly convert an overbroad token into financial loss.

Description-Behavior Mismatch

High
Confidence
93% confidence
Finding
The client is not narrowly scoped to voice-pack generation; it includes generic remote tool invocation, installation registration, asset upload, and a full self-update mechanism that can replace package files. In a skill whose stated purpose is converting written civil-affairs materials into audio clips, this broad remote-control surface materially increases the chance of misuse, unauthorized behavior expansion, and supply-chain compromise.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
The code fingerprints the hosting agent environment by inspecting environment variables and persists a platform label for later transmission. That data collection is not necessary for the advertised audio-generation purpose, and it increases privacy risk while enabling environment-specific targeting or analytics.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The client maintains persistent installation telemetry and a local inventory of installed skills on every use, which exceeds the expected scope of a voice-clip generation skill. This creates unnecessary tracking data and local state that could reveal user behavior, installation paths, and platform details.

Intent-Code Divergence

Low
Confidence
80% confidence
Finding
The function is described as best-effort registration telemetry, but it also mutates device-local skill inventory state on every use. This mismatch reduces transparency and can conceal broader stateful behavior from users or reviewers, which is dangerous in an agent skill that already contains out-of-scope functionality.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The uninstall script is explicitly designed to manage and potentially revoke a shared Beatra device credential and remove shared state in ~/.beatra. That behavior exceeds the advertised purpose of a civil-affairs voice clip skill and creates cross-skill impact: uninstalling this package can disrupt unrelated installed skills and alter global authentication state.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
This code constructs an authenticated request to a remote revocation endpoint using a bearer token read from local shared state. For a skill whose stated function is generating voice-pack clips, accessing shared credentials and contacting a central authorization service is unnecessary and dangerous because it gives the skill power over account/device access beyond its functional scope.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The script defines shared state files including credentials.json, installation.json, host.json, skills.json, and registrations.json for deletion from ~/.beatra. Even with checks intended to avoid unsafe revocation, this still grants the skill authority to erase global platform state unrelated to its own package, creating denial-of-service and account-management risk for other skills.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The document explicitly states that the client silently checks for and automatically installs updates by default before ordinary commands, without separate confirmation. Even with integrity checks and rollback protections, silent self-modifying behavior increases supply-chain and trust risk because code changes can occur without the user's contemporaneous awareness or approval.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The documentation states that the client automatically performs a registration call on first use and writes a local registrations cache, but it does not clearly present this as a user-facing warning or consent event. Silent transmission and persistence of package, version, platform, and installation-reference metadata can undermine transparency expectations and lead users to unknowingly expose operational metadata.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill can silently auto-update itself and replace installed package files during normal execution without contemporaneous user awareness. Even though the code includes integrity checks, silent self-modification in a skill context creates a substantial supply-chain and trust risk because behavior can change after installation without explicit approval.

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
95% confidence
Finding
The presence of credentials.json in the list of managed shared state indicates this skill is aware of and authorized to remove credential material. In the context of a voice-generation skill, touching credential storage is unjustified and dangerous because it expands the blast radius from content generation to authentication and platform access control.

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
99% confidence
Finding
The _device_token function reads an access token from ~/.beatra/credentials.json, giving the skill direct access to bearer credentials. This is a credential-access pattern with significant risk because compromise, misuse, or unintended invocation could allow unauthorized revocation or other authenticated actions against the Beatra service.

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 feature allows the installed skill package to modify its own codebase. In the context of an agent skill with a narrowly described purpose, self-modification is a strong risk signal because it enables post-installation behavior changes and amplifies supply-chain compromise impact.

Static analysis

No suspicious patterns detected.