Back to skill

Security audit

Homeroom Week Voice Pack

Security checks for vulnerabilities and agentic risk

Overview

This voice-generation skill is not clearly malicious, but it asks for broad Beatra account authority and can silently replace its own files, so it should be reviewed before installation.

Install only if you are comfortable giving this package a shared Beatra device credential with broad media, artifact, task, and spending authority, and with automatic package updates enabled by default. Consider disabling auto-update with the documented command, using it only on a trusted single-user device, and reviewing Beatra account permissions and console revocation options before authorizing.

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:957
Finding
Silent Self-Update Allows Remote Replacement of Executable Skill Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:957-1019`, with update endpoints defined at `scripts/mcp_client.py:31-32` and automatic invocation at `scripts/mcp_client.py:1535-1537` **Vulnerability Type**: Remote payload retrieval and execution without an independent publisher trust anchor **Risk Level**: High ### Vulnerable Code ```python PACKAGE_DISCOVERY_URL = "https://beatra.ai/skills/homeroom-week-voice/channels/clawhub/install.json" PACKAGE_CDN_BASE_TEMPLATE = "https://cdn.beatra.ai/agent-packages/homeroom-week-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 ...[truncated 3232 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default. Update checks may remain opt-in or informational, but executable replacement should require informed user confirmation. 2. Sign discovery metadata or release manifests using a dedicated offline publisher key. 3. Embed or securely provision the corresponding public verification key in the audited client. 4. Verify a detached signature before accepting the manifest, archive hash, version, or file list. 5. Consider a signed metadata framework such as TUF to provide key rotation, rollback protection, threshold signatures, and repository-compromise resilience. 6. Display the current version, proposed version, release identity, and changed executable files before installation. 7. Preserve the existing archive limits, path validation, ownership restrictions, lock, rollback journal, and downgrade protection as defense-in-depth. 8. Ensure update failures are observable through an audit log rather than being completely suppressed, while still preventing automatic replay of paid calls. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/authorize.py:35
Finding
Device Authorization Requests Privileges Beyond the Voice-Pack Workflow<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:35-38`, with unrestricted tool selection at `scripts/mcp_client.py:1474-1511` **Vulnerability Type**: Excessive OAuth scope and unrestricted generic 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}, ) ``` ```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 functionality requires text-to-speech generation, optional authorized voice cloning, voice/model discovery, asset upload, task status retrieval, and limited wallet reads or spending needed for approved generation. The authorization request additionally grants capabilities for: - Image generation. - Video generation. - Music generation. - General artifact writes. - Task cancellation. - Broad wallet spending. These capabilities are not necessary to turn a homeroom plan into voice clips. The bundled client also accepts any tool name supplied on the command line and does not e ...[truncated 1533 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the shared full-scope token with a package-specific, least-privilege credential. 2. Restrict authorization to the exact capabilities required by this Skill, such as speech generation, authorized voice operations, asset upload, model/voice reads, and task reads. 3. Remove image, video, and music generation scopes from this package. 4. Remove task cancellation unless the declared workflow requires it and the user explicitly requests cancellation. 5. Separate read-only wallet access from spending authority where supported. 6. Add a strict local allowlist of tool names, for example: - `beatra.models.list` - `beatra.voices.list` - `beatra.voices.clone` - `beatra.speech.synthesize` - `beatra.assets.upload` - `beatra.tasks.get` - `beatra.tasks.list` - Required wallet read methods 7. Enforce argument schemas locally for sensitive or billable operations. 8. Clearly disclose every requested permission during authorization, particularly wallet spending and task cancellation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:1044
Finding
Windows Credential Confidentiality Is Assumed but Not Enforced<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:122-137` and `scripts/mcp_client.py:1044-1052` **Vulnerability Type**: Missing Windows ACL creation and validation for a broad bearer credential **Risk Level**: Medium ### Vulnerable Code ```python def _private_directory(path: Path) -> None: # POSIX gets explicit 700/600. On Windows the state directory lives under # the user profile, whose default ACL is already private to the user — # the same posture as gh/aws/gcloud credential stores. The former custom # DACL ceremony was dropped deliberately: its command patterns read as # hostile to agent safety policies and endpoint security, failing installs # while adding no protection an elevated administrator could not bypass. path.mkdir(mode=0o700, parents=True, exist_ok=True) if os.name == "posix": path.chmod(0o700) def _restrict_file(path: Path) -> None: if os.name == "posix": path.chmod(0o600) ``` ```python def _read_private_credentials(state_dir: Path, path: Path) -> str: if os.name == "nt": # The state directory lives under the user profile, whose default # ACL is already private to the user (the gh/aws/gcloud posture). # The former custom DACL verification was dropped deliberately: its # command patterns read as hostile to agent safety policies and # endpoint security, failing installs while adding nothing an # elevated administrator could not bypass. return path.read_text(encoding="utf-8") ``` ### Technical Analysis On POSIX systems, the client creates the state directory with mode `0700`, creates the credential file with mode `0600`, verifies ownership, rejects non-regular files, and uses `O_NOFOLLOW` when available. On Windows, the implementation neither creates an owner-only discretionary access control list nor verifies the effective ACL before reading the bearer token. It relies entirely on inherited permissions f ...[truncated 1287 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the token in Windows Credential Manager or another operating-system-backed secret store instead of a plaintext JSON file. 2. If a file remains necessary, create an explicit owner-only DACL using a safe native Windows API. 3. Disable permission inheritance where required and grant access only to the current user and mandatory system principals. 4. Validate the file owner and effective ACL before every credential read. 5. Reject credentials readable by broad groups such as `Everyone`, `Users`, or `Authenticated Users`. 6. Fail closed and require reauthorization when secure permissions cannot be established. 7. Add Windows-specific tests covering migrated profiles, inherited group permissions, network-backed profiles, and ACL modification after authorization. 8. Update the documentation so its confidentiality guarantee matches the implemented enforcement. ]]>

