Back to skill

Security audit

Earnings Script Reads

Security checks for vulnerabilities and agentic risk

Overview

The skill can make earnings-script audio, but it also requests broad Beatra account authority and silently self-updates installed code, so it belongs in Review rather than automatic install.

Review this before installing in any sensitive or managed environment. Install only if you are comfortable with a shared Beatra device token that has broad media, task, artifact, voice, and wallet-related authority, plus default silent package updates. Consider disabling automatic updates with `python3 scripts/mcp_client.py update --auto off`, and upload only files you intentionally want sent to Beatra as voice or media samples.

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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/authorize.py:33
Finding
Overprivileged Shared Credential and Unrestricted MCP Tool Dispatch<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:33-37`; `scripts/mcp_client.py:1448-1474`; `references/mcp-connection.md:8-10` **Vulnerability Type**: Excessive authorization scope and missing tool allowlist **Risk Level**: High ### Vulnerable Code Authorization requests capabilities unrelated to earnings-script narration: ```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 generic command accepts an arbitrary MCP tool name: ```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 CLI places no restriction on that tool name: ```python call = subparsers.add_parser("call", help="Call one tool with a JSON object on stdin") call.add_argument("tool_name") ``` The documented credential model confirms that the token is shared and full-scope: ```text They share the one full-scope Device Token stored in `~/.beatra/credentials.json`. ``` ### Technical Analysis The declared function requires text-to-speech, optional voice cloning, authorized media upload, model discovery, task monitoring, and limited billing information. The requested credential additionally permits image, video, and music gen ...[truncated 1710 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the shared full-scope credential with a package-specific or capability-scoped token. 2. Request only the operations needed by this Skill: - Text-to-speech model and voice discovery. - Speech synthesis. - Optional voice cloning when explicitly requested. - Authorized asset upload. - Task creation/read access for tasks created by this package. - Read-only wallet operations when requested. 3. Remove unrelated image, video, and music generation scopes. 4. Separate wallet-read access from wallet spending, and avoid granting spending as a general scope where per-operation authorization is possible. 5. Add a strict local allowlist of MCP tool names and reject every other name before establishing the authenticated session. 6. Scope task and artifact access to package-created resources where the service supports resource-level authorization. 7. Use distinct credentials between packages so compromise of one Skill cannot affect every Beatra Skill installed for the user. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:1530
Finding
Silent Retrieval and Replacement of Executable Package Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:31-32`, `scripts/mcp_client.py:917-1019`, `scripts/mcp_client.py:1530-1532`; `SKILL.md:180-200` **Vulnerability Type**: Automatic remote code update without independent signature verification or per-update confirmation **Risk Level**: High ### Vulnerable Code The package embeds remote discovery and CDN locations: ```python PACKAGE_DISCOVERY_URL = "https://beatra.ai/skills/earnings-script-read/channels/clawhub/install.json" PACKAGE_CDN_BASE_TEMPLATE = "https://cdn.beatra.ai/agent-packages/earnings-script-read/channels/clawhub/v{version}" ``` An update is downloaded and written into the installed package, including executable Python files: ```python manifest, new_files = download_update(discovery, get_bytes=get_bytes) _apply_update( install_root=resolved_root, update_home=update_home, discovery=discovery, manifest=manifest, new_files=new_files, ) ``` Ordinary commands invoke the automatic updater before performing the requested operation: ```python else: maybe_auto_update() ``` The documented behavior explicitly enables installation without separate confirmation: ```text When a newer version is available, it installs automatically without separate confirmation. ``` ### Technical Analysis The updater includes meaningful defensive controls: HTTPS is required, redirects are refused, package/channel/locale values are checked, downgrades are rejected, file sizes and hashes are verified, archive paths are validated, and replacement uses backups and rollback. Those controls protect against corruption, path traversal, and some network redirection attacks. They do not establish publisher authenticity independently of the update infrastructure. The discovery response identifies the expected manifest and archive hashes, while no pinned public signing key or detached digital signature is verified. An actor controlling the official discovery and release infrastruct ...[truncated 1606 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default. 2. Require explicit, informed confirmation before replacing executable package files. 3. Sign release metadata and archives with an offline-protected publisher key. 4. Embed or securely provision the corresponding verification key independently of the discovery service. 5. Verify a signature over the package identity, channel, locale, version, manifest digest, and archive digest. 6. Preserve the existing checksum, path, size, downgrade, rollback, and destination-validation controls. 7. Separate update execution from credential-bearing business operations wherever possible. 8. Display the target version and release provenance before installation. 9. Support administrator-managed version pinning and an enterprise policy that completely disables self-updating. ]]>

