Back to skill

Security audit

Listing Still Sets

Security checks for vulnerabilities and agentic risk

Overview

This skill performs the advertised listing-image workflow, but it also requests broad Beatra account permissions and silently self-updates by default.

Review this before installing. Use it only if you are comfortable granting a shared Beatra device token with permissions beyond listing-image creation and with automatic package updates enabled by default. Consider disabling auto-updates with the documented update setting, review the Beatra approval page carefully, and avoid uploading sensitive local files unless you intend to send them to Beatra-controlled upload infrastructure.

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)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Default-Enabled Silent Remote Code Replacement<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1019, 1542-1544` **Vulnerability Type**: Default-enabled remote payload retrieval and subsequent execution **Risk Level**: Critical ### 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_ro ...[truncated 3176 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default. Update checks may be automatic, but installation should require explicit informed approval. 2. Display the current version, proposed version, publisher, release digest, and files to be changed before approval. 3. Sign release metadata and manifests with a detached digital signature verified against a public key embedded in the reviewed package. 4. Use signed metadata with expiration, version, rollback, and key-rotation protections, such as a TUF-style update design. 5. Separate update installation from paid or sensitive business operations so a routine generation request cannot silently alter executable code. 6. Prefer replacement through the trusted package-distribution mechanism rather than a custom self-updater. 7. Preserve the existing path traversal, archive-size, ownership, locking, and rollback controls as defense in depth. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/authorize.py:35
Finding
Device Authorization Requests Permissions Unrelated to Listing Image Generation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:35-38` **Vulnerability Type**: Excessive OAuth/device-token scope and violation of least privilege **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" ) ``` Existing and newly issued credentials are required to have this complete scope set: ```python if ( not isinstance(value, dict) or value.get("schema_version") != 1 or value.get("mcp_url") != MCP_URL or value.get("token_type") != "Bearer" or any(not isinstance(value.get(name), str) or not value[name] for name in required_strings) or set(value["scope"].split()) != set(SCOPE.split()) ): return None ``` The scope is sent during authorization: ```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, } ``` ### Technical Analysis The Skill's declared purpose is to create still images for real-estate listings. Its documented workflow requires image generation and editing, optional artifact upload, model and task reads, limited task cancellation, and billing-related operations. The requested token additionally grants unrelated capabilities: - `videos:generate` - `music:generate` - `speech:generate` - `voices:read` - `voices:write` These privileges are not necessary to create listing stills. The implementation also requires exact equality between the stored scope and the broad hardcoded scope, discouraging use of a narrower credential. This expands the authority represented by a single bearer token. Because the credential is shared across Beatra Skills, compromise of this p ...[truncated 1327 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define a package-specific minimal scope containing only the exact capabilities required for listing still creation. 2. Remove video, music, speech, and voice scopes from this Skill. 3. Separate read-only task/model/artifact permissions from billable generation and cancellation permissions where the service supports that distinction. 4. Request cancellation authority only when cancellation is actually needed, or use step-up authorization for destructive operations. 5. Accept existing credentials whose scopes are a safe superset of the required minimal scope rather than requiring exact equality with a global full-scope set. 6. Present the requested scopes and their consequences clearly on the approval page. 7. Consider package-bound or audience-bound tokens so one Skill cannot automatically inherit every capability needed by unrelated Beatra Skills. ]]>

other

