Back to skill

Security audit

nutrition-panel-art

Security checks for vulnerabilities and agentic risk

Overview

This skill can make nutrition-panel images, but it also asks for broad Beatra account powers and silently updates its own code, so it needs review before installation.

Install only if you trust Beatra with a broad shared account token that can cover more than this image skill, including billable media and wallet-related authority. Review or disable automatic updates before use, and be aware that selected local files may be uploaded, installation metadata is registered, and uninstall may affect shared Beatra connection state.

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 Tool Dispatch<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:32-35`; `scripts/mcp_client.py:1450-1467` **Vulnerability Type**: Excessive authorization scope and missing client-side tool allowlist **Risk Level**: High ### Complete Code Snippet ```python SCOPE = ( "mcp:tools artifacts:write images:generate videos:generate music:generate " "speech:generate voices:read voices:write wallet:spend tasks:read artifacts:read tasks:cancel" ) ``` ```python def _run_command(command: str, tool_name: str | None = None) -> dict[str, Any]: session = _session_with_registration( state_dir=Path.home() / ".beatra", post_json=_default_post_json, ) if command == "tools": return session.request(2, "tools/list", {}) try: arguments = json.load(os.sys.stdin) except json.JSONDecodeError as exc: raise RuntimeError("Tool arguments on stdin must be one JSON object") from exc if not isinstance(arguments, dict): raise RuntimeError("Tool arguments on stdin must be one JSON object") assert tool_name is not None return session.request( 2, "tools/call", {"name": tool_name, "arguments": arguments}, ) ``` ### Technical Analysis The Skill's declared purpose is to generate nutrition-panel still images from seller-provided facts. Its legitimate operations include image generation and editing, optional artifact upload, model discovery, task inspection, limited task cancellation, and billing or wallet reads. The requested Device Token is substantially broader. It grants video generation, music generation, speech generation, voice read/write access, general wallet spending, and artifact/task capabilities shared across Beatra packages. The bundled client also accepts an arbitrary tool name and forwards it through `tools/call` without enforcing a package-specific allowlist. The combination violates least privilege: a component intended for nutrition-panel images receives ...[truncated 1714 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the full-scope shared token with a package-specific, least-privilege token. 2. Restrict authorization to the capabilities required by this Skill, such as: - Image generation and editing. - Artifact upload/read for explicit user-selected references and outputs. - Model listing. - Task reads and narrowly controlled cancellation. - Read-only wallet and billing access. 3. Remove video, music, speech, and voice scopes. 4. Separate read-only wallet access from wallet-spending authorization. 5. Add an explicit client-side allowlist for accepted tool names, for example: - `beatra.models.list` - `beatra.images.generate` - `beatra.images.edit` - `beatra.assets.upload` - `beatra.tasks.get` - `beatra.tasks.list` - `beatra.tasks.cancel` - Required read-only wallet tools 6. Reject every tool not present in the package allowlist before opening a network connection. 7. Require explicit user confirmation immediately before every billable operation and task cancellation. 8. Avoid sharing one bearer token across packages with different privilege requirements. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Silent Automatic Replacement of Executable Code Without Independent Release Signatures<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1018`; related endpoint definitions at `scripts/mcp_client.py:31-32` and automatic invocation at `scripts/mcp_client.py:1543` **Vulnerability Type**: Mutable remote code-update channel **Risk Level**: High ### Complete Code Snippet ```python PACKAGE_DISCOVERY_URL = "https://beatra.ai/skills/nutrition-panel-art/channels/clawhub/install.json" PACKAGE_CDN_BASE_TEMPLATE = "https://cdn.beatra.ai/agent-packages/nutrition-panel-art/channels/clawhub/v{version}" ``` ```python def maybe_auto_update( *, state_dir: Path | None = None, install_root: Path | None = None, get_bytes: GetBytes = _default_get_bytes, now: float | None = None, ) -> bool: """Best-effort silent update. Never block the requested MCP command.""" resolved_state = state_dir or Path.home() / ".beatra" try: resolved_root = (install_root or _current_install_root()).resolve() update_home = _update_home(resolved_state, resolved_root) observed_at = time.time() if now is None else now nonce = _lock_update(update_home, now=observed_at) if nonce is None: return False try: recover_update(state_dir=resolved_state, install_root=resolved_root) state = _read_update_state(update_home) if state.get("auto_update", True) is False: return False last_checked = state.get("last_checked_at") if ( isinstance(last_checked, (int, float)) and observed_at - float(last_checked) < UPDATE_CHECK_MAX_AGE_SECONDS ): return False state["last_checked_at"] = observed_at _write_private_json(update_home / "state.json", state) checked = check_update(get_bytes=get_bytes) if not checked["update_available"]: return False _ensure_owned_baseline( insta ...[truncated 3234 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Sign every release manifest with an offline or otherwise strongly protected release-signing key. 2. Embed the corresponding public key in the audited package and verify the signature before trusting version numbers, file hashes, or archive URLs. 3. Consider threshold signatures or multiple independent release keys for high-impact executable updates. 4. Pin the package identifier, channel, locale, version, and every file hash inside the signed material. 5. Make automatic installation opt-in rather than enabled by default. 6. Keep silent update checks separate from code installation; notify the user when an update is available. 7. Require explicit approval before replacing executable files, especially `scripts/mcp_client.py` and `scripts/authorize.py`. 8. Publish auditable release metadata and support reproducible package verification. 9. Preserve the existing redirect, archive, path, ownership, backup, and rollback protections, as they remain useful defense-in-depth controls. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:1044
Finding
Windows Device Token ACL Requirements Are Not Enforced<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:1044-1051`; credential creation behavior at `scripts/authorize.py:122-134` **Vulnerability Type**: Unsafe credential-file access control on Windows **Risk Level**: Medium ### Complete Code Snippet ```python def _private_directory(path: Path) -> None: # POSIX gets explicit 700/600. On Windows the state directory lives under # the user profile, whose default ACL is already private to the user — # the same posture as gh/aws/gcloud credential stores. The former custom # DACL ceremony was dropped deliberately: its command patterns read as # hostile to agent safety policies and endpoint security, failing installs # while adding no protection an elevated administrator could not bypass. path.mkdir(mode=0o700, parents=True, exist_ok=True) if os.name == "posix": path.chmod(0o700) def _restrict_file(path: Path) -> None: if os.name == "posix": path.chmod(0o600) ``` ```python def _read_private_credentials(state_dir: Path, path: Path) -> str: if os.name == "nt": # The state directory lives under the user profile, whose default # ACL is already private to the user (the gh/aws/gcloud posture). # The former custom DACL verification was dropped deliberately: its # command patterns read as hostile to agent safety policies and # endpoint security, failing installs while adding nothing an # elevated administrator could not bypass. return path.read_text(encoding="utf-8") ``` The documentation claims a stronger requirement: ```text On Windows the current user must be the only principal granted access through the file ACL. ``` ### Technical Analysis On POSIX, the implementation creates the state directory with mode `0700`, writes credentials with mode `0600`, verifies ownership, rejects non-regular files, and attempts to avoid symlink traversal. On Windows, the code neither applies an owner ...[truncated 1777 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use supported Windows security APIs to create and verify a DACL granting access only to the current user and necessary system principals. 2. Apply the secure ACL to both `~/.beatra` and `credentials.json`. 3. Validate existing paths before authorization and fail closed if unauthorized principals have read or write access. 4. Detect and reject unsafe reparse points or unexpected non-regular credential objects. 5. Revalidate the ACL each time credentials are read, not only during creation. 6. Create temporary credential files with equivalent protections before atomic replacement. 7. Update documentation only after the implementation demonstrably enforces the stated Windows access-control invariant. ]]>

