Back to skill

Security audit

Fund Quarterly Report Talking Clips

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed Beatra media-generation workflow, but it also creates a broad shared credential and silently replaces local executable skill files by default.

Review before installing. Authorizing this skill creates a broad Beatra device token under `~/.beatra` that may be usable for more than this quarterly-report clip workflow, and ordinary client commands can silently update the installed skill. Install only if you trust Beatra and its update channel; consider disabling automatic updates with `python3 scripts/mcp_client.py update --auto off` and revoke the device in the Beatra Console when no longer needed.

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:34
Finding
Overprivileged Shared Device Token and Unrestricted MCP Tool Dispatch<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:34-38`; `scripts/mcp_client.py:1466-1481`; `scripts/mcp_client.py:1488-1490` **Vulnerability Type**: Excessive authorization scope and unrestricted remote tool selection **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 Skill obtains a shared bearer token that grants broad access to Beatra tools. The requested scope includes unrelated capabilities such as music generation, image generation, general wallet spending, and task cancellation. The declared quarterly-report workflow primarily requires asset upload, optional voice operations, speech generation, video generation, and related task reads. The client compounds this excessive scope by accepting any MCP tool name from the command line. It does not enforce a local allowlist corresponding to the operations documented by the Skill. Therefore, a ...[truncated 1260 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the shared full-scope token with a package-specific, least-privilege credential. 2. Remove permissions unrelated to this workflow, particularly `music:generate` and unrelated image-generation access. 3. Separate read-only wallet access from spending authorization. 4. Add a hardcoded local allowlist of documented tool names. 5. Reject any tool name outside that allowlist before creating an MCP session. 6. Require explicit user confirmation immediately before wallet-spending, generation, or cancellation operations. 7. Use separate credentials or capability tokens for destructive operations such as task cancellation. 8. Add server-side enforcement so source-package attribution cannot invoke tools outside the package's approved capabilities. ]]>

other

Warning
Location
scripts/authorize.py:340
Finding
Collection and Transmission of Hostname and Agent-Environment Telemetry<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:340-383`; `scripts/authorize.py:456-467`; `scripts/authorize.py:565-568`; `scripts/mcp_client.py:1140-1164`; `scripts/mcp_client.py:1217-1227` **Vulnerability Type**: Excessive device reconnaissance and telemetry **Risk Level**: Medium ### 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 ``` ```python if method == "tools/call": arguments = params.get("arguments") if isin ...[truncated 1998 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make hostname and platform telemetry opt-in rather than automatic. 2. Present the exact transmitted fields before authorization. 3. Use a random, user-editable device label instead of the operating-system hostname. 4. Do not persist the raw hostname in `host.json`. 5. Allow users to authorize without source-platform attribution. 6. Minimize server retention and provide controls to view and delete installation telemetry. 7. Keep the stable installation reference pseudonymous and avoid associating it with unnecessary host metadata. 8. If platform telemetry is operationally required, transmit only a coarse category and document its purpose and retention period. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:973
Finding
Silent Automatic Retrieval and Replacement of Executable Skill Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:31-32`; `scripts/mcp_client.py:299-330`; `scripts/mcp_client.py:475-491`; `scripts/mcp_client.py:516-523`; `scripts/mcp_client.py:973-1017`; `scripts/mcp_client.py:1542-1543` **Vulnerability Type**: Remote payload retrieval and execution through automatic updates **Risk Level**: High ### Vulnerable Code ```python PACKAGE_DISCOVERY_URL = "https://beatra.ai/skills/fund-report-talking/channels/clawhub/install.json" PACKAGE_CDN_BASE_TEMPLATE = "https://cdn.beatra.ai/agent-packages/fund-report-talking/channels/clawhub/v{version}" ``` ```python def download_update( discovery: dict[str, Any], *, get_bytes: GetBytes = _default_get_bytes, ) -> tuple[dict[str, Any], dict[str, bytes]]: archive_url, manifest_url = _release_urls(discovery) manifest_content = get_bytes( manifest_url, UPDATE_DOWNLOAD_TIMEOUT_SECONDS, MAX_UPDATE_MANIFEST_BYTES, ) if _sha256(manifest_content) != discovery["manifest_sha256"]: raise RuntimeError("Beatra update manifest checksum does not match discovery") manifest = _json_object(manifest_content, "Beatra update manifest") manifest_files = _manifest_files(manifest, discovery=discovery) archive = get_bytes( archive_url, UPDATE_DOWNLOAD_TIMEOUT_SECONDS, MAX_UPDATE_ARCHIVE_BYTES, ) if _sha256(archive) != discovery["archive_sha256"]: raise RuntimeError("Beatra update archive checksum does not match discovery") return manifest, _validated_archive(archive, manifest_files=manifest_files) ``` ```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_v ...[truncated 4244 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic code updates by default. 2. Require explicit user approval before replacing executable or instruction files. 3. Sign release manifests with an offline-protected private key. 4. Embed and pin the corresponding public key in the audited package. 5. Verify signatures before trusting versions, URLs, file lists, or hashes. 6. Implement signed key rotation rather than trusting a replacement key from discovery metadata. 7. Display the current version, proposed version, and changed files before installation. 8. Separate update operations from credential-bearing MCP operations. 9. Consider using the host platform's signed package-management and rollback facilities. 10. Retain the existing path, size, archive, locking, and rollback protections as defense in depth. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:231
Finding
Server-Controlled Upload URL Is Not Restricted to Trusted Storage Hosts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:231-260` **Vulnerability Type**: Insufficient validation of remote upload destination **Risk Level**: Medium ### Vulnerable Code ```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} ``` ### Technical Analysis The upload workflow asks the canonical MCP server for a pre-authorized upload destination. The cl ...[truncated 1465 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Maintain an explicit allowlist of approved Beatra upload domains. 2. Validate the normalized hostname and require exact matches or carefully defined subdomain suffixes. 3. Reject IP-literal destinations, unrelated domains, nonstandard ports, and ambiguous internationalized hostnames. 4. Document the authorized storage hosts in the Skill's security documentation. 5. Bind upload grants cryptographically to the canonical MCP origin, artifact request, digest, size, and MIME type. 6. Display the destination host and request confirmation if a noncanonical destination is ever necessary. 7. Preserve redirect rejection and existing content-length, MIME, and regular-file validation. ]]>
Vulnerability Patterns
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (6)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes a bundled Python client, reads local files, uploads artifacts, polls remote tasks, and supports automatic updates, which collectively require file, shell, and network capabilities. Having these capabilities without explicit permission declarations weakens reviewability and informed consent, increasing the chance that a host grants broader access than users expect.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The stated purpose is narrowly framed as producing talking clips, but the skill also directs authentication flows, persistent credential handling, remote tool invocation, file upload, telemetry/registration, uninstall behavior, and package update/install behavior. This mismatch is dangerous because users and reviewers may authorize a media-generation skill without realizing it can modify the local installation, store credentials, and communicate with multiple remote services.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill states that the bundled client can automatically download and install newer releases without separate confirmation. Even with signature or manifest verification, silent self-update expands the trusted codebase after approval and can introduce new behaviors or defects without a fresh user consent or security review.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The document states that the client silently checks for updates and installs newer releases automatically without separate user confirmation. Even with integrity checks and fixed update sources, silent file replacement changes executable behavior on the user's system without an explicit approval step, which creates supply-chain and change-management risk if the update channel is compromised or if an update causes harmful behavior.

Missing User Warnings

Medium
Confidence
83% confidence
Finding
The client performs silent automatic self-updates before normal commands, downloading code from the network and overwriting installed package files without interactive confirmation at execution time. Even though the implementation includes integrity checks, this still creates a privileged remote code replacement path: compromise of the vendor update infrastructure, signing/checksum publication path, or distribution account would immediately propagate new executable code to installed clients.

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
86% confidence
Finding
This package includes a built-in self-modification capability that can replace its own installed files via network-fetched updates. In an agent skill context, self-updating code is especially sensitive because it changes the executable behavior after installation and expands the trust boundary from the installed artifact to the ongoing security of the update channel and vendor infrastructure.

Static analysis

No suspicious patterns detected.