Back to skill

Security audit

SKU Comparison Charts

Security checks for vulnerabilities and agentic risk

Overview

This chart-making skill needs Review because it combines a normal image workflow with broad Beatra account authority, persistent shared credentials, telemetry, generic remote tool calls, and silent self-updates.

Review carefully before installing. Use this only if you trust Beatra with a persistent shared device credential and broad account capabilities beyond SKU image generation. Consider disabling auto-updates with scripts/mcp_client.py update --auto off, avoid uploading sensitive local files, and revoke the Beatra device authorization from the Beatra Console when you no longer need it.

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
Overprivileged Shared Device Token and Unrestricted MCP Tool Dispatch<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:32-37`; `scripts/mcp_client.py:1462-1483` **Vulnerability Type**: Excessive authorization scope and unrestricted privileged tool access **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 dispatcher accepts any caller-supplied MCP tool name: ```python def _run_command(command: str, tool_name: str | None = None) -> dict[str, Any]: session = _session_with_registration( state_dir=Path.home() / ".beatra", post_json=_default_post_json, ) if command == "tools": return session.request(2, "tools/list", {}) try: arguments = json.load(os.sys.stdin) except json.JSONDecodeError as exc: raise RuntimeError("Tool arguments on stdin must be one JSON object") from exc if not isinstance(arguments, dict): raise RuntimeError("Tool arguments on stdin must be one JSON object") assert tool_name is not None return session.request( 2, "tools/call", {"name": tool_name, "arguments": arguments}, ) ``` ### Technical Analysis The declared SKU comparison workflow needs image generation, optional artifact upload, model lookup, and task-management operations. The authorization request additionally obtains video, music, speech, voice-write, general wallet-spend, and other shared capabilities that are unrelated to creating SKU comparison charts. The client also provides a generic `call` command without a package-specific allowlist. Consequently, any MCP tool exposed to the shared full-scope bearer token can be selected by name. The safety restrictions in `SKILL.md` are instructional controls rather than an enforceable authorization boundary. This violates least privilege at both layers: 1. The bea ...[truncated 1249 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Issue a package-scoped token containing only the permissions required for: - model-card lookup; - image generation and editing; - explicitly approved artifact upload; - task read and user-requested cancellation; - narrowly scoped billing reads. 2. Remove video, music, speech, voice-write, and other unrelated permissions. 3. Avoid granting generic `wallet:spend`; bind charging authorization to the specific approved image operation. 4. Add a local allowlist for the exact MCP tool names used by this Skill and reject all others before opening a session. 5. Enforce the same package-level tool restrictions on the server. Local validation alone is not a security boundary. 6. Use separate credentials for separate packages or capabilities instead of one full-scope token shared by every package. 7. Require explicit user confirmation before expanding an existing credential's scope. ]]>

other

Warning
Location
scripts/authorize.py:340
Finding
Hostname and Agent-Environment Fingerprinting Is Transmitted During Authorization and Tool Use<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:340-369`, `scripts/authorize.py:439-451`; `scripts/mcp_client.py:1218-1228` **Vulnerability Type**: Device fingerprinting and telemetry beyond minimum functional requirements **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] ``` The collected values are added to the remote 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) `` ...[truncated 2098 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not collect or transmit the operating-system hostname by default. 2. Generate a non-identifying local alias, or allow the user to provide an optional device display name. 3. Obtain informed opt-in consent before transmitting hostname or agent-platform telemetry. 4. Clearly document every telemetry field, its destination, retention period, and purpose. 5. Make `source_platform` optional and disabled by default for business calls. 6. Store only the minimum metadata required for authentication and uninstall bookkeeping. 7. Provide a command that displays and deletes locally persisted telemetry without deleting the authentication token. ]]>

T03 · Remote Payload Retrieval and Execution

Warning
Location
scripts/mcp_client.py:969
Finding
Silent Remote Package Updates Can Replace Executable Skill Code Without Independent Signature Verification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1018` **Vulnerability Type**: Automatic remote payload retrieval and executable file replacement **Risk Level**: Medium ### Vulnerable Code ```python def maybe_auto_update( *, state_dir: Path | None = None, install_root: Path | None = None, get_bytes: GetBytes = _default_get_bytes, now: float | None = None, ) -> bool: """Best-effort silent update. Never block the requested MCP command.""" resolved_state = state_dir or Path.home() / ".beatra" try: resolved_root = (install_root or _current_install_root()).resolve() update_home = _update_home(resolved_state, resolved_root) observed_at = time.time() if now is None else now nonce = _lock_update(update_home, now=observed_at) if nonce is None: return False try: recover_update(state_dir=resolved_state, install_root=resolved_root) state = _read_update_state(update_home) if state.get("auto_update", True) is False: return False last_checked = state.get("last_checked_at") if ( isinstance(last_checked, (int, float)) and observed_at - float(last_checked) < UPDATE_CHECK_MAX_AGE_SECONDS ): return False state["last_checked_at"] = observed_at _write_private_json(update_home / "state.json", state) checked = check_update(get_bytes=get_bytes) if not checked["update_available"]: return False _ensure_owned_baseline( install_root=resolved_root, update_home=update_home, get_bytes=get_bytes, ) discovery = checked["discovery"] manifest, new_files = download_update(discovery, get_bytes=get_bytes) _apply_update( install_root=resolved_root, ...[truncated 2985 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Sign discovery documents or manifests with an offline release key. 2. Embed only the verification public key in the client and reject releases without a valid signature. 3. Ensure signatures cover the package identity, version, channel, locale, complete file manifest, and archive digest. 4. Separate the signing authority from the web/CDN publishing infrastructure. 5. Use key rotation and revocation metadata protected by an existing trusted key. 6. Disable automatic executable replacement by default, or obtain explicit user consent before the first automatic update. 7. Display the target version and signature identity for manual updates. 8. Consider release transparency or reproducible package manifests so unauthorized releases can be detected. 9. Preserve the existing path, size, rollback, and ownership checks; they remain valuable defense-in-depth controls. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:1044
Finding
Windows Credential Confidentiality Relies on Unverified Inherited ACLs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:1044-1052`; related credential creation in `scripts/authorize.py:119-134` **Vulnerability Type**: Missing Windows access-control enforcement for a full-scope bearer credential **Risk Level**: Medium ### Vulnerable Code ```python def _read_private_credentials(state_dir: Path, path: Path) -> str: if os.name == "nt": # The state directory lives under the user profile, whose default # ACL is already private to the user (the gh/aws/gcloud posture). # The former custom DACL verification was dropped deliberately: its # command patterns read as hostile to agent safety policies and # endpoint security, failing installs while adding nothing an # elevated administrator could not bypass. return path.read_text(encoding="utf-8") ``` Credential creation only applies explicit permission restrictions on POSIX systems: ```python def _private_directory(path: Path) -> None: path.mkdir(mode=0o700, parents=True, exist_ok=True) if os.name == "posix": path.chmod(0o700) def _restrict_file(path: Path) -> None: if os.name == "posix": path.chmod(0o600) ``` ### Technical Analysis The documentation states that, on Windows, the current user must be the only principal granted access to the credential through the file ACL. The implementation neither creates such a discretionary access-control list nor verifies the effective ACL before writing or reading the token. The `mode=0o700` and `0o600` arguments do not provide equivalent Windows DACL enforcement. The implementation instead assumes the user's profile directory has safe inherited permissions. That assumption can be invalid on shared systems, migrated profiles, manually modified directories, development environments, or enterprise configurations. The discrepancy is particularly significant because `credentials.json` contains a shared, full-scope bearer token rather tha ...[truncated 1116 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create `~/.beatra` and `credentials.json` with an explicit Windows DACL that grants access only to: - the current user; - required system principals, if operationally necessary. 2. Disable unsafe permission inheritance for the credential file. 3. Before every credential read, inspect the effective owner and access-control entries and fail closed if another non-administrative principal has access. 4. Use native Windows security APIs or a narrowly reviewed library rather than shell commands such as `icacls`. 5. Atomically create and replace credential files while preserving the restrictive DACL. 6. Update documentation to match the implemented guarantees. 7. Reduce the token's scope and support immediate revocation to limit the consequences of local disclosure. ]]>
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 (22)

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill declares no permissions, yet its instructions clearly require shell execution, local file inspection, network access, file writes, and likely environment/credential use via the bundled client. This creates a dangerous transparency gap: a host or reviewer may treat the skill as low-privilege content while it actually performs sensitive operations including uploads and account-linked remote actions.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The documented purpose is generating SKU comparison charts, but the skill also introduces authentication flows, persistent credential storage, remote tool invocation, local file upload, telemetry/registration behavior, uninstall/token revocation, and package self-update mechanics. That mismatch is dangerous because users and policy systems may authorize a simple content-generation skill without realizing it can establish long-lived trust, move local data off-host, and modify its own runtime behavior.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The skill embeds self-updating package behavior unrelated to the immediate user task, allowing code on the local installation to change over time outside the main approval flow. Even with signature verification claims, self-update expands the trust boundary and can introduce supply-chain risk, unexpected behavior changes, or security bypass if the update channel or verification process is ever compromised.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
This file documents a client-side auto-update mechanism that is unrelated to the declared SKU comparison chart function of the skill. In a skill context, introducing update/install behavior expands the trust boundary from content generation to local software modification, creating a serious supply-chain and unauthorized system-change risk even if the text claims verification safeguards.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The file describes silent default self-updating that installs newer releases automatically without separate confirmation. For a SKU chart generation skill, this capability is unjustified and dangerous because it normalizes autonomous code changes on the user's system, which could be abused if the update channel, signing, or distribution process is ever compromised.

