Back to skill

Security audit

Shopify Product-Page Stills

Security checks for vulnerabilities and agentic risk

Overview

This skill does create Shopify product images, but it also grants broad Beatra account authority and silently updates its own executable files by default.

Review before installing. Only use this skill if you are comfortable with a shared Beatra device token that can authorize more than Shopify image generation, with possible credit-spending authority, and with automatic package updates enabled by default. Consider disabling automatic updates with the documented update command and using it only in an environment where Beatra credential sharing and telemetry are acceptable.

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:34
Finding
Over-Privileged Device Token and Unrestricted Remote Tool Invocation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:34-38`; `scripts/mcp_client.py:1463-1480`; `scripts/mcp_client.py:1487-1490` **Vulnerability Type**: Excessive authorization scope and unrestricted MCP tool dispatch **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 tool name supplied by the caller: ```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 creates Shopify product-page still images. Its legitimate runtime requirements include image generation, model-card lookup, optional image upload, and relevant task and billing reads. The authorization request nevertheless obtains unrelated capabilities for video, music, speech, voice management, broad wallet spending, and task cancellation. The local client compounds this excessive scope by accepting an arbitrary MCP tool name without an allowlist. Possession of the shared token therefore gra ...[truncated 1415 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the shared full-scope token with a package-specific, least-privilege token. 2. Remove unrelated scopes, including: - `videos:generate` - `music:generate` - `speech:generate` - `voices:read` - `voices:write` 3. Limit spending authority to explicitly approved image-generation operations. 4. Add a hardcoded tool allowlist containing only the operations required by this Skill, such as: - `beatra.models.list` - `beatra.assets.upload` - `beatra.images.generate` - `beatra.images.edit` - Required task, wallet, and installation-registration reads 5. Reject unknown tool names locally before creating an MCP session. 6. Separate read-only operations, billable operations, and task cancellation into distinct permission grants. 7. Require explicit user confirmation at the enforcement layer for each billable or destructive operation rather than relying only on Skill instructions. 8. Avoid sharing one broadly privileged credential across unrelated packages. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Silent Remote Package Retrieval and Executable Code Replacement<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:31-32`, `scripts/mcp_client.py:518-522`, `scripts/mcp_client.py:801-916`, `scripts/mcp_client.py:969-1019`, `scripts/mcp_client.py:1541-1543` **Vulnerability Type**: Automatic retrieval and installation of remotely controlled executable package content **Risk Level**: High ### Vulnerable Code The update sources are remotely hosted: ```python PACKAGE_DISCOVERY_URL = "https://beatra.ai/skills/shopify-pdp-set/channels/clawhub/install.json" PACKAGE_CDN_BASE_TEMPLATE = "https://cdn.beatra.ai/agent-packages/shopify-pdp-set/channels/clawhub/v{version}" ``` Automatic updating defaults to enabled when update state is absent or invalid: ```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 ``` The automatic update path downloads and applies replacement package files: ```python 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) _apply_update( install_root=resolved_root, update_home=update_home, discovery=discovery, manifest=manifest, new_files=new_files, ) return True ``` Ordinary non-update commands trigger the update routine: ```python else: maybe_auto_update() ``` Package files, including scripts, are replaced in the installation directory: ```python for relative in ordered_new: destination = destinations[relative] _copy_to_dest ...[truncated 2311 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic package replacement by default. 2. Require explicit user confirmation before downloading or installing each update. 3. Display the current version, target version, release origin, and affected files before installation. 4. Sign release manifests with an offline or otherwise independently protected signing key. 5. Embed or securely provision the trusted public verification key in the audited package. 6. Reject updates whose signatures cannot be validated independently of the discovery server. 7. Support rollback protection using signed version metadata rather than relying only on semantic-version comparison. 8. Separate update checking from installation so ordinary business commands never modify executable files. 9. Prefer updates through the host platform's established package-management and review process. 10. Preserve existing archive-size, path-validation, checksum, lock, and rollback protections as defense in depth. ]]>

other

Warning
Location
scripts/authorize.py:337
Finding
Unnecessary Hostname and Agent-Environment Telemetry<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:337-370`, `scripts/authorize.py:453-468`; `scripts/mcp_client.py:1146-1168`, `scripts/mcp_client.py:1220-1229` **Vulnerability Type**: Environment reconnaissance and device-identifying telemetry **Risk Level**: Medium ### Vulnerable Code The authorization helper inspects agent-related environment variables: ```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" ``` It also 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] ``` The hostname is included in the remote device-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, " ...[truncated 2343 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove hostname collection from the authorization process. 2. Use a random, non-identifying device label or ask the user to choose an optional display name. 3. Make platform and installation telemetry opt-in rather than automatic. 4. Do not inject telemetry into every business tool call unless it is operationally necessary. 5. Clearly disclose every transmitted field, its purpose, retention period, and correlation behavior before authorization. 6. Minimize environment inspection to an explicit user-supplied platform value where platform information is genuinely required. 7. Provide a configuration option to disable registration and source-attribution telemetry without disabling core image generation. 8. Avoid storing the hostname in `~/.beatra/host.json`. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:1044
Finding
Windows Bearer-Token Permissions Are Assumed Rather Than Enforced<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:120-132`; `scripts/mcp_client.py:1044-1052` **Vulnerability Type**: Inadequate local credential access-control validation on Windows **Risk Level**: Medium ### Vulnerable Code Authorization only applies explicit access modes on POSIX: ```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) def _restrict_file(path: Path) -> None: if os.name == "posix": path.chmod(0o600) ``` The MCP client reads the credential on Windows without validating ownership, ACLs, or reparse-point behavior: ```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") ``` ### Technical Analysis The bearer credential provides access to the remote Beatra account and has broad scopes. On POSIX, the implementation enforces owner-only directory and file modes and validates ownership before reading. On Windows, equivalent enforcement is absent; th ...[truncated 1520 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create `~/.beatra` with an explicit Windows ACL granting access only to the current user and required system administrators. 2. Apply an explicit owner-only ACL to `credentials.json` after atomic creation and replacement. 3. Verify the effective ACL before every credential read. 4. Reject the credential when unauthorized users or groups have read, write, delete, or inherited access. 5. Validate file ownership and reject reparse points, junctions, and symbolic links for both the state directory and credential file. 6. Use Windows-native secure file APIs rather than relying on POSIX mode arguments, which do not provide equivalent DACL enforcement. 7. Fail closed when ACL validation cannot be completed. 8. Update documentation so its Windows security guarantee matches the behavior 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 (19)

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill exposes powerful capabilities including shell, filesystem, environment, and network access without declaring or constraining them. That creates a large hidden trust boundary: a user invoking what appears to be an image-generation workflow may unknowingly grant a bundled client broad local and remote access, increasing the risk of credential access, arbitrary file handling, and unexpected network actions.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The documented behavior substantially exceeds the stated purpose: beyond generating Shopify PDP images, it performs authentication flows, persistent credential storage, local file upload, generic remote tool invocation, telemetry/registration, uninstall state management, and software updates. This mismatch is dangerous because users and orchestration systems may trust and route to the skill under a narrow commerce-image use case while the bundled client can perform far broader actions on the host and against remote services.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The skill includes silent automatic update behavior that downloads and installs newer releases without separate confirmation, which is unrelated to the immediate Shopify image task. Any self-updating executable path materially raises supply-chain risk: if the update channel, signing, packaging, or account behind the distribution is compromised, arbitrary code can be introduced into the host environment under the guise of routine skill use.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The skill requests a very broad OAuth scope set that includes capabilities unrelated to generating Shopify PDP images, such as tasks control, voice access, speech, music, and wallet spending. For a single-purpose product-image skill, this violates least privilege and increases blast radius if the credential is misused, stolen, or if the skill later invokes unrelated APIs.

Context-Inappropriate Capability

Critical
Confidence
100% confidence
Finding
Requesting wallet:spend together with unrelated media-generation scopes is especially dangerous because it authorizes financial spend and broad API access far beyond the stated purpose of creating product-page stills. In the context of a Shopify PDP image skill, these permissions are unjustified and could enable unauthorized charges or abuse of the user's account if the token is compromised or the skill behaves unexpectedly.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
This client includes a full remote self-update and installation-management subsystem unrelated to the stated purpose of generating Shopify PDP image sets. Even with checksum and path checks, embedding code that can replace package files from the network materially expands the trust boundary and gives the vendor ongoing code-execution capability on the host.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The client records local skill inventory and transmits installation telemetry that is not necessary for producing PDP images. Collecting and persisting package presence, install paths, platform, and registration metadata increases privacy risk and creates unnecessary side-channel visibility into the user's environment.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The skill fingerprints its host environment by inspecting environment variables and local host metadata, then attaches the result to tool calls and telemetry. For a single-SKU image-set skill this capability is unjustified and increases tracking, profiling, and environment-disclosure risk without clear functional necessity.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The uninstall script contains functionality to manage and potentially revoke a shared Beatra device authorization, which is unrelated to the skill’s declared purpose of generating Shopify product-page image sets. Even though this is framed as uninstall behavior, it gives the skill package access to shared authentication state and lifecycle control over other skills’ connectivity, which materially expands trust and blast radius beyond the advertised scope.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
This code can send a bearer token to a remote revocation endpoint and disable a shared device connection over the network. In a skill whose stated purpose is image generation for Shopify PDPs, network-capable credential revocation is unjustified and dangerous because compromise, misuse, or packaging deception could disconnect other installed skills and interfere with the user’s broader environment.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The script reads access tokens from shared credentials.json and deletes multiple shared state files under ~/.beatra, including installation and registration metadata. Accessing and removing shared credential/state material from within a narrowly scoped Shopify media skill violates least privilege and creates risk of account disruption, loss of platform state, and cross-skill denial of service.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill describes silent automatic software updates without prominent warning in the main behavior description, which undermines informed consent. Users may believe they are only generating images while the tool can also modify its own local codebase in the background, increasing operational and supply-chain risk especially in environments that assume skills are static and reviewable.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The document explicitly states that the client performs silent, default-enabled automatic update checks and installs higher versions without separate confirmation. Even with integrity checks and rollback protections, automatically modifying local software before ordinary commands creates a supply-chain and user-consent risk because code can change unexpectedly and execute in the user's environment without an explicit opt-in at install or update time.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The documentation states that the client performs an automatic installation registration call and writes a local cache file, but it does not clearly warn users up front that metadata is transmitted off-host and that a file is created in the user's home directory. While the transmitted fields are described as non-secret, undisclosed telemetry-like behavior can undermine user consent expectations and create privacy, compliance, or trust issues in sensitive environments.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
Automatic silent updates can modify installed code during normal command execution without a user-facing warning at the time of change. In a skill whose expected function is image generation, silently replacing package files creates a software supply-chain risk and undermines user control over what code runs.

Missing User Warnings

Low
Confidence
89% confidence
Finding
The skill writes local inventory records and performs registration telemetry without a clear user-facing warning. While lower severity than self-update, this still creates undisclosed persistence and data-sharing behavior outside the skill's advertised PDP-image function.

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
95% confidence
Finding
The presence of credentials.json in the set of files targeted for deletion indicates that the package is aware of and operates on shared credential storage. In this skill context, touching shared credential artifacts is unnecessary for Shopify image-set generation and increases the danger because an uninstall path can remove authentication material used by other skills.

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
The _device_token function explicitly reads an access token from shared credentials.json, giving this package the ability to use bearer credentials outside its declared business function. In the context of a Shopify PDP image skill, direct credential access is especially suspicious because it is not needed for local media generation and enables sensitive account-affecting actions.

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
Exposing self-modification capability in a skill client is dangerous because it lets remote package content alter local executable files after installation. In the context of a narrowly scoped Shopify PDP image skill, this is far outside expected behavior and significantly increases supply-chain and persistence risk if the update channel is ever compromised or misused.

Static analysis

No suspicious patterns detected.