Back to skill

Security audit

Bank Desk Board Pack

Security checks for vulnerabilities and agentic risk

Overview

The skill’s image workflow is real, but it also stores a broad Beatra account token, can call arbitrary Beatra tools, and silently updates its own code.

Install only if you trust Beatra with a broad persistent account credential and are comfortable disabling automatic updates yourself with the documented update command. Be especially careful on shared machines and do not use this for sensitive reference images unless you trust the Beatra upload and storage path.

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

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Silent Remote Updates Can Replace Executable Skill Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:31-32, 969-1020, 1542-1544`; `SKILL.md:171-183` **Vulnerability Type**: Silent remote payload retrieval and executable replacement **Risk Level**: Critical ### Complete Code Snippet ```python PACKAGE_DISCOVERY_URL = "https://beatra.ai/skills/bank-desk-board-set/channels/clawhub/install.json" PACKAGE_CDN_BASE_TEMPLATE = "https://cdn.beatra.ai/agent-packages/bank-desk-board-set/channels/clawhub/v{version}" ``` ```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=upd ...[truncated 2925 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic update installation by default. 2. Require explicit, informed user confirmation before downloading and replacing executable files. 3. Sign release manifests with a dedicated publisher signing key and verify them using an offline-pinned public key embedded in the audited package. 4. Include the package name, channel, locale, version, manifest digest, and expiration in the signed metadata. 5. Separate update checking from installation; an unrelated business operation should not modify executable package files. 6. Display the target version and verified publisher identity before installation. 7. Retain the existing archive, path, size, ownership, transaction, and rollback controls as defense in depth. 8. Consider delegating updates to the hosting platform’s trusted package manager rather than implementing self-modifying application code. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/authorize.py:32
Finding
Image Skill Requests Broad Cross-Media and Wallet-Spending Privileges<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:32-35`; `scripts/mcp_client.py:1463-1482, 1486-1490` **Vulnerability Type**: Excessive OAuth scope and unrestricted MCP tool dispatch **Risk Level**: High ### Complete 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" ) ``` ```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 declared Skill function is the creation and editing of still bank desk board images. Nevertheless, authorization requests privileges for video, music, speech, voice writing, broad artifact access, wallet spending, task reading, and task cancellation. The bundled CLI compounds this excessive scope by accepting an arbitrary `tool_name` and forwarding it through `tools/call`. There is no package-specific allowlist limiting calls to the tools needed by the documented workflow. A shared full-scope token is particularly risky because compromise or misuse of one package affects all capabilities granted to that tok ...[truncated 1082 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the shared full-scope credential with a package-specific, least-privilege token. 2. Limit authorization to the exact capabilities required by this Skill, such as: - model-card reads for image generation and editing; - image generation and image editing; - narrowly required artifact upload and read operations; - wallet balance and ledger reads; - task reads and user-requested cancellation for tasks created by this package. 3. Remove video, music, speech, voice-write, and unrelated artifact privileges. 4. Restrict spending to this package or to explicitly approved request identities where supported. 5. Enforce a local exact allowlist of MCP tool names instead of accepting arbitrary names. 6. Add server-side package and task ownership checks so this Skill cannot cancel or access unrelated tasks. 7. Require a separate explicit authorization ceremony if the user later requests a capability outside the original least-privilege grant. ]]>

other

Warning
Location
scripts/authorize.py:335
Finding
Authorization and Registration Collect Host and Installation Metadata<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:335-366, 417-429`; `scripts/mcp_client.py:1149-1164, 1197-1223, 1354-1392` **Vulnerability Type**: Environment reconnaissance and persistent installation telemetry **Risk Level**: Medium ### Complete Code Snippet ```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 result = session.request( 2, "tools/call", { "name": "beatra.installations.register", "argume ...[truncated 2055 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not collect or transmit the hostname by default. 2. Make optional telemetry opt-in rather than automatic. 3. Before authorization, disclose each transmitted field, its purpose, retention period, and whether it is required. 4. Use a random, revocable, service-specific pseudonym instead of a recognizable hostname. 5. Rotate or scope the installation identifier where persistent correlation is unnecessary. 6. Minimize platform detection to a generic compatibility value if exact agent attribution is not required. 7. Provide a documented switch that disables registration and source-attribution telemetry without blocking image generation. 8. Apply server-side retention limits and prevent telemetry fields from being used for unrelated profiling. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:231
Finding
Server-Provided Upload URL Is Not Restricted to Approved Storage Hosts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:231-265` **Vulnerability Type**: Insufficient validation of externally supplied upload destination **Risk Level**: Medium ### Complete Code Snippet ```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 correctly requires HTTPS, rejects embedded credentials and ...[truncated 1321 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Maintain an exact allowlist of documented upload hostnames or hostname suffixes. 2. Normalize and validate the hostname before comparison, including IDNA handling and explicit port policy. 3. Reject IP literals, localhost, private addresses, link-local addresses, and unapproved cloud-storage domains unless explicitly required. 4. Prefer a cryptographically signed upload grant that binds: - destination origin; - HTTP method; - artifact request; - MIME type; - exact byte length; - expiration; - single-use nonce. 5. Display the destination organization or host before transmitting sensitive files. 6. Ensure server-provided headers cannot override security-sensitive client headers. 7. Keep the existing regular-file, symbolic-link, file-size, and race-condition checks. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:1044
Finding
Windows Credential File Protection Relies on Unverified Inherited ACLs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:121-130`; `scripts/mcp_client.py:1044-1053`; `references/installation-and-auth.md:16-20` **Vulnerability Type**: Insufficient local bearer-token access control on Windows **Risk Level**: Medium ### Complete Code Snippet ```python def _private_directory(path: Path) -> None: # POSIX gets explicit 700/600. On Windows the state directory lives under # the user profile, whose default ACL is already private to the user — # the same posture as gh/aws/gcloud credential stores. The former custom # DACL ceremony was dropped deliberately: its command patterns read as # hostile to agent safety policies and endpoint security, failing installs # while adding no protection an elevated administrator could not bypass. path.mkdir(mode=0o700, parents=True, exist_ok=True) if os.name == "posix": path.chmod(0o700) ``` ```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") ``` The documentation states: ```text On POSIX systems the directory must be mode 0700 and both files mode 0600. On Windows the current user must be the only principal granted access through the file ACL. ``` ### Technical Analysis On POSIX systems, the client validates directory ownership and mode `0700`, opens the credential file without following symbolic links, and validates file ownership and mode `0600`. On Windows, it performs no equivalent ACL creation or verification and directly reads ...[truncated 1344 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create the Windows state directory and credential file with an explicit DACL limited to the current user and required system principals. 2. Disable inherited permissions where necessary and verify the effective ACL before storing or reading the token. 3. Fail closed with a clear remediation message when another non-administrative principal has read access. 4. Store the token through Windows Credential Manager or DPAPI rather than a plaintext JSON file where practical. 5. Keep non-secret installation metadata separate from bearer credentials. 6. Reduce token scope and lifetime so local disclosure has less impact. 7. Add automated tests covering permissive inherited ACLs, shared profiles, copied files, and ACL modification after authorization. 8. Align the documentation with the controls actually enforced by the implementation. ]]>
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 (25)

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill declares itself as a simple board-image generator, yet its documented operation requires shell execution, file access, network access, credential handling, and local state changes through a bundled client. Undeclared powerful capabilities reduce transparency and bypass least-privilege expectations, making it easier for a user or host to authorize risky behavior without understanding the true access scope.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
There is a major description-behavior mismatch: the skill is presented as producing bank desk board stills, but it also performs authentication flows, persistent credential storage, arbitrary remote tool interaction, file uploads, telemetry/registration, uninstall logic, and automatic updates. This can mislead users into granting trust to a content-generation skill that actually behaves like an installable networked client with broad system and account reach.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The documented automatic self-update mechanism is unrelated to the core task of creating bank desk board stills and introduces a software supply-chain risk inside a content skill. Even with claimed verification, silent download and local replacement create a path for unexpected code changes after approval, increasing the blast radius of any compromise in the update channel or validation logic.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
A manifest for a board-still generation skill should not also include remote update checks, downloads, and local package replacement without prominent disclosure and separate trust boundaries. This expands the skill from media generation into executable software lifecycle management, which materially increases risk to the local environment.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The changelog references ranking a top-up tier, hardcoded tier pricing, a top-up address, and newly added balance and ledger calls, which are unrelated to a board-image generation skill. This mismatch strongly suggests hidden financial/account functionality or repurposed code paths that expand the skill's effective privileges beyond user expectations.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
Read-only balance and ledger access is unjustified for generating branch window board graphics and creates unnecessary access to sensitive financial/account data. Even if no funds can be moved, exposure of balances or transaction history can leak private information and enable profiling or follow-on attacks.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The skill is described as generating bank desk board stills, but this script provisions a general Beatra account credential and MCP access instead of a narrowly scoped capability tied to that image task. That creates a privilege boundary mismatch: installing or authorizing a simple media skill grants reusable account access that could be leveraged for unrelated actions if the skill or surrounding tooling is compromised.

Context-Inappropriate Capability

Critical
Confidence
100% confidence
Finding
The requested OAuth scope includes artifacts write/read, videos, music, speech, voice management, wallet spending, and task operations, far beyond what a bank desk board image skill should need. Excessive scopes dramatically increase blast radius: a stolen or misused token could spend funds, manipulate artifacts, and access or trigger unrelated generation services.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The script detects host platform, reads the hostname, writes host metadata, and records local install paths into a shared skills inventory. While not remote code execution, this is unnecessary collection for a board-generation skill and creates privacy and reconnaissance value by persisting environment details and filesystem locations that could aid follow-on abuse.

Description-Behavior Mismatch

High
Confidence
92% confidence
Finding
The file implements a broad remote-control client for MCP operations, uploads, registration, telemetry, and package lifecycle management that goes far beyond the declared purpose of generating bank desk board graphics. In this skill context, that mismatch is dangerous because it grants a nominally creative package a general networked control plane and data movement capability, increasing the chance of unauthorized remote actions and making abuse harder for users to anticipate.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
The code can autonomously discover, download, validate, and replace local package files, including silent update paths via maybe_auto_update(). Even with integrity checks, self-modifying behavior in a board-layout skill materially expands the attack surface: compromise of the update channel or operational mistakes can change executable code on disk without a user-initiated install workflow.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
The code fingerprints the host environment from environment variables and host.json to derive a platform identifier for source attribution. In isolation this is not severe, but it is unrelated to the stated creative function and increases privacy and telemetry collection surface, especially when combined with remote registration and tool calls.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The client records local skill inventory and transmits installation telemetry/registration data to a remote service, which is unrelated to producing desk board graphics. In this context, undisclosed inventorying and telemetry are risky because they create persistent local tracking artifacts and outbound metadata flows users would not reasonably expect from a board-generation skill.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
This uninstall script manages and can delete shared global Beatra state, including credentials and installation metadata, even though the skill’s declared purpose is only board-generation. That creates an unnecessary privileged side effect: installing or removing a content-generation skill should not require direct authority over a shared device authorization store, because compromise, misuse, or logic errors could disconnect other skills or tamper with global agent state.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The code performs network-based OAuth/device-token revocation by sending a bearer token to a remote API endpoint during uninstall. For a bank desk board-generation skill, this is unrelated privileged behavior and expands the attack surface: a skill package gains the ability to trigger account/device disconnect operations over the network, which is dangerous if the package is modified, invoked unexpectedly, or distributed through an untrusted channel.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The script reads the shared access token from credentials.json and deletes multiple files under ~/.beatra, including credentials and global installation state. In the context of a simple graphics/board skill, this is over-privileged behavior that can remove shared authentication and operational metadata for the whole environment, potentially disrupting unrelated skills and erasing state needed for recovery or auditing.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The documentation explicitly states that the client silently checks for updates by default and automatically installs newer versions without separate confirmation. Even with checksum and source validation, unattended self-updating changes local files and execution behavior without an explicit just-in-time user warning, which creates supply-chain and user-consent risk if the update channel or signing/verification process is ever compromised.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The documentation describes an automatic installation-registration call that transmits package and environment metadata and writes a persistent local cache, but it does not mention any explicit user notice, opt-in, or easy opt-out. Even if the data is described as non-secret and non-billable, silent telemetry-like behavior can violate user expectations, privacy requirements, or enterprise policy, especially in security-sensitive agent environments.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
maybe_auto_update() performs best-effort silent updates and may replace installed package files before normal command handling, without a user-facing warning at execution time. Silent code changes are especially risky in a skill whose declared purpose is simple media generation, because users are unlikely to expect background modification of local executable content.

Credential Access

High
Category
Privilege Escalation
Content
},
  "mcp": {
    "authentication": "device-bearer",
    "credential_file": "~/.beatra/credentials.json",
    "name": "beatra",
    "transport": "streamable-http",
    "url": "https://mcp.beatra.ai/mcp"
Confidence
96% confidence
Finding
The manifest specifies use of a local credential file with device-bearer authentication for a remote MCP endpoint, giving the skill a path to authenticated account access. In the context of a simple board-pack generator, this credential dependency is unnecessary and dangerous because it can expose or misuse user-linked tokens and broaden the blast radius of any compromise.

Credential Access

High
Category
Privilege Escalation
Content
def _existing_credential(state_dir: Path) -> Path | None:
    path = state_dir / "credentials.json"
    try:
        path_stat = os.lstat(path)
        if not stat.S_ISREG(path_stat.st_mode):
Confidence
95% confidence
Finding
The code reads and validates a reusable bearer token from credentials.json, which is credential handling with significant security sensitivity. Although local credential storage is sometimes necessary, in this skill context the token is broad and shared, so compromise of the file or misuse by another component could grant substantial account access.

Credential Access

High
Category
Privilege Escalation
Content
scope = _required_string(polled, "scope")
            if set(scope.split()) != set(SCOPE.split()):
                raise RuntimeError("Beatra authorization returned an unsupported scope")
            credential_path = state_dir / "credentials.json"
            _atomic_json(
                credential_path,
                {
Confidence
97% confidence
Finding
The script writes an access token and related identifiers into credentials.json after device authorization, creating a persistent local bearer credential. Even with restrictive file permissions, this remains a high-value secret; if exfiltrated or accessed by another local process, it could be used to exercise the skill's broad granted scopes.

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
94% confidence
Finding
The presence of credentials.json in the set of files the script may remove indicates direct handling of shared credential material. Even though this occurrence is in a constant list, it documents capability to operate on authentication state that is unrelated to the skill’s advertised function, which is dangerous because a content-generation skill should not be entrusted with deleting shared secrets or their backing files.

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
99% confidence
Finding
This function directly opens ~/.beatra/credentials.json and extracts an access token for use in revocation. Direct credential access from a skill package is highly sensitive and unnecessary for the stated board-generation purpose; if abused or modified, it enables unauthorized use of bearer tokens and privileged account/device operations.

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
The command surface explicitly exposes self-update functionality, and elsewhere the code can replace package files on disk. Self-modification is dangerous in this context because a desk-board generation skill should not need to alter its own executable installation; this creates a durable persistence and supply-chain risk if the update mechanism or upstream service is compromised.

Static analysis

No suspicious patterns detected.