Back to skill

Security audit

Crowdfunding Gallery Stills

Security checks for vulnerabilities and agentic risk

Overview

The skill can generate crowdfunding gallery images, but it also grants broad Beatra account powers and silently replaces its own package files, so it needs careful review before installation.

Install only if you are comfortable granting a Beatra device credential with broad account capabilities, automatic package replacement from Beatra infrastructure, and persistent local state under ~/.beatra. Consider disabling automatic updates immediately with scripts/mcp_client.py update --auto off, review the approval scopes carefully, and avoid uploading sensitive local files as references.

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:32
Finding
Overbroad Device Authorization and Unrestricted Remote Tool Dispatch<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:32-36`; `scripts/mcp_client.py:1463-1482` **Vulnerability Type**: Excessive authorization scope and missing 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 Skill is declared as a crowdfunding still-image generator, but its authorization request includes privileges for video, music, speech, voice creation and modification, general artifact writing, wallet spending, artifact and task reading, and task cancellation. The bundled MCP client compounds this excessive scope by accepting an arbitrary tool name from the command line and forwarding it through `tools/call`. There is no local allowlist restricting calls to the image-generation, image-editing, model-listing, upload, task-read, and billing operations needed by the declared workflow. Although the server may independently enforce token scopes and tool-specific authorization, the local implementation does not provide a package-level least-privilege boundary. ...[truncated 1305 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the full shared scope with the smallest set required by this Skill, limited to: - Image generation and editing. - Model-card reads. - Explicit reference-image upload. - Task reads for polling and recovery. - Narrow billing reads and the minimum spending permission required for approved generation. 2. Remove video, music, speech, voice-write, broad artifact, and task-cancel privileges unless a documented workflow requires each one. 3. Introduce a local exact-match allowlist for permitted tool names. 4. Reject every unrecognized tool before opening an authenticated session. 5. Separate task cancellation into an explicitly confirmed path if it must remain available. 6. Prefer package-specific tokens rather than a full-scope credential shared by multiple Skills. 7. Display the requested permissions to the user before beginning Device Authorization. 8. Add tests proving that unrelated tool names and scopes are rejected. ]]>

other

Warning
Location
scripts/authorize.py:342
Finding
Automatic Collection and Transmission of Persistent Device Metadata<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:342-370`, `scripts/authorize.py:441-469`; `scripts/mcp_client.py:1354-1398` **Vulnerability Type**: Environment reconnaissance and persistent installation telemetry **Risk Level**: Medium ### 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 external_reference = _installation_reference(state_dir) 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) ``` ```python result = s ...[truncated 2653 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove hostname collection unless it is strictly necessary for a user-requested device-management feature. 2. Use a user-selected display name rather than automatically transmitting the system hostname. 3. Make installation telemetry opt-in and separate it from required authentication and creative operations. 4. Clearly disclose every transmitted field before authorization, including hostname, platform, package version, and persistent identifier. 5. Use an ephemeral or package-scoped identifier where long-term device correlation is unnecessary. 6. Minimize environment inspection and avoid scanning all keys for platform signatures. 7. Provide a configuration option that disables registration and source-attribution telemetry without disabling image generation. 8. Define retention, deletion, and purpose limitations for installation metadata. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Silent Executable Self-Updates Lack Independent Publisher Signature Verification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:29-46`, `scripts/mcp_client.py:469-490`, `scripts/mcp_client.py:969-1020`, `scripts/mcp_client.py:1543` **Vulnerability Type**: Mutable remote code retrieval and automatic package replacement **Risk Level**: High ### Vulnerable Code ```python PACKAGE_CHANNEL = "clawhub" PACKAGE_LOCALE = "en" PACKAGE_DISCOVERY_URL = "https://beatra.ai/skills/indiegogo-gallery-set/channels/clawhub/install.json" PACKAGE_CDN_BASE_TEMPLATE = "https://cdn.beatra.ai/agent-packages/indiegogo-gallery-set/channels/clawhub/v{version}" ``` ```python def download_update( discovery: dict[str, Any], *, get_bytes: GetBytes = _default_get_bytes, ) -> tuple[dict[str, Any], dict[str, bytes]]: archive_url, manifest_url = _release_urls(discovery) manifest_content = get_bytes( manifest_url, UPDATE_DOWNLOAD_TIMEOUT_SECONDS, MAX_UPDATE_MANIFEST_BYTES, ) if _sha256(manifest_content) != discovery["manifest_sha256"]: raise RuntimeError("Beatra update manifest checksum does not match discovery") manifest = _json_object(manifest_content, "Beatra update manifest") manifest_files = _manifest_files(manifest, discovery=discovery) archive = get_bytes( archive_url, UPDATE_DOWNLOAD_TIMEOUT_SECONDS, MAX_UPDATE_ARCHIVE_BYTES, ) if _sha256(archive) != discovery["archive_sha256"]: raise RuntimeError("Beatra update archive checksum does not match discovery") return manifest, _validated_archive(archive, manifest_files=manifest_files) ``` ```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_in ...[truncated 4117 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default; make update checks and installation explicit user actions. 2. Sign release metadata and archives with a publisher key whose public key is pinned in the audited package. 3. Verify signatures before trusting version numbers, URLs, checksums, manifests, or archives. 4. Protect signing keys using offline or hardware-backed release procedures and publish revocation guidance. 5. Use reproducible builds and publish transparency-log entries for every release. 6. Display the current version, target version, changed files, and signer identity before replacement. 7. Require explicit confirmation when executable files or `SKILL.md` will change. 8. Retain the existing redirect, path-traversal, symlink, ownership, size, rollback, and checksum protections as defense in depth. 9. Treat update failures as visible security events rather than silently suppressing all exceptions. 10. Allow administrators to pin an approved version and disable all network update checks. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:231
Finding
MCP-Controlled Upload Grants Permit Arbitrary HTTPS Destinations<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:231-265` **Vulnerability Type**: Insufficient validation of remote upload destination **Risk Level**: Medium ### Vulnerable Code ```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 MCP server returns an upload URL and headers. The client verifies that the method is `PUT`, t ...[truncated 1883 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Maintain an exact allowlist of approved upload hostnames and reject all other destinations. 2. Validate the effective port and require standard HTTPS unless a documented storage endpoint requires otherwise. 3. Bind upload grants cryptographically to: - Destination hostname and path. - File SHA-256 digest. - MIME type. - Exact byte length. - Expiration time. - Single-use nonce. 4. Verify the grant signature locally using a pinned service key. 5. Reject unexpected or security-sensitive server-provided headers rather than forwarding an arbitrary header dictionary. 6. Show the destination domain and file name to the user before transmitting sensitive reference files. 7. Add tests covering attacker-controlled domains, Unicode hostname confusion, nonstandard ports, redirects, expired grants, and altered file digests. 8. Preserve the existing regular-file, symlink, size, and race-condition protections. ]]>
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 (21)

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill declares no permissions while instructing use of a bundled client that can access the environment, read and write files, invoke shell commands, and make network requests. That mismatch removes an important trust boundary for users and reviewers, making it easier for powerful operations to occur without informed consent.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The stated purpose is image generation for crowdfunding gallery stills, but the skill also performs credentialed authentication flows, persistent state storage, file upload, telemetry/registration, self-update, and uninstall/revocation behaviors. This is a material expansion of scope that can surprise users and create additional attack surface, especially because it introduces credential handling, remote communications, and local system modification unrelated to the narrow creative task.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The skill is described as generating crowdfunding gallery stills, but the requested OAuth scope includes broad capabilities far beyond that purpose, including wallet spending, task control, artifact access, video/music/speech generation, and voice management. Over-scoped authorization violates least privilege and materially increases blast radius if the skill, host, or stored credential is abused.