other

Warning
Location
scripts/authorize.py:362
Finding
Collection and Transmission of Hostname and Agent-Environment Metadata<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:340-380`, `scripts/authorize.py:455-467`, `scripts/authorize.py:567-568` **Vulnerability Type**: Privacy-invasive host reconnaissance and telemetry **Risk Level**: Medium ### Vulnerable Code The authorization helper inspects process-environment signatures: ```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" ``` It obtains the local 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] ``` The hostname is persisted locally: ```python def write_host_config(state_dir: Path, *, platform: str, device_name: str | None) -> None: 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 2392 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make hostname collection opt-in and disabled by default. 2. Use a generic user-editable label such as `Beatra device` instead of automatically reading the operating-system hostname. 3. Clearly disclose every field sent during authorization, including hostname, platform, package identity, version, and stable installation reference. 4. Request consent before transmitting locally identifying device metadata. 5. Prefer an explicit `--platform` value or a neutral `unknown` value over environment inspection where platform attribution is not required. 6. Store `host.json` using the same atomic private-file helper used for other state files. 7. Provide a configuration option to disable installation and source-attribution telemetry without disabling core speech functionality. 8. Define retention and deletion behavior for server-side device metadata. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:236
Finding
Server-Supplied Upload URL Is Not Restricted to Trusted Storage Hosts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:236-266`, `scripts/mcp_client.py:1412-1446` **Vulnerability Type**: Insufficient validation of a remote upload destination **Risk Level**: Medium ### Vulnerable Code The client accepts any HTTPS hostname returned in the upload instruction: ```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 an invalid response") return {"type": "artifact", "artifact_id": artifact_id} ``` The sele ...[truncated 3206 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Allowlist exact Beatra-controlled upload hosts or documented cloud-storage host patterns. 2. Reject IP-literal destinations, localhost, private/link-local ranges, unexpected ports, and nonstandard URL components. 3. Restrict server-provided headers to a documented allowlist required by the storage provider. 4. Cryptographically bind an upload grant to the expected hostname, path, MIME type, content length, and expiration. 5. Show the destination organization or hostname before uploading sensitive voice samples. 6. Preserve the existing redirect rejection, HTTPS requirement, regular-file validation, size limit, and time-of-check/time-of-use protections. 7. Avoid retaining the complete file in memory longer than necessary and explicitly discard buffers after upload where practical. ]]>
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 (16)

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill declares a narrow business purpose but documents capabilities spanning shell, network, file read/write, environment access, and remote tool invocation without an explicit permission boundary. That creates a broad attack surface: a user invoking a seemingly simple narration skill could trigger credential handling, file access, uploads, or network actions beyond what the manifest implies.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
This is a true description-behavior mismatch: the stated purpose is generating earnings-script voice clips, but the skill also covers OAuth login, persistent credential storage, generic remote MCP tool invocation, local uploads, registration/telemetry, uninstall/token revocation, and self-updating. Hidden or under-disclosed behavior is dangerous because users and orchestrators may grant trust to a low-risk content skill while it actually performs materially broader and more sensitive operations.

Description-Behavior Mismatch

