Back to skill

Security audit

risk-grade-set

Security checks for vulnerabilities and agentic risk

Overview

The skill is mainly an image-pack generator, but it can authorize and use broader Beatra account capabilities and silently replace its own package files.

Review this before installing. It stores a shared Beatra bearer credential in ~/.beatra, can spend Beatra credits, can upload selected local reference files, records package/platform installation metadata, and silently auto-updates by default. Install only if you trust Beatra with those broader account permissions; consider disabling automatic updates with scripts/mcp_client.py update --auto off after installation.

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:31
Finding
Overprivileged Device Token Combined with Unrestricted Tool Dispatch<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:31-35`; `scripts/mcp_client.py:1463-1482` **Vulnerability Type**: Excessive authorization scope and missing local tool allowlist **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}, ) ``` ### Technical Analysis The declared purpose of the Skill is to create risk-grade image stills. However, authorization requests a shared bearer token with permissions for unrelated capabilities, including video, music, speech, voice writing, artifact writing, wallet spending, and task cancellation. The command dispatcher accepts an arbitrary `tool_name` from the command line and forwards it to the remote MCP endpoint without enforcing a package-specific allowlist. Consequently, the broad token permissions are directly reachable through the bundled client rather than merely being dormant scopes. This violates least privilege. The legitimate workflow appears to require image generation and editing, optional artifact upload, model discovery, task reads, wallet reads, and narrow ...[truncated 1182 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Issue a package-specific token with only the scopes required by this Skill. 2. Remove unrelated video, music, speech, and voice scopes. 3. Separate read-only wallet access from wallet spending and request spending authority only immediately before an approved billable operation. 4. Add a strict local tool allowlist, for example: - `beatra.models.list` - `beatra.images.generate` - `beatra.images.edit` - `beatra.assets.upload` - `beatra.tasks.get` - `beatra.tasks.list` - narrowly required wallet-read operations 5. Gate billable tools and `beatra.tasks.cancel` on explicit, operation-specific user approval. 6. Reject unknown tool names locally before opening an authenticated MCP session. 7. Avoid sharing one full-scope token across unrelated packages; use audience- and package-restricted credentials where supported. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Silent Remote Replacement of Executable Skill Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1018`; invocation at `scripts/mcp_client.py:1539-1544` **Vulnerability Type**: Default-enabled remote code update without per-update approval or independent signature verification **Risk Level**: High ### Vulnerable Code ```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) ...[truncated 3118 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default; make update checks and installation opt-in. 2. Require explicit user approval for each version before replacing executable or instruction files. 3. Sign release manifests with an offline publisher key and embed only the verification public key in the client. 4. Verify the signature before trusting versions, URLs, hashes, or file lists. 5. Display the current version, proposed version, changed files, and signer identity before installation. 6. Consider prohibiting automatic replacement of the currently running updater and authorization scripts. 7. Preserve the existing fixed-host, redirect rejection, path validation, size limits, ownership checks, locking, journal, and rollback protections. 8. Provide an enterprise policy that permanently disables self-updates and supports externally managed, pinned package versions. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:1040
Finding
Windows Bearer Token Permissions Are Assumed Rather Than Enforced<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:1040-1046`; credential creation behavior at `scripts/authorize.py:124-130` **Vulnerability Type**: Insufficient access-control validation for locally stored credentials **Risk Level**: Medium ### Vulnerable Code ```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") ``` ```python def _private_directory(path: Path) -> None: 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) ``` ### Technical Analysis On POSIX systems, the client verifies directory ownership and mode `0700`, verifies file ownership and mode `0600`, and refuses unsafe credentials. On Windows, it reads `credentials.json` without checking the file owner, inherited access-control entries, or whether other local principals can read the file. The authorization helper likewise performs no Windows ACL hardening. The implementation relies on the assumption that the user-profile directory has a suitable default ACL. That assumption may not hold on migrated profiles, shared workstations, administratively modified systems, or installations where inherited permissions were broadened. The documentation claims that the current user must be the only principal granted access, but this condition is not enforced by the code. ### Attack Path 1. The Skill authorizes successfully on ...[truncated 836 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the token in Windows Credential Manager or protect it with DPAPI rather than relying solely on profile-directory ACL inheritance. 2. If a JSON credential file must remain, create an explicit user-only DACL when the file and state directory are created. 3. Validate ownership and effective access before every credential read. 4. Reject credentials readable by unrelated users or broad groups. 5. Avoid invoking shell commands to configure ACLs; use supported Windows security APIs or a small audited platform abstraction. 6. Update the documentation so its security guarantees exactly match enforced behavior. 7. Provide migration logic that securely relocates or reprotects existing credential files. ]]>

other

Note
Location
scripts/authorize.py:335
Finding
Unnecessary Hostname and Agent-Environment Telemetry<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:335-370`; transmission at `scripts/authorize.py:452-473` **Vulnerability Type**: Privacy-relevant collection and transmission of local environment identifiers **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) ``` ### Technical Analysis The authorization flow reads environment signatures to ident ...[truncated 1710 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use a generic device label by default, such as the package display name plus a random non-identifying suffix. 2. Make hostname transmission explicitly opt-in. 3. Clearly disclose every telemetry field before authorization, including hostname, platform, package version, package slug, and stable installation reference. 4. Provide command-line options such as `--device-name` and `--no-host-telemetry`. 5. Avoid persisting the hostname unless required for a user-requested device-management feature. 6. Minimize platform attribution and allow it to remain `unknown` without affecting functionality. 7. Define retention and deletion behavior for server-side installation telemetry. ]]>
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 (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill declares no permissions while instructing the agent to use shell, network, file access, and a bundled Python client that can perform remote operations and local updates. That creates a dangerous transparency gap: users and hosts cannot accurately consent to or sandbox the skill, and the skill’s update/auth/upload behaviors materially exceed what its metadata suggests.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill is presented as an image-pack generator, but its documented behavior includes persistent credential storage, browser-based OAuth, remote MCP tool invocation, local file upload, installation registration/telemetry, auto-update installation, and uninstall/state removal. This mismatch is dangerous because it hides a much broader trust boundary and can trick users or platforms into authorizing credentialed networked code execution and local state changes they did not reasonably expect from the description.

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
83% confidence
Finding
This client includes a self-update mechanism that downloads and replaces package files automatically, including in best-effort background mode before normal commands. Although it performs checksum and path validation, it still creates a remote code delivery channel: compromise of the discovery/manifest/signing pipeline, CDN origin, or release process would let an attacker push malicious code into the local installation with the user's privileges.

Static analysis

No suspicious patterns detected.