other

Note
Location
scripts/authorize.py:350
Finding
Unnecessary Hostname and Agent-Environment Telemetry During Authorization<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:350-370`; transmission at `scripts/authorize.py:403-437` **Vulnerability Type**: Excessive device telemetry **Risk Level**: Low ### Complete Code Snippet ```python def detect_host_platform(explicit: str | None = None) -> str: """The agent environment this process runs inside (docs/device-model.md). Order: explicit agent self-report > environment signatures > unknown. Detection reads the process environment only — nothing else runs, nothing reaches the network. """ if explicit: candidate = explicit.strip().lower().replace(" ", "-") if _PLATFORM_VALUE.fullmatch(candidate): return candidate env = os.environ if env.get("CLAUDECODE") == "1" or "CLAUDE_CODE_ENTRYPOINT" in env: return "claude-code" if any(key.startswith("CODEX_") for key in env): return "codex" ai_agent = env.get("AI_AGENT", "").lower() matched = re.match(r"([a-z0-9-]+)_", ai_agent) if matched and _PLATFORM_VALUE.fullmatch(matched.group(1)): return matched.group(1) return "unknown" def device_display_name() -> str | None: """A hostname the user will recognise in the console device list.""" try: name = socket.gethostname().strip() except OSError: return None if not name or not name.isprintable(): return None return name[:120] ``` ```python form: dict[str, str] = { "client_id": CLIENT_ID, "resource": MCP_URL, "scope": SCOPE, "platform": host_platform, "client_name": PACKAGE_DISPLAY_NAME, "external_installation_ref": external_reference, "package_version": PACKAGE_VERSION, "package_slug": PACKAGE_SLUG, } if device_name: form["device_name"] = device_name status, created = post_form(DEVICE_AUTHORIZATION_URL, form) ``` ### Technical Analysis The authorization helper inspects process-environment signatures to identify the hosting agent and calls ` ...[truncated 1673 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not transmit the hostname by default. 2. Use a randomly generated, non-identifying device alias for console display. 3. If a recognizable device name is desired, ask the user to opt in and show the exact value before transmission. 4. Clearly document every telemetry field, its purpose, destination, retention period, and deletion mechanism. 5. Minimize platform detection to an explicit user- or host-provided identifier where possible. 6. Avoid persisting telemetry locally unless it is required for functionality. 7. Provide a configuration option that disables all optional source-attribution and device telemetry. ]]>
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
96% confidence
Finding
The skill declares no permissions, yet its instructions require shell execution, network access, local file inspection/upload, and writes/updates through a bundled client. This creates a hidden capability gap: a user or host may authorize a seemingly simple image-layout skill without understanding that it can access local files, persist state, contact remote services, and self-modify via updates.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The documented purpose is limited to generating nutrition panel art, but the skill also performs authentication flows, persistent credential storage, telemetry/registration, arbitrary remote MCP operations, local file upload, uninstall/state removal, and automatic package updates. This mismatch is dangerous because it obscures materially different trust and attack surfaces, increasing the chance that users or orchestrators grant access they would not have approved if behavior were accurately disclosed.