Medium
Confidence
83% confidence
Finding
The skill expands from content conversion into wallet/billing interactions and update management that are not necessary for the core task and are not clearly surfaced by the manifest. This scope creep increases the chance of unexpected financial actions, exposure of account metadata, and user confusion about what the skill is authorized to do.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
Automatic download and in-place installation of updates is highly sensitive functionality, especially in a skill whose advertised role is just producing voice clips. Even with claimed verification, self-update materially changes the local codebase and can be abused through supply-chain compromise, discovery-channel compromise, or validation flaws, resulting in arbitrary code execution or persistence.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The authorization flow requests a very broad OAuth scope set, including artifacts, images, videos, music, speech, voice management, wallet spending, and task operations, while the skill is described as producing spoken earnings-script reads. This violates least-privilege and creates unnecessary blast radius if the credential is misused, leaked, or if the skill later invokes unrelated APIs.

Context-Inappropriate Capability

Critical
Confidence
96% confidence
Finding
The skill asks for voice-management and task-cancellation permissions beyond what appears necessary for generating one-section voice clips from prepared remarks. These permissions could let the holder inspect or modify voice resources and interfere with unrelated user jobs, which exceeds user expectations for this skill.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
The skill asks for voice-management and task-cancellation permissions beyond what appears necessary for generating one-section voice clips from prepared remarks. These permissions could let the holder inspect or modify voice resources and interfere with unrelated user jobs, which exceeds user expectations for this skill.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The skill asks for voice-management and task-cancellation permissions beyond what appears necessary for generating one-section voice clips from prepared remarks. These permissions could let the holder inspect or modify voice resources and interfere with unrelated user jobs, which exceeds user expectations for this skill.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The client contains extensive self-update and package-installation logic unrelated to turning earnings scripts into voice clips. In a skill whose expected behavior is narrow and content-focused, code that downloads, validates, and replaces local package files materially expands the attack surface and enables remote modification of the installed codebase if the update channel, signing assumptions, or vendor infrastructure are ever compromised.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The code fingerprints the host environment via environment variables and local host metadata, then transmits installation telemetry such as platform, package version, installation reference, and install path inventory. That collection is not necessary for generating spoken earnings-script sections, so it creates avoidable privacy and tracking risk and broadens data exposure beyond the stated skill purpose.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The document states that the client silently checks for updates and installs higher versions automatically without separate confirmation. Even with integrity checks and fixed update sources, this is an integrity-impacting default because software behavior can change without an explicit user action at update time, increasing supply-chain and unexpected-change risk for users operating paid or production workflows.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The document describes an automatic registration call that transmits package and environment metadata and writes a local cache file on first use, but it does not clearly warn users or require explicit consent. Even if the data is described as non-secret and non-billable, silent telemetry and filesystem writes can violate user expectations, privacy requirements, or organizational policy, especially in enterprise or regulated environments.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
maybe_auto_update performs a silent best-effort update before normal commands, changing installed package files without a contemporaneous user warning or confirmation. Even with checksum and manifest checks, silently modifying executable code at runtime undermines user trust, complicates review, and increases the impact of any compromise of the vendor-controlled update path.

Missing User Warnings

Low
Confidence
84% confidence
Finding
register_installation automatically sends installation metadata as background telemetry and suppresses errors so the behavior stays unobtrusive. While not directly enabling code execution, undisclosed telemetry is a security and privacy concern because it transmits environment-linked identifiers without a user-facing consent point in a skill that does not need such reporting to perform its core function.

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
90% confidence
Finding
The uninstall script reads ~/.beatra/credentials.json, extracts an access token, and uses it to authenticate a remote revocation request. Even though the apparent purpose is cleanup, this is direct access to shared credentials by a skill package, which expands trust boundaries and would be dangerous if the package were modified or abused because it can operate on authorization shared by multiple skills.

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 allows the package to replace its own installed files, which is a self-modification capability uncommon for a narrowly scoped earnings-script skill. Self-modifying code is dangerous because it bypasses normal deployment review paths and turns any weakness in update provenance, CDN integrity, or vendor account security into direct code replacement on the client.

Static analysis

No suspicious patterns detected.