Description-Behavior Mismatch

Medium
Confidence
88% confidence
Finding
The documentation describes automatic outbound installation registration, collection of package/version/platform metadata, and persistent local caching that are unrelated to generating SKU comparison charts. Even if labeled non-billable and best-effort, this introduces telemetry-like behavior and environment fingerprinting outside the skill’s stated purpose, creating an unnecessary privacy and trust boundary expansion.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The file specifies resolving and recording the real agent environment from environment signatures or host metadata, which amounts to platform detection and host characterization unrelated to the advertised chart-generation function. In the context of a simple creative/listing skill, this increases sensitivity because it enables environment fingerprinting and durable installation tracking without a functional need tied to the skill’s purpose.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The skill requests a very broad OAuth scope set including artifacts, images, videos, music, speech, voices, tasks, and wallet spending, which is far beyond what a SKU comparison chart skill appears to need. Excessive privileges violate least privilege and significantly increase blast radius if the skill, host environment, or stored token is compromised.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
Requesting wallet-spending and broad multimodal generation permissions for a SKU comparison chart tool is particularly dangerous because those capabilities enable financial actions and unrelated content generation if the token is abused. In the context of a seller-spec-to-chart skill, these permissions are unjustified and materially raise the risk profile.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The file implements a broad remote control surface: generic MCP RPC, arbitrary tool calls from stdin, local file upload, installation telemetry, and package self-update. For a skill whose declared purpose is generating SKU comparison charts, this is unjustified overreach and materially expands the attack surface for remote data exfiltration, unintended command brokerage, and supply-chain compromise.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The code can fetch remote manifests and archives, validate them, and then replace installed package files on disk. Even though there are several integrity checks, a self-updating mechanism is a high-risk supply-chain primitive and is especially dangerous in a simple content-production skill where runtime code replacement is not expected by users.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The skill records local inventory and sends installation registration telemetry that is unrelated to producing SKU comparison charts. This creates unnecessary privacy and metadata leakage about installed paths, platform, and usage, which increases user risk without supporting the stated function of the skill.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
The upload path reads an arbitrary local regular file and obtains remote upload instructions, allowing the skill to send local data off-host. In the context of a SKU comparison chart skill, arbitrary local file upload is mismatched functionality and materially raises the risk of sensitive file exfiltration if the feature is misused or triggered unexpectedly.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
This code performs network-based device-token revocation against a remote API during uninstall, which is unrelated to the advertised SKU comparison/chart-generation purpose of the skill. Even if framed as cleanup, introducing authorization-management behavior into a content-generation skill expands privilege and creates a path to affect shared account state outside user expectations.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The main uninstall flow includes device authorization handling, remote revocation, and deletion of shared local state, capabilities that are unjustified for a SKU comparison chart skill. In this context, the mismatch is dangerous because users would not reasonably expect a listing-graphics tool to manipulate shared authentication material used by other skills.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
Automatic installation of newer versions without separate confirmation means the effective code executed by the skill can change after initial review or user approval. In this context, the risk is elevated because the skill already has access to local files, uploads, billing-linked remote services, and persistent credentials, so silent updates could materially expand or alter sensitive behavior.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The markdown states that updates are installed automatically and silently by default, but does not prominently warn that this modifies the local installation without separate confirmation. Even if technically constrained, this can mislead users about the operational impact and reduce their ability to make informed trust decisions about software changes.