Warning
Location
scripts/authorize.py:363
Finding
Local Hostname Is Collected, Persisted, and Transmitted During Authorization<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:363-371, 425-440, 579-581` **Vulnerability Type**: Environment reconnaissance and device metadata disclosure **Risk Level**: Medium ### Vulnerable Code ```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 persisted in local state: ```python def write_host_config(state_dir: Path, *, platform: str, device_name: str | None) -> None: """Persist detection results so mcp_client never re-detects per request and still has a truth source when its own env detection comes up empty. Best-effort: config failure must never block authorization.""" try: payload: dict[str, Any] = {"platform": platform} if device_name: payload["device_name"] = device_name (state_dir / "host.json").write_text( json.dumps(payload, ensure_ascii=False, separators=(",", ":")) + "\n", encoding="utf-8", ) except OSError: pass ``` It is then included in the remote device-authorization form: ```python if device_name: form["device_name"] = device_name status, created = post_form(DEVICE_AUTHORIZATION_URL, form) ``` The collection is activated automatically: ```python host_platform = detect_host_platform(platform) device_name = device_display_name() write_host_config(state_dir, platform=host_platform, device_name=device_name) ``` ### Technical Analysis The authorization helper reads the operating system's hostname, stores it in `~/.beatra/host.json`, and sends it to the Beatra authorization service as `device_name`. A friendly device label can be useful in an account console, but the raw hostname is not necessary for producing listing images. Hostnames may co ...[truncated 1537 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not collect or transmit the operating-system hostname by default. 2. Generate a generic local label, such as `Beatra device`, or use a random non-identifying device identifier. 3. Allow the user to supply an optional friendly device name after clearly explaining that it will be sent to Beatra. 4. If hostname use is retained, obtain explicit consent and document the purpose, recipient, retention period, and deletion controls. 5. Avoid persisting `device_name` in `host.json` unless it is required for a documented local function. 6. Apply secure file creation to `host.json`, including atomic creation and restrictive permissions, rather than relying only on the parent directory. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:231
Finding
Server-Provided Upload URL Is Not Restricted to Approved Storage Hosts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:231-265` **Vulnerability Type**: Unrestricted HTTPS destination for user-selected file uploads **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 upload grant is returned by the remote MCP service. The client verifies that the d ...[truncated 1999 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Maintain an explicit allowlist of documented Beatra-controlled or approved object-storage hostnames. 2. Reject IP literals, localhost, loopback, link-local, private-network, and metadata-service destinations. 3. Reject unexpected ports and require the canonical HTTPS port unless a specific approved storage endpoint requires otherwise. 4. Cryptographically bind the upload URL, object key, content type, content length, expiration, and artifact identity to the authenticated grant. 5. Validate the returned artifact identifier against the original grant after upload. 6. Do not forward arbitrary server-provided headers; allow only the exact headers required for the signed upload. 7. Document the storage domains to which user files may be transferred and obtain consent before uploading sensitive property images. ]]>
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 (23)

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill exposes broad operational capabilities—environment access, file read/write, network, and shell—without declaring permissions or constraining them to the narrow listing-still task. That makes the trust boundary opaque and enables unexpected local or remote side effects, especially since the same document instructs use of a bundled Python client, local file handling, and remote service calls.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The documented behavior substantially exceeds the stated purpose of generating real-estate listing graphics by including OAuth login, token persistence, generic remote tool invocation, telemetry/registration, local uploads, uninstall behavior, and package self-management. This mismatch is dangerous because users may grant trust for a simple media workflow while the skill actually introduces credential handling, persistent state, software modification, and broad remote interaction surfaces.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill includes an automatic update mechanism that can download and replace package files, which is unrelated to the advertised listing-still function. Even with verification claims, self-updating behavior expands supply-chain risk and can change the reviewed code after approval, increasing the chance of malicious or faulty code being introduced into an otherwise simple content workflow.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The changelog references ranking a top-up tier, hardcoded tier pricing, a top-up address, and added balance/ledger calls, which are unrelated to a real-estate listing graphics skill. This strongly suggests the manifest was copied from or connected to a financial/account-oriented capability set, creating a dangerous mismatch between the declared user-facing purpose and the backend behavior users may authorize.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
Balance and ledger access are context-inappropriate for a skill that should only transform seller-supplied room facts into listing images. Even if described as read-only, financial visibility can expose sensitive account data and may be abused to profile holdings or support later social-engineering or transaction-related attacks.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
This file documents an uninstall and device-disconnection workflow that is unrelated to the stated purpose of the skill, which is generating real-estate listing stills from floor-plan facts. Embedding package-removal and shared-device connection handling in an unrelated skill expands the skill's operational scope and can facilitate unauthorized persistence, disruption of other installed skills, or social engineering around destructive actions.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The documentation instructs management of shared authorization state in ~/.beatra and describes revoking device credentials, which is far beyond what a listing-still generation skill should need. In this context, such instructions are dangerous because they normalize access to sensitive shared state and could lead to denial of service against other skills, unintended credential revocation, or broader compromise of agent/device trust relationships.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The OAuth scope string requests a very broad set of capabilities, including artifacts, images, videos, music, speech, voices, wallet spending, and task control, while the skill is described as generating listing stills from seller-supplied floor-plan facts. This violates least privilege and means any stolen or misused credential would grant far more access than the skill needs.

Context-Inappropriate Capability

Critical
Confidence
100% confidence
Finding
Requesting wallet:spend and unrelated media/voice permissions is especially dangerous because it enables financial actions and access to capabilities unrelated to real-estate listing still generation. In this skill context, those scopes are unjustified, so compromise or abuse of the stored bearer token could lead to unauthorized spending or broader platform abuse.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The skill is described as generating real-estate listing stills from seller-supplied floor-plan facts, but this client includes extensive package self-update, file replacement, rollback, and installation-state management logic unrelated to that purpose. That extra capability can silently modify local code and persist behavior changes, materially expanding the attack surface for a content-generation skill.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
The upload() function reads and uploads any local regular file chosen by path to a remote service, while the skill description only justifies processing seller-provided floor-plan facts into graphics. This mismatch enables unnecessary local file exfiltration capability, which is especially dangerous in an agent context where users may not expect broad filesystem upload powers from a real-estate imaging skill.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The code fingerprints the host environment, records local skill inventory, caches installation metadata, and sends source attribution and installation telemetry to a remote service. For a skill whose stated purpose is creating listing graphics, this data collection is not obviously necessary and increases privacy risk and the amount of contextual information disclosed to the operator.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
This uninstall script manages a shared device authorization and can revoke remote credentials, which is outside the stated purpose of a skill for generating real-estate listing stills. That mismatch is dangerous because users installing a content-generation skill would not reasonably expect it to inspect shared skill inventory or alter shared authentication state affecting other installed skills.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The script performs a network call to revoke device authorization during uninstall, a privileged action not justified by the advertised functionality of producing listing graphics. Even if intended as cleanup, embedding this capability in a skill broadens its authority and creates a path to disrupt account/device access or affect other skills sharing the same credential context.

Missing User Warnings

Medium
Confidence
94% 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, silently replacing installed files changes the user's system and trust boundary without explicit per-update consent or prominent warning, which can surprise users and increase the risk of unwanted code changes if the update channel or signing/distribution process is ever compromised.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The documentation states that the bundled client automatically performs an installation registration call and writes a local cache file, but it does not present a clear user-facing warning or consent mechanism for this telemetry-like behavior. Even if the data is described as non-secret and non-billable, automatic transmission of package, version, platform, and installation reference can create privacy and transparency concerns, especially in environments with strict compliance or monitoring requirements.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The authorization flow prints generic approval instructions but does not clearly disclose the full breadth of privileges being requested before sending the user to approve access. Users may authorize powerful capabilities without informed consent, increasing the likelihood of unsafe approval of overprivileged access.

Missing User Warnings

Low
Confidence
78% confidence
Finding
The code sends platform and device_name during authorization, and device_name is derived from the hostname, which can reveal identifying information about the user's machine or organization. While not severe on its own, the transfer happens without explicit disclosure in the user prompt, creating an unnecessary privacy risk.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
maybe_auto_update() performs silent best-effort updates and file replacement during normal command execution without a contemporaneous user-facing warning. Even with integrity checks, silent code changes in a skill runtime reduce user control, complicate auditing, and can unexpectedly alter behavior between runs.

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
94% confidence
Finding
The manifest explicitly points to a local bearer credential file, indicating the skill can operate with reusable authentication material. In the context of an ostensibly simple listing-image skill, access to account credentials is excessive and increases the risk of unauthorized API use, account data access, or abuse of whatever the connected MCP service permits.

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 skill manipulates indicates the skill is aware of and participates in deletion of shared credential material. For a real-estate listing skill, touching authentication artifacts is over-privileged and increases the chance of credential misuse, accidental denial of service, or unauthorized lifecycle control over other skills' access.

Credential Access

High
Category
Privilege Escalation
Content
def _device_token(state_dir: Path) -> str | None:
    path = state_dir / "credentials.json"
    try:
        value = json.loads(path.read_text(encoding="utf-8"))
    except (OSError, ValueError):
Confidence
91% confidence
Finding
This code reads an access token from credentials.json and uses it to drive a revocation request, demonstrating direct credential access by a skill unrelated to authentication management. Direct token handling in a low-trust skill increases exposure of bearer tokens and enables actions against shared account state beyond the skill's expected purpose.

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 CLI exposes a self-update command that can replace package files on disk, which is a self-modification capability. In the context of a narrowly scoped real-estate listing skill, this is unnecessarily powerful and dangerous because it allows the skill runtime to alter itself outside the user's primary task domain.

Static analysis

No suspicious patterns detected.