Back to skill

Security audit

Spoken Seeding Video Maker

Security checks for vulnerabilities and agentic risk

Overview

The skill’s video workflow is mostly coherent, but it also grants broad Beatra account authority and silently self-updates installed code, so users should review it before installing.

Install only if you are comfortable with a Beatra device credential shared across Beatra skills, paid account actions through that credential, local state in ~/.beatra, and silent package self-updates. Consider disabling auto-update with the documented command before normal use, avoid using the upload command for this zero-upload workflow, and revoke the device in the Beatra Console if you uninstall or no longer trust the package.

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 (4)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Silent automatic updates permit post-audit replacement of executable Skill code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1017`, invoked by `scripts/mcp_client.py:1541-1543` **Vulnerability Type**: Silent remote payload retrieval and executable-file replacement **Risk Level**: High ### Code Snippet ```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( ...[truncated 3040 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default. Update checks may remain available, but installing a release should require explicit, informed user approval. 2. Authenticate release metadata with a digital signature verified against a public key pinned in the reviewed client. Do not rely solely on hashes delivered by the same update service. 3. Separate update checking from update application and display the target version, signed release identity, and files to be changed before installation. 4. Preserve the existing redirect, hostname, archive-path, size, ownership, lock, transaction, and rollback protections. 5. Consider delegating updates to the trusted Skill/package host rather than allowing runtime code to replace itself. 6. Record update activity in an auditable local log without including credentials or user content. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/mcp_client.py:1428
Finding
Generic local-file upload capability exceeds the declared zero-upload workflow<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:1428-1461`; local file reading at `scripts/mcp_client.py:191-225`; upload URL handling at `scripts/mcp_client.py:231-265` **Vulnerability Type**: Excessive local-file access and unrestricted remote upload destination **Risk Level**: High ### Code Snippet ```python def upload( path: Path, *, mime_type: str, state_dir: Path | None = None, post_json: PostJson = _default_post_json, put_bytes: PutBytes = _default_put_bytes, ) -> dict[str, str]: if re.fullmatch(r"[a-z0-9][a-z0-9.+-]*/[a-z0-9][a-z0-9.+-]*", mime_type) is None: raise RuntimeError("Local upload MIME type is invalid") filename, content = _read_local_upload(path) resolved_state_dir = state_dir or Path.home() / ".beatra" session = _session_with_registration(state_dir=resolved_state_dir, post_json=post_json) result = session.request( 2, "tools/call", { "name": "beatra.assets.upload", "arguments": { "filename": filename, "mime_type": mime_type, "size_bytes": len(content), }, }, ) if failure := _tool_failure_message(result): raise RuntimeError(f"Beatra rejected the upload grant request: {failure}") return _complete_upload( result, mime_type=mime_type, content=content, put_bytes=put_bytes, ) ``` The server-provided upload URL is only required to use HTTPS; its hostname is not restricted: ```python 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") ``` ### Technical Analysis The declared Skill workflow states that users do not need to upload footage or other material. The ...[truncated 2281 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the generic `upload` command from this zero-upload Skill. 2. If uploads become a legitimate feature, require explicit user confirmation that identifies the exact canonical path, byte size, MIME type, and destination before reading the file. 3. Restrict selectable files to a dedicated user-approved media directory rather than arbitrary filesystem paths. 4. Validate file signatures and content against a narrow allowlist of required media formats; do not trust a caller-supplied MIME type alone. 5. Pin upload destinations to documented Beatra-controlled hostnames or cryptographically validate a narrowly scoped signed upload grant. 6. Keep the current regular-file, no-follow, size-limit, and file-stability checks. 7. Do not include artifact-upload permission in this package's authorization scope unless the declared workflow actually requires it. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/authorize.py:34
Finding
Shared bearer credential and unrestricted tool dispatch violate least privilege<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:34-38`; unrestricted tool dispatch at `scripts/mcp_client.py:1463-1481` **Vulnerability Type**: Excessive OAuth scope and absence of a package-level tool allowlist **Risk Level**: High ### Code Snippet ```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 bundled client accepts an arbitrary tool name from its caller: ```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}, ) ``` ### Technical Analysis The authorization request obtains one shared device token with broad privileges covering generic MCP tools, artifact writing, image generation, video generation, music generation, speech generation, voice reading and writing, wallet spending, task reading, artifact reading, and task cancellation. Some paid generation and task-reading permissions are consistent with the declared workflow. However, `voices:write`, generic artifact writing/upload behavior, generic MCP tool access, and unrestricted task cancellation are broader than the minimum permissions necessary to generate a topic-only recommendation video. The client also accepts any caller-provided too ...[truncated 1973 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the shared full-scope device token with a package-specific, least-privilege token. 2. Remove `voices:write`, generic artifact/upload permissions, and any other scope not required by the documented workflow. 3. Add a hardcoded allowlist for the exact documented tools, such as model and voice listing, image generation, speech synthesis, optional music generation, video animation, task status operations, and explicitly documented wallet reads. 4. Separate read-only tools from paid or destructive tools and require explicit user approval immediately before wallet spending or task cancellation. 5. Enforce least privilege on the server as well as in the local client; a local allowlist alone is not a security boundary against replaced client code. 6. Avoid sharing one bearer credential across unrelated packages. If sharing is unavoidable, issue constrained capability tokens for individual operations. 7. Preserve the existing POSIX ownership, permission, and symbolic-link checks around the credential file. ]]>

other

Warning
Location
scripts/authorize.py:362
Finding
Authorization transmits the local hostname without explicit disclosure or consent<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:362-369`, transmitted at `scripts/authorize.py:458-468` **Vulnerability Type**: Host-environment telemetry and device fingerprinting **Risk Level**: Medium ### Code Snippet ```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 collected hostname is placed into the authorization request: ```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 value is collected automatically during authorization: ```python 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()`, stores the result in `~/.beatra/host.json`, and sends it to `https://api.beatra.ai/oauth/device_authorization` as `device_name`. It also detects the hosting agent from process-environment signatures. Collecting a hostname can support a recognizable device list, but it is not technically necessary to authorize the service or perform the declared video-generation workflow. Hostnames frequently contain employee names, company identifiers, project names, asset numbers, or internal naming conventions. This makes the field useful for correlation and host fingerpri ...[truncated 1371 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not collect the operating-system hostname by default. 2. Use a random, non-identifying installation label or ask the user to provide an optional display name. 3. Clearly disclose every telemetry field before authorization, including hostname, platform, package identity, version, and stable installation reference. 4. Require explicit opt-in before transmitting a host-derived device name. 5. Provide a command-line option such as `--device-name` and otherwise omit the field entirely. 6. Minimize retention and correlation of host metadata on the server, and document the applicable retention period. 7. If a value is stored locally, create `host.json` atomically with user-only permissions consistent with the rest of the state directory. ]]>
Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (10)

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill declares no permissions while instructing use of shell, network, filesystem, environment, and a Python client that performs remote API calls and local state changes. This under-disclosure is dangerous because users and host systems cannot make informed trust decisions, and the hidden capabilities materially increase the attack surface for credential access, file modification, network exfiltration, and unintended command execution paths.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill advertises simple video generation but also introduces broader behaviors: OAuth login, persistent credential storage, arbitrary authenticated Beatra tool access, uploads, telemetry/registration, uninstall state handling, and self-updating code. That mismatch is dangerous because it obscures security-relevant behavior from users and reviewers, making over-privileged installation and trusted execution more likely than if those behaviors were disclosed up front.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
Automatic self-update adds a code-fetching and self-replacement mechanism unrelated to the core task of generating videos. Even with stated verification controls, this materially increases supply-chain risk: a compromise in discovery, signing, CDN, or update logic could lead to silent code changes on user systems without transaction-time review.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The client contains extensive self-update and package replacement logic unrelated to the advertised video-generation function, including downloading archives, validating manifests, and replacing files in the installation root. Even though there are several integrity checks, this materially expands the attack surface and enables code changes on the host outside the user's requested task, which is especially risky for an agent skill.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The skill performs installation registration, local skill inventory tracking, and telemetry persistence that are not necessary to generate recommendation videos. Collecting and storing this metadata increases privacy risk and broadens the capability set of the package beyond its stated purpose, making misuse or overcollection more concerning in agent environments.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The code fingerprints the host environment using environment variables and host.json to classify the agent platform. For a video-making skill, this is unnecessary and can support tracking, tailored behavior, or environment-specific evasion, which makes it suspicious in context.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
Maintaining a device-local inventory of installed skills is unrelated to producing spoken seeding videos and creates a local surveillance/telemetry capability. This can expose user tooling information and provides persistence of metadata that may be valuable for profiling or for coordinating broader package behavior.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The document explicitly states that the client silently checks for and automatically installs updates by default, without separate confirmation. Even though it describes integrity checks and rollback protections, unattended self-modification of an installed client materially increases supply-chain and user-consent risk because code can change before ordinary commands without an explicit approval step or prominent warning.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill can silently self-update during normal execution via maybe_auto_update(), modifying installed package files without a user-facing warning at run time. Silent code replacement in an agent skill undermines change transparency and can turn any compromise of the update channel or publisher into immediate host-side code execution on subsequent use.

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
97% confidence
Finding
Exposing self-update/self-modification behavior through the CLI confirms that this package is designed to replace its own installed code. In the context of a skill whose purpose is content generation, self-modifying behavior is unjustified and significantly increases risk because future code can change independently of the originally reviewed package.

Static analysis

No suspicious patterns detected.