Back to skill

Security audit

Fund Factsheet Page

Security checks for vulnerabilities and agentic risk

Overview

This skill can generate fund factsheet page images, but it also uses a broad shared Beatra credential, sends device/package telemetry, and silently self-updates, so it should be reviewed before installation.

Install only if you are comfortable connecting a Beatra account, storing a shared full-scope Beatra token on disk, sending supplied fund content and optional reference files to Beatra, allowing package telemetry, and accepting silent package updates. Consider disabling automatic updates, using a Beatra account with limited credits, and revoking the device from the Beatra Console when finished.

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:30
Finding
Overprivileged Shared Device Credential Exceeds the Skill's Functional Requirements<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:30-34` **Vulnerability Type**: Excessive authorization scope and shared bearer credential **Risk Level**: High ### Evidence ```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 broad scope is subsequently required when validating an existing credential: ```python if ( not isinstance(value, dict) or value.get("schema_version") != 1 or value.get("mcp_url") != MCP_URL or value.get("token_type") != "Bearer" or any(not isinstance(value.get(name), str) or not value[name] for name in required_strings) or set(value["scope"].split()) != set(SCOPE.split()) ): return None ``` Documentation confirms that this is a shared, full-scope token: ```text They share the one full-scope Device Token stored in ~/.beatra/credentials.json. ``` ### Technical Analysis The declared functionality is the creation and editing of fund-factsheet page images. The credential nevertheless grants access to unrelated capabilities, including: - Video generation - Music generation - Speech generation - Voice-resource reading and modification - General wallet spending - Artifact reading and writing - Task reading and cancellation The authorization helper does not merely request these permissions opportunistically. It rejects an existing token unless its scope exactly equals the complete broad scope, preventing operation with a more restricted credential. The token is also shared across Beatra Skill packages. This expands the trust boundary: compromise of this Skill, another package using the same credential, or the package's update channel could expose permissions unrelated to fund-page image generation. This violates the principle of least privilege. The legitimate workflow appears to require image generation and editing, model disc ...[truncated 1402 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the shared full-scope token with a package-specific credential. 2. Request only the capabilities required by this Skill, such as: - Image generation and editing - Model discovery - Uploading explicitly selected reference images - Reading tasks created by this package 3. Remove video, music, speech, voice-write, and unrelated task-cancellation permissions. 4. Scope wallet spending to this package's approved operations rather than granting general spending authority. 5. Do not require exact equality with a globally broad scope. Validate that the token contains the minimum required scopes instead. 6. Require separate, explicit user authorization before adding any optional capability. 7. Isolate task and artifact access by package or installation reference where supported. 8. Rotate existing broad tokens after deploying the reduced-scope authorization model. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/authorize.py:350
Finding
Hostname and Agent-Environment Reconnaissance Is Collected and Transmitted<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:350-369`, with transmission at `scripts/authorize.py:444-468` and recurring platform attribution at `scripts/mcp_client.py:1220-1229` **Vulnerability Type**: Environment reconnaissance and device telemetry beyond the minimum creative workflow **Risk Level**: Medium ### Evidence The authorization helper fingerprints the agent environment and reads the local hostname: ```python 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] ``` The collected values are added to 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 platform value is also attached to every business tool call: ```python if method == "tools/call": arguments = params.get("arguments") if isinstance(arguments, dict): arguments.setdefault("source_package_slug", PACKAGE_SLUG) arguments.setdefault("source_platform", host_platform()) ``` ### Technical Analysis The code inspects process-environme ...[truncated 2238 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove automatic hostname collection from the authorization flow. 2. If a recognizable device label is useful, ask the user to provide an optional label and default to a generic value. 3. Make agent-platform telemetry opt-in rather than automatically inspecting environment signatures. 4. Default `source_platform` to `unknown` when the user has not consented to telemetry. 5. Do not persist `device_name` unless it is operationally necessary. 6. Clearly disclose: - Every collected field - The destination service - The purpose of collection - Retention duration - Whether the field is attached to business activity 7. Minimize correlation by using a rotating or package-specific identifier where a stable device identifier is unnecessary. 8. Provide a supported way to disable and delete telemetry without invalidating the creative functionality. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Default Silent Self-Update Can Replace Executable Skill Code Without Independent Release Authentication<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1018`, with update trust roots at `scripts/mcp_client.py:31-32` and automatic invocation at `scripts/mcp_client.py:1543-1544` **Vulnerability Type**: Remote payload retrieval and executable package replacement **Risk Level**: High ### Evidence The update sources are fixed remote URLs: ```python PACKAGE_DISCOVERY_URL = "https://beatra.ai/skills/fund-page-set/channels/clawhub/install.json" PACKAGE_CDN_BASE_TEMPLATE = "https://cdn.beatra.ai/agent-packages/fund-page-set/channels/clawhub/v{version}" ``` The complete automatic update function silently downloads and applies a newer release: ```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) ...[truncated 4450 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default. 2. Require explicit user approval for each release before replacing package files. 3. Sign release manifests with a detached digital signature. 4. Pin the corresponding public verification key in the audited package. 5. Verify the signature before trusting version numbers, file lists, URLs, or hashes. 6. Separate release-signing authority from discovery and CDN publishing authority. 7. Use a transparency log or reproducible release process so published versions can be independently audited. 8. Do not silently replace the updater itself. Use a separately trusted update mechanism or require explicit approval. 9. Before installation, display: - Current and proposed versions - Signer identity - Changed files - Permission or scope changes - Release digest 10. Preserve the existing path, archive, size, ownership, rollback, and redirect defenses; these are useful but do not replace cryptographic publisher authentication. 11. Ensure failed updates generate visible diagnostics rather than being silently suppressed in all cases. 12. Recommend that existing users run `python3 scripts/mcp_client.py update --auto off` until authenticated updates are available. ]]>
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (26)

Lp3

Medium
Category
MCP Least Privilege
Confidence
97% confidence
Finding
The skill invokes a bundled Python client and explicitly documents capabilities that imply shell execution, network access, file reads/writes, and credential/state storage, yet it declares no permissions. This creates a transparency and consent failure: users and hosts may treat it as a simple page-layout skill while it can perform broader local and remote actions, including credential handling and package modification.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The manifest frames the skill as a fund factsheet image generator, but the documented behavior expands into OAuth login, persistent bearer credential storage, arbitrary remote MCP tool invocation, local uploads, registration/telemetry, uninstall/revocation logic, and self-update. That mismatch is dangerous because it obscures the true trust boundary and can induce users to grant access or run code they would not expect from a simple graphics workflow.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The skill includes an automatic self-update mechanism that downloads and replaces local package files, which is unrelated to generating factsheet pages. Any self-modifying mechanism increases supply-chain risk; if the update channel, signing, verification, or distribution is compromised, the skill can change behavior after installation without a fresh review by the host or user.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The manifest presents a simple page-layout/still-generation skill, yet it also provisions authenticated access to a remote MCP endpoint. That creates an unexpected trust boundary and data egress path: user-supplied fund content and session context may be transmitted to an external service despite the functionality not obviously requiring networked privileged access.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
Authenticated remote MCP access is not justified by the stated purpose of converting supplied fund factsheet points into static page stills. This mismatch increases the likelihood of overprivileged design, unnecessary collection of sensitive document content, or abuse of the authenticated channel for actions unrelated to the user-visible task.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The file documents a full remote authorization and credential-handling workflow for Beatra, including browser-based device authorization, token polling, persistent storage, and MCP access. In a skill whose stated purpose is generating fund factsheet page layouts, this is materially out of scope and expands the trust boundary to remote account linkage and network access, creating a strong supply-chain/backdoor concern if the skill induces users or agents to authenticate to an unrelated service.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The skill instructs the host to create persistent local credential files and obtain a reusable Device Token for remote service access, despite no obvious relationship to rendering fund stills. This unnecessary persistence and account-linking capability increases the chance of unauthorized service use, token abuse, and hidden data exfiltration through bundled network tooling.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The authorization flow requests a very broad OAuth scope set including wallet spending, voice management, speech/music/video generation, and task/artifact operations that are not necessary for a skill whose stated purpose is turning fund factsheet points into page stills. Over-scoped tokens violate least privilege and materially increase blast radius if the token is stolen, misused by the skill, or later reused by other components sharing the same credential store.

Context-Inappropriate Capability

Medium
Confidence
82% confidence
Finding
The script detects host platform from environment variables and captures a device-identifying hostname, then persists this metadata locally and sends the device name in the authorization request. For a page-layout skill, this collection is unnecessary to core functionality and creates avoidable privacy and fingerprinting exposure that can aid tracking across installations.

Context-Inappropriate Capability

Medium
Confidence
85% confidence
Finding
The skill records a local inventory of installed skills and their absolute install paths in ~/.beatra/skills.json, which exceeds the declared page-generation purpose and creates a privacy-sensitive local software inventory. That information can expose user environment details, workspace layout, and other installed packages, which increases risk if the file is later accessed by another local process or exfiltrated.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
The file claims to support a fund factsheet page skill, but actually implements a generic authenticated remote client with tool invocation, uploads, telemetry, and self-update. This is dangerous because it materially exceeds the declared scope of the skill, giving the package a broad remote-control surface and code replacement path that users would not reasonably expect from a page-layout helper.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
The code fingerprints the runtime environment using environment variables and local host state to classify the agent platform. In the context of a fund-page layout skill, this is unnecessary collection that can support tracking, behavioral profiling, or conditional server-side behavior unrelated to the advertised creative function.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The client records a local skill inventory and sends installation registration telemetry on use, which is unrelated to producing fund factsheet pages. In this skill context, the mismatch increases risk because the package silently persists metadata about installed skills and reports usage state to a remote service beyond the user's expected task.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
The uninstall script is capable of revoking a shared Beatra device credential and removing global state under ~/.beatra, which affects more than just this skill package. For a skill whose declared purpose is page-layout generation, this broad control is outside expected scope and can disrupt other installed skills or shared platform access if inventory/state assumptions are wrong.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
This code performs network-based OAuth device-token revocation during uninstall, giving the skill the ability to invalidate shared authorization with the remote service. In the context of a graphics/layout skill, that capability is unnecessary and expands blast radius from local package removal to remote account connectivity changes.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The script enumerates and deletes shared files including credentials, installation, host, skills, and registrations data from ~/.beatra. Even though it tries to be conservative, a content-generation skill should not directly manage or remove shared platform state because mistakes or tampering could break unrelated skills and erase important local configuration.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The skill states that newer versions install automatically without separate confirmation and replace package-owned files locally. Silent code changes without an explicit user-facing warning undermine change control and informed consent, and they can bypass host review expectations for skills that users assume are static after installation.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The document explicitly states that the client performs silent automatic installation of updates by default, without separate confirmation. Even though it describes integrity checks and rollback protections, automatically modifying installed code before ordinary commands materially changes the system and expands the trust boundary, creating supply-chain and unexpected code execution risk if the update channel or signing/checking process is ever compromised.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The document describes automatic first-use registration that transmits package slug, version, platform, and a stable external installation reference without any explicit user notice or opt-in. Even if labeled non-billable and non-secret, this creates unsolicited outbound telemetry and persistent installation tracking metadata, which can violate privacy expectations and enterprise deployment requirements.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The documentation states that the package uses a single full-scope device token from a shared credential store and automatically attaches telemetry fields on tool calls, but it does not clearly warn users about the sensitivity of that credential or the privacy implications of source attribution. In a skill package, normalizing use of a broad-scoped shared token without prominent consent and scope-minimization guidance increases the risk of over-privileged access, unintended cross-package actions, and silent metadata disclosure.

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
95% confidence
Finding
Referencing a local credential file for device-bearer authentication gives the skill a path to use existing user credentials against a remote service. In the context of a content-layout skill, this is especially risky because it can silently leverage stored tokens to access external resources or transmit data without a clear functional need, increasing the blast radius if the skill or backend is abused.

Credential Access

High
Category
Privilege Escalation
Content
- `~/.beatra/installation.json` contains one stable, non-secret installation
  reference.
- `~/.beatra/credentials.json` contains the single Device Token.

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
Confidence
90% confidence
Finding
The document specifies storage of a long-lived Device Token in a fixed path under the user's home directory. Even though it recommends restrictive permissions, introducing persistent bearer-token storage into an unrelated layout skill increases credential exposure risk if the host, filesystem, backups, or other local processes are compromised.

Credential Access

High
Category
Privilege Escalation
Content
4. polls every 5 seconds for up to 15 minutes while the user signs in (or
   creates their account) and selects Allow;
5. atomically saves the returned Device Token to
   `~/.beatra/credentials.json` without printing an HTTP response body;
6. validates the new credential with the same non-billable MCP request and
   prints Ready only after it succeeds.
Confidence
91% confidence
Finding
The instruction to save the returned Device Token atomically to a local credentials file confirms active credential acquisition and persistence by the skill. In context, this is dangerous because it establishes durable remote access capability that is unnecessary for a fund-page layout tool and could later be abused for unauthorized calls to the external MCP service.

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
84% confidence
Finding
Referencing credentials.json as part of state removal indicates the skill can directly interact with shared authentication material. While the script is uninstall-focused rather than exfiltrating secrets, access to shared credential storage is still sensitive and inappropriate for a fund-page rendering skill, increasing the risk of denial of service or future misuse.

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
91% confidence
Finding
The _device_token function reads the access token from credentials.json so it can be used for revocation requests. Reading a bearer token into skill-controlled code creates unnecessary credential exposure for a package unrelated to authentication, and any compromise or modification of the script could turn this into direct credential abuse.

Static analysis

No suspicious patterns detected.