Missing User Warnings

Low
Confidence
80% confidence
Finding
The immediate update command is presented as a routine command without an explicit caution that it will replace installed files. This is a weaker issue than the silent default update, but it still increases the chance that users run a system-modifying command without appreciating its effect.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
maybe_auto_update() performs silent background update checks and can replace installed package files without a user-facing warning at invocation time. Even with integrity checks, silent code modification undermines transparency and makes supply-chain failures or malicious release events harder for users to detect and control.

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
90% confidence
Finding
The script explicitly targets credential-related local state, including credentials.json, as part of its uninstall behavior. Access to shared credential storage is sensitive even if intended for cleanup, because a non-auth-management skill should not directly enumerate or control authentication artifacts that may affect other installed 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
97% confidence
Finding
This function reads an access token from credentials.json and uses it to perform a revocation request, which is direct handling of authentication secrets. In the context of a SKU comparison/chart skill, that credential access is out of scope and increases the risk of unauthorized account disruption, token misuse, or abuse if the package is modified or repurposed.

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 exposed update command intentionally enables self-modification by downloading and installing new package bytes over the current installation. Self-modifying behavior is inherently high risk because it turns the skill into its own software installer/updater, which is far beyond the expected scope of a SKU chart generator and magnifies supply-chain impact if the remote source is ever compromised.

Static analysis

No suspicious patterns detected.