Back to skill

Security audit

wealth-cover-set

Security checks for vulnerabilities and agentic risk

Overview

This cover-image skill is coherent in its creative workflow, but it needs Review because it uses broad shared Beatra account credentials and silently self-updates executable files.

Install only if you are comfortable granting Beatra a shared device authorization that covers more than still image generation and allowing this package to update itself automatically. Before use, consider disabling auto-update with the documented command, review the Beatra approval page carefully, and avoid uploading local files unless they are intended to be sent to Beatra.

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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/authorize.py:32
Finding
Overprivileged Shared Device Token and Unrestricted MCP Tool Invocation## Vulnerability Details **File Location**: `scripts/authorize.py:32-35`; `scripts/mcp_client.py:1463-1481` **Vulnerability Type**: Excessive authorization scope and insufficient local authorization enforcement **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" ) ``` The generic command handler accepts any caller-supplied 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}, ) ``` ### Technical Analysis The declared Skill functionality is limited to generating and editing still-image cover sets. Nevertheless, the authorization request includes unrelated privileges for video generation, music generation, speech generation, voice creation, and generic MCP tool access. The credential is also shared between Beatra Skill packages. Consequently, compromise of this package, its updater, or another component able to read the token could expose all granted capabilities rather than only those needed for still-image production. The bundled client does not maintain a local allowlist of permitted MCP tool names. Its `call` command forwards an arbitrary tool name supplied on the command l ...[truncated 1459 chars]
Remediation
## Remediation Suggestions 1. Replace the full shared scope with a least-privilege scope limited to: - Image generation and image editing. - Model-card reads. - Required artifact upload and read operations. - Task reads and user-requested cancellation. - Narrow billing reads or spending authority strictly required by approved generation. 2. Remove video, music, speech, and voice-write scopes from this Skill's authorization request. 3. Avoid using one full-scope token across unrelated Skill packages. Issue package-specific or capability-specific credentials. 4. Add a local allowlist in `_run_command`, permitting only the exact tools documented by this Skill, such as: - `beatra.models.list` - `beatra.images.generate` - `beatra.images.edit` - `beatra.assets.upload` - `beatra.tasks.get` - `beatra.tasks.list` - `beatra.tasks.cancel` - The documented wallet read operations 5. Reject all other tool names before sending a network request. 6. Require explicit user reauthorization when a future package version genuinely needs an additional capability. 7. Provide users with a clear authorization summary showing each requested capability and why it is required.

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Default Silent Self-Update Can Replace Executable Skill Files Without User Approval## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1018`; default behavior at `scripts/mcp_client.py:515-526` **Vulnerability Type**: Mutable remote code retrieval and automatic executable replacement **Risk Level**: High ### Vulnerable Code The update state defaults to automatic updates being enabled: ```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 ``` Ordinary client commands can silently fetch and install a newer package: ```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 ...[truncated 3510 chars]
Remediation
## Remediation Suggestions 1. Disable automatic installation by default. Default `auto_update` to `False`. 2. Separate update checking from update installation: - Silent checks may report availability. - Installation should require explicit user approval. 3. Display the target version, changelog, affected files, and release origin before installation. 4. Sign release metadata with a private release key and embed the corresponding public verification key in the reviewed package. 5. Verify a signature over the package name, channel, locale, version, archive digest, manifest digest, and expiry information. 6. Protect against key rollback and key substitution; do not obtain the verification key from the same discovery response. 7. Continue retaining the existing archive validation, path traversal defenses, file ownership checks, transaction journal, and rollback logic. 8. Consider distributing updates through the host platform's reviewed Skill/package update mechanism rather than implementing an independent executable updater. 9. Notify the user after any successful update and require a new audit or review for materially changed permissions.

other