other

Note
Location
scripts/authorize.py:347
Finding
Authorization Collects and Transmits Optional Hostname and Agent-Environment Metadata<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:347-370` and `scripts/authorize.py:446-457` **Vulnerability Type**: Unnecessary device fingerprinting and environment 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_AUTHORIZATION_URL, form) ``` The MCP client also adds platform attribution to business calls: ```python if method == "tools/call": arguments ...[truncated 2016 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not collect or transmit the hostname by default. 2. Use a random installation identifier or a user-selected display label when a recognizable console name is needed. 3. Obtain explicit opt-in consent before sending a real hostname. 4. Clearly disclose every transmitted telemetry field, its purpose, retention period, and correlation behavior. 5. Minimize platform attribution to coarse values and transmit it only where operationally necessary. 6. Provide a command-line option such as `--device-name` so users can choose a non-sensitive label. 7. Provide a telemetry-disable option that does not prevent the core speech workflow. 8. Avoid persisting the hostname in `~/.beatra/host.json` unless the user has opted in. ]]>
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 (22)

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill exposes broad capabilities—environment access, file read/write, network, and shell—without declaring them, which prevents users and reviewers from understanding the true execution surface. In context, the skill instructs use of a bundled Python client, local file handling, network calls, and local state changes, so the undeclared capability set materially increases the risk of hidden data access, command execution, or exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The advertised purpose is simple voice-pack generation, but the skill also performs authentication flows, persistent credential storage, arbitrary MCP tool invocation, file upload, telemetry/registration, uninstall-side credential actions, and automatic software update/install. That mismatch is dangerous because users may authorize a seemingly narrow content-generation skill without realizing it can modify local state, communicate broadly over the network, and change itself over time.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill includes an automatic self-update/install mechanism unrelated to its core task, creating a software supply-chain risk inside a content-generation workflow. Even with signature and manifest verification described, silent code replacement expands the trust boundary and enables future behavior changes without a fresh user review or explicit approval at the time of change.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The changelog mentions top-up tiers, prices, addresses, balances, and ledger calls, which are unrelated to a homeroom voice-pack skill. This mismatch is a strong indicator of hidden or repurposed functionality and can mislead reviewers and users about the skill’s real capabilities, especially when paired with external MCP access.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
Financial/account capabilities implied by the changelog are not justified by the stated purpose of converting weekly plans into voice clips. Unnecessary balance or ledger features expand the attack surface toward account probing, payment abuse, or deceptive monetization under an education-themed facade.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The requested OAuth scope is far broader than the skill’s stated purpose of generating homeroom week voice clips. It includes unrelated capabilities such as artifacts write/read, image/video/music generation, voice management, task control, and wallet spending, so a compromised or buggy skill using this credential could perform actions far outside user expectations.

Context-Inappropriate Capability

Critical
Confidence
99% confidence
Finding
The authorization scope includes images:generate, videos:generate, and music:generate even though the skill is described as voice-only. This mismatch increases blast radius substantially: the same bearer token could be reused to generate unrelated content or incur additional service usage beyond what a teacher would expect from a homeroom audio tool.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The authorization scope includes images:generate, videos:generate, and music:generate even though the skill is described as voice-only. This mismatch increases blast radius substantially: the same bearer token could be reused to generate unrelated content or incur additional service usage beyond what a teacher would expect from a homeroom audio tool.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
The script implements a full self-update mechanism that downloads, verifies, and replaces package files on disk, even though the declared skill purpose is homeroom voice-pack generation. This materially expands the trust boundary: any compromise of the vendor update channel, signing/checksum publication path, or package distribution process becomes code execution inside the user's environment.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The code performs installation telemetry and writes a local skill inventory unrelated to generating voice clips from lesson plans. While not directly a code-execution issue, it collects and persists environment metadata beyond the stated function, increasing privacy risk and normalizing undisclosed tracking behavior in a narrowly scoped classroom-oriented skill.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The skill presents as a focused voice-pack generator, but the implementation exposes a general MCP client capable of listing tools, arbitrarily calling remote tools, uploading local files, and auto-updating itself. This mismatch is dangerous because users or reviewing systems may grant trust based on a narrow educational purpose while the code actually enables broad remote interaction and data movement.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
This uninstall script handles shared device credential lifecycle and can remove shared local auth state, which is outside the declared purpose of a classroom voice-pack generation skill. Even if intended as package cleanup, that mismatch expands the skill's privilege scope and creates a supply-chain risk: installing a seemingly harmless content skill also grants it influence over global Beatra authentication state.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The code performs a network POST to revoke a shared device authorization token, a high-impact capability unrelated to generating homeroom audio clips. If abused or triggered unexpectedly, it can disconnect other installed skills from the shared Beatra account, causing denial of service across the device and giving this skill undue control over account connectivity.

Missing User Warnings

Low
Confidence
88% confidence
Finding
The skill states that updates install automatically without separate confirmation, but this is not surfaced as a prominent warning in the user-facing description. Hidden or low-visibility silent-update behavior undermines informed consent and can lead users to run code that changes over time with capabilities broader than expected.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The document explicitly describes silent, default-enabled automatic updates that perform network checks and replace installed files without separate confirmation. Even with integrity checks and rollback protections, this behavior materially changes user software and creates a supply-chain and consent risk because code can be modified automatically without an explicit per-update user action or prominent warning.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The document states that the bundled client will automatically make a network registration call on first use and write a local cache file, but it does not present this behavior as an explicit user-facing warning or consent requirement. Even if the data is described as non-secret and non-billable, this is still telemetry-like behavior and filesystem modification that can violate user expectations, enterprise policy, or privacy requirements when performed silently.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
maybe_auto_update() silently checks for and applies package updates during normal command execution without user-facing notice or confirmation. Even with checksum validation, silent code modification is risky in an educational voice skill because it can change behavior, permissions, or network interactions outside user expectations and complicates review and incident response.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The script records local inventory and attempts registration telemetry on ordinary use as a best-effort background action without user-facing disclosure. In a classroom-oriented skill, undisclosed metadata collection is more concerning because deployment may involve student or school-managed environments with heightened privacy and procurement expectations.

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
91% confidence
Finding
The manifest declares use of a local credential file for device-bearer authentication to an external MCP endpoint. Referencing host credentials is sensitive because any over-broad MCP tool access can let the skill act with the user’s existing account privileges, and in the context of an educational voice skill this access is broader than users would reasonably expect.

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
88% confidence
Finding
Referencing and deleting credentials.json indicates the skill is designed to operate on shared credential material in ~/.beatra. Although this instance is not exfiltrating the secret, unnecessary access to shared credentials by a low-scope classroom content skill violates least privilege and increases the blast radius if the package is modified or compromised.

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
93% confidence
Finding
The _device_token function reads an access token from credentials.json so it can be used in a bearer-authenticated revocation request. Reading raw shared access tokens inside a skill package is dangerous because any compromise of this skill or future code changes could turn that access into credential theft, unauthorized API use, or cross-skill account disruption.

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
96% confidence
Finding
The exposed self-update command gives the package an explicit self-modification path, allowing installed code to be replaced from a remote source. For a skill whose stated role is generating weekly homeroom voice clips, self-modifying behavior is unjustified and significantly raises supply-chain and persistence risk.

Static analysis

No suspicious patterns detected.