Description-Behavior Mismatch

Medium
Confidence
90% confidence
Finding
The changelog references balance, ledger, top-up tiers, and addresses, which are unrelated to a nutrition-label rendering skill. This mismatch is a supply-chain red flag because it suggests the manifest may have been copied from another skill, repurposed without proper review, or hiding broader financial/account capabilities than the user-facing description discloses.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The file documents background installation registration and collection of package slug, version, platform, and a stable external installation reference, which is unrelated to generating nutrition panel art. This creates an unnecessary telemetry/data-collection capability in a skill whose stated purpose is purely creative, increasing privacy and supply-chain risk because users would not reasonably expect outbound registration behavior from this skill.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The documented ability to resolve the real agent environment from environment signatures or host metadata, then transmit it during installation registration, exceeds what is needed for nutrition-label generation. Environment fingerprinting can leak deployment context and enable user or host tracking across runs, making the skill more dangerous because the capability is hidden behind an unrelated domain-specific tool.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The skill requests a very broad OAuth scope set that includes capabilities far beyond generating nutrition panel art. For a narrowly described packaging-label skill, this violates least privilege and would give the skill unnecessary access if the token is later used by the skill or any compromised component.

Context-Inappropriate Capability

Critical
Confidence
99% confidence
Finding
The authorization includes audio, voice, speech, music, and video generation/read-write capabilities that are unrelated to creating a static nutrition facts panel. These extra permissions expand what the credential can do and create unnecessary abuse opportunities if the token is stolen or the skill performs unexpected actions.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The authorization includes audio, voice, speech, music, and video generation/read-write capabilities that are unrelated to creating a static nutrition facts panel. These extra permissions expand what the credential can do and create unnecessary abuse opportunities if the token is stolen or the skill performs unexpected actions.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
Task read/cancel permissions are not obviously required for a nutrition panel art skill and broaden the token's authority into workflow management. While less severe than wallet access, these scopes still violate least privilege and could let the skill inspect or interfere with unrelated user tasks.

Description-Behavior Mismatch

High
Confidence
92% confidence
Finding
The client can fetch remote discovery metadata, download archives, and replace package files on disk, which is outside the declared nutrition-panel-generation purpose and materially increases attack surface. Even with checksum and path checks, this is a built-in self-modifying update channel that can change local code after install; if the update infrastructure or trust chain is compromised, the skill gains a remote code replacement mechanism.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
The code detects the host agent/platform from environment variables and local state, then later injects that value into tool-call arguments. This telemetry is unrelated to nutrition panel generation and creates unnecessary environment fingerprinting, which increases privacy risk and can aid backend profiling or targeting of specific agent environments.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The skill records a local inventory of installed skills and sends installation-registration telemetry, including package identifiers, install paths, versions, platform, and external installation references. That behavior is unrelated to the stated nutrition-art function and expands both privacy exposure and the consequences of backend misuse or compromise.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
This uninstall script is explicitly designed to inspect and potentially revoke a shared Beatra device credential and remove shared state under ~/.beatra, which is broader than this skill’s stated nutrition-panel functionality. Even though the code includes safeguards to avoid revoking while other skills remain, it still grants this skill package authority over global authentication state, so compromise, misuse, or user surprise could disrupt unrelated installed skills.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
The code performs a remote POST to revoke the device OAuth token using a bearer token read from shared local state. For a nutrition-art skill, direct authority to revoke a device-wide authorization is excessive privilege; if triggered unexpectedly or modified, it can deny service to other skills and sever the shared agent connection.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
This code reads the shared access token from credentials.json and later deletes multiple shared Beatra state files, not just files owned by this skill. Accessing and deleting platform-wide credential and installation metadata exceeds the least-privilege needs of a nutrition label rendering skill and can affect all installed skills on the device.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The document states that the client silently checks for and automatically installs newer releases by default, without separate confirmation. Even with integrity checks and rollback protections, silent self-updating changes executable/package behavior without an explicit user approval step, which creates supply-chain and trust risks if the update source, signing process, or release pipeline is ever compromised.

Missing User Warnings

Low
Confidence
92% confidence
Finding
The documentation says the bundled client performs a first-use registration call that transmits installation metadata, but there is no prominent warning in the skill description or user-facing consent flow. Lack of disclosure undermines informed consent and can cause silent privacy leakage, especially because the skill appears to be a simple art-generation utility rather than a networked telemetry component.

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
93% confidence
Finding
The manifest declares use of a local bearer-token credential file and a remote MCP endpoint. Even though this is common for authenticated integrations, it is security-relevant because the skill can potentially act with the user's Beatra account privileges, and in the context of the unrelated changelog references, the credentialed access is more suspicious and raises the risk of unauthorized remote actions or data access if the server or skill behavior is broader than advertised.

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
88% confidence
Finding
The presence of credentials.json in the set of files this script may delete indicates that the skill package is aware of and operates on shared credential material. Even without exfiltration, giving a domain-specific skill package filesystem-level access to credential storage creates unnecessary exposure and increases blast radius if the package or uninstall flow is tampered with.

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
93% confidence
Finding
The _device_token function reads the shared OAuth access token from credentials.json so the skill can use it for revocation. This is credential access beyond the functional requirements of nutrition-panel generation, and in this context it is especially dangerous because the token is device-wide and could be reused for unauthorized actions if the code were altered or abused.

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
94% confidence
Finding
Exposing a self-update command confirms that the package is designed to modify and replace its own code after installation. In a skill whose declared purpose is nutrition-panel art generation, self-modification is unnecessary and dangerous because it creates an execution path for remote code changes independent of the host's normal review and deployment controls.

Static analysis

No suspicious patterns detected.