Note
Location
scripts/authorize.py:362
Finding
Local Hostname Is Collected and Transmitted During Device Authorization## Vulnerability Details **File Location**: `scripts/authorize.py:362-370, 458-470` **Vulnerability Type**: Undisclosed environment identifier collection **Risk Level**: Low ### Vulnerable Code The authorization helper collects the machine 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] ``` It then includes the hostname in the remote 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) ``` ### Technical Analysis Collecting one hostname is not broad system reconnaissance, and the apparent purpose—providing a recognizable label in a device-management console—is legitimate. The code does not enumerate network interfaces, collect IP addresses, scan the local network, access SSH keys, or inspect unrelated system files. However, a hostname is still an environment identifier. Hostnames may encode a person's name, employer, business unit, asset number, internal domain convention, or system role. The value is transmitted to `https://api.beatra.ai` as part of device authorization and persisted locally in `~/.beatra/host.json`. The reviewed authentication documentation describes the stable installation reference and agent platform but does not clearly state that the local hostname will be collected and sent to the remote service. The collection is automatic and does not provide an opt-out. ### Attack Path 1. The user ...[truncated 894 chars]
Remediation
## Remediation Suggestions 1. Clearly disclose before authorization that the hostname, platform, package identity, and stable installation reference will be transmitted. 2. Make hostname sharing opt-in rather than automatic. 3. Default to a generated opaque label such as `Beatra device <short-random-id>`. 4. Add a command-line option such as `--device-name` and a privacy-preserving option such as `--no-hostname`. 5. Permit the user to review or edit the device label before transmission. 6. Avoid persisting the hostname in `host.json` unless it is required for a documented local function. 7. Apply server-side retention limits and allow users to delete or rename stored device identifiers from the console.
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
92% confidence
Finding
The skill declares itself as a simple image-cover generator, but the content instructs use of a bundled Python client with shell execution, network access, local file interaction, and credential/state management. That is a real security concern because these capabilities materially exceed the apparent user-facing purpose and are not explicitly permission-scoped, increasing the chance of unnoticed data access, remote calls, and side effects.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
This is a strong true positive: the skill is marketed as generating session cover stills, but it also introduces OAuth login, persistent bearer credentials, remote tool invocation, arbitrary local file upload, telemetry/registration, shared credential inventory, uninstall/token revocation flows, and automatic update/install behavior. That mismatch is dangerous because users may consent to a low-risk creative skill while unknowingly granting a much broader software supply-chain and account-access footprint.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The authorization flow requests a very broad scope set, including wallet spending, task control, and multiple media-generation capabilities that are not necessary for a skill whose stated purpose is generating still wealth cover images. Excessive OAuth scopes violate least-privilege and significantly increase blast radius if the token is misused, exfiltrated, or the skill behaves unexpectedly.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The script fingerprints the host agent platform from environment variables and captures a recognizable device hostname, then persists that metadata locally and sends platform/device information during authorization. For a cover-image skill, this collection is not clearly necessary and creates unnecessary privacy and environment-disclosure risk that could aid profiling or tie credentials to specific hosts.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The skill records a local inventory of installed skills including slug, platform, and resolved install path under a shared state directory, which exceeds the stated purpose of generating cover stills. Maintaining cross-skill installation metadata can expose user environment details and creates an unnecessary registry that may be abused by other code with local access.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
The file implements a general-purpose remote MCP client with tool invocation, asset upload, registration, and package self-update capabilities that far exceed the stated purpose of generating wealth cover stills. In a skill context, this broad remote control surface materially increases risk because the package can contact remote services, upload local files, and change its own installed code independent of the narrowly described user task.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The code fingerprints the host environment using environment variables and persisted host metadata, then attaches that platform attribution to remote tool calls. While not an exploit by itself, this is unnecessary data collection for a cover-generation skill and increases privacy and tracking risk by exposing host context to a remote service.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The skill records persistent local inventory and installation telemetry unrelated to the user-facing creative function, then attempts remote installation registration. In this skill context, that behavior is over-scoped and creates avoidable privacy, tracking, and governance concerns because usage metadata is being persisted and reported outside the core task.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The uninstall script manages shared device credentials and can remove local auth state for all skills, behavior that is unrelated to the skill's advertised purpose of generating cover images. Even though this appears framed as cleanup logic rather than overtly malicious functionality, it introduces sensitive account/authorization handling into an otherwise content-generation package and broadens the blast radius if the script is invoked unexpectedly or modified.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
This code performs a remote POST to revoke device authorization using a bearer token, which is sensitive account-management functionality not justified by the skill's stated purpose. A packaged skill capable of revoking shared authorization can disrupt other installed skills or the user's device access if triggered inappropriately, and it creates a network-capable path for handling credentials inside an untrusted extension.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill states that the bundled client can automatically install newer releases without separate confirmation. Even with verification claims, silent point-of-use updates create a supply-chain risk: code executed during a benign image-generation workflow can change underneath the user without an interactive trust decision, potentially introducing new capabilities or behavior.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The document states that the client silently checks for and automatically installs newer releases by default, without requiring separate user confirmation. Even with integrity checks and rollback protections, automatic modification of installed files changes the user's environment and trust boundary; if the update channel, signing process, or upstream distribution is ever compromised, users may receive code changes without an explicit decision point or warning about system impact.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The runtime can silently download and apply updates that modify installed package files during normal operation, without any user-facing warning. Even with checksum and manifest validation, this creates a self-modifying trust channel: a compromise of the update infrastructure or release pipeline would let new code be introduced into the agent environment under the guise of a routine skill invocation.

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
93% confidence
Finding
The script explicitly targets credentials.json as part of the shared Beatra state it may delete, indicating access to and control over stored authentication material. In the context of a cover-generation skill, touching shared credential storage is unnecessary and dangerous because compromise, misuse, or accidental execution could remove or otherwise impact authentication used by other skills on the device.

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
97% confidence
Finding
The _device_token function reads an access token from credentials.json and returns it for use in remote authorization revocation. Reading bearer tokens inside a third-party skill is high risk because it gives the package direct access to reusable credentials, and in this skill's context that capability is unrelated to image-cover generation and materially increases the danger of abuse or 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
94% confidence
Finding
The skill exposes explicit self-update functionality that can replace its own package files. In a narrowly scoped creative skill, self-modification is especially risky because it permits post-install behavior changes outside the original reviewed code, expanding the trust boundary from static package contents to the entire remote update supply chain.

Static analysis

No suspicious patterns detected.