Context-Inappropriate Capability

Critical
Confidence
94% confidence
Finding
The requested mcp:tools scope is broader than the narrowly described image-studio use case and may expose general tool capabilities not required for rendering gallery stills. In a shared credential model, unnecessary tool access increases the attack surface and can enable unintended actions through the platform.

Context-Inappropriate Capability

High
Confidence
94% confidence
Finding
The requested mcp:tools scope is broader than the narrowly described image-studio use case and may expose general tool capabilities not required for rendering gallery stills. In a shared credential model, unnecessary tool access increases the attack surface and can enable unintended actions through the platform.

Context-Inappropriate Capability

High
Confidence
94% confidence
Finding
The requested mcp:tools scope is broader than the narrowly described image-studio use case and may expose general tool capabilities not required for rendering gallery stills. In a shared credential model, unnecessary tool access increases the attack surface and can enable unintended actions through the platform.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The requested mcp:tools scope is broader than the narrowly described image-studio use case and may expose general tool capabilities not required for rendering gallery stills. In a shared credential model, unnecessary tool access increases the attack surface and can enable unintended actions through the platform.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The skill includes a full self-update mechanism that downloads manifests and archives from remote infrastructure and replaces local installation files, behavior unrelated to generating crowdfunding gallery stills. Even with checksum and path validation, this materially expands trust and attack surface: compromise of the vendor update channel or signing/distribution process leads to arbitrary code replacement on the host.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The skill records installation telemetry and local inventory data unrelated to its stated image-generation purpose, including package/version/platform and local install path. This creates unnecessary data collection and device profiling, increasing privacy risk and broadening the consequences of backend compromise or misuse.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The code fingerprints the host environment via environment variables and persisted host metadata to classify the agent platform, which is not necessary for a gallery-stills skill. Environment fingerprinting can be used for tracking, selective behavior, or targeting, and it increases privacy and policy concerns when bundled into unrelated functionality.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The client exposes a generic passthrough for listing tools and calling an arbitrary tool name with JSON from stdin, which exceeds the justified scope of a crowdfunding gallery-stills skill. This turns the package into a general remote capability broker, potentially enabling unintended operations through the MCP backend if the package is invoked in broader contexts.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The skill package contains uninstall logic that manages shared Beatra device credentials and authorization state, which is outside the stated purpose of generating crowdfunding gallery stills. Even if framed as lifecycle management, bundling credential and connection revocation into an unrelated content-generation skill expands the skill's authority and creates unnecessary security-sensitive behavior.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
This code can revoke a shared device token and delete local state files under ~/.beatra, affecting other skills and the broader agent environment rather than just this package. In the context of an image/gallery skill, this is over-privileged behavior that could be abused to disrupt service, remove authentication state, or cause denial of service across installed skills.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The skill states that its bundled client can automatically install newer releases without separate confirmation and replace package-owned files. Even with signature verification, silent self-update is a system-modifying behavior that increases supply-chain risk and can change code after review, undermining user expectations and security auditing.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The document explicitly states that the client performs silent, enabled-by-default update checks and installs newer versions automatically without separate confirmation. Even though it describes integrity checks and rollback protections, silent self-updating changes executable package files without an explicit user approval step, which creates supply-chain and change-management risk if the trusted update source or signing process is ever compromised.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The document states that the bundled client automatically performs a network registration call on first use and writes a local cache file, but it does not include a clear user-facing disclosure, consent, or warning. Even if the transmitted fields are described as non-secret and non-billable, silent telemetry and file creation can violate user expectations, privacy requirements, or enterprise policy, especially in security-sensitive environments.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The helper drives the user through device authorization but does not clearly enumerate the extensive privileges being granted, despite requesting broad account-scoped access. That weakens informed consent and makes it easier for users to authorize risky capabilities they would not expect from a gallery-stills skill.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill can silently auto-update its installed files during normal execution without user-visible confirmation at the moment changes occur. Silent code replacement is especially risky in a creative skill context because users do not expect package-management side effects when invoking generation-related actions.

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
89% confidence
Finding
Referencing credentials.json as part of the files this script may delete indicates the skill is aware of and operates on shared credential material. Although this line alone is declarative, within this file it contributes to a real capability to manipulate authentication state that is unnecessary for the advertised gallery-generation purpose.

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
98% confidence
Finding
The _device_token function reads an access token from ~/.beatra/credentials.json so the script can use it to call the revocation endpoint. Reading bearer tokens is credential access, and in a skill whose purpose is generating campaign imagery, this is unrelated privileged behavior that increases the blast radius if the skill is modified, repurposed, or invoked unexpectedly.

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
96% confidence
Finding
The exposed self-update command formalizes a pathway for the skill to replace its own local code, which is high-risk functionality unrelated to the declared gallery-stills purpose. Any weakness or compromise in the remote update supply chain could be converted into host-side code execution through a trusted package update flow.

Static analysis

No suspicious patterns detected.