Back to skill

Security audit

used-car-walkaround

Security checks for vulnerabilities and agentic risk

Overview

The car-video workflow is mostly coherent, but it needs review because it stores broad Beatra credentials and silently updates its own code by default.

Install only if you are comfortable giving this skill broad Beatra account access, letting it store a shared bearer token under `~/.beatra`, uploading selected media files to Beatra-provided destinations, and allowing silent package self-updates by default. Consider disabling auto-update with `python3 scripts/mcp_client.py update --auto off`, use it only with non-sensitive upload files, and revoke the Beatra device authorization when it is no longer needed.

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 (5)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Silent unsigned remote updates can replace executable Skill code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1019`, `scripts/mcp_client.py:1542-1544` **Vulnerability Type**: Silent remote code replacement without an independent signature trust root **Risk Level**: High ### 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( ...[truncated 2532 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic updates by default and require explicit, informed user approval before replacing package files. 2. Sign release manifests with an offline or otherwise independently protected signing key. 3. Embed or securely provision the corresponding public key in the reviewed package and verify the signature before trusting any version, URL, checksum, or file list. 4. Bind the signature to the package name, channel, locale, version, archive digest, manifest contents, and release timestamp. 5. Implement signing-key rotation through a separately authenticated process. 6. Display the current and proposed versions, affected executable files, and publisher identity before installation. 7. Preserve the existing archive validation, path restrictions, ownership checks, transactional replacement, and rollback protections as defense-in-depth. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/authorize.py:30
Finding
Authorization scopes and arbitrary MCP tool dispatch exceed least privilege<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:30-33`, `scripts/mcp_client.py:1463-1483` **Vulnerability Type**: Excessive OAuth scope combined with unrestricted remote tool selection **Risk Level**: High ### Vulnerable Code The authorization helper requests capabilities unrelated to the declared used-car workflow: ```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 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 workflow requires image generation, speech synthesis, video animation, model and voice discovery, artifact handling, task polling, and limited wallet inspection. The requested scope additionally includes `music:generate` and `voices:write`, neither of which is necessary for producing a used-car walkaround. The bearer token also includes wallet-spending and task-cancellation authority. Some spending authority is necessary for the declared paid operations, but it is bundled into one broadly reusable token rather than constrained to the approved operation or package. The local ...[truncated 1283 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove unrelated scopes, particularly `music:generate` and `voices:write`. 2. Use package-specific, least-privilege credentials rather than one shared full-scope device token. 3. Enforce a local allowlist of MCP tools required by this Skill, including only the documented model, voice-read, image, speech, video, artifact, task, and read-only wallet operations. 4. Require a distinct confirmation boundary or short-lived authorization for spending and task cancellation. 5. Where supported, bind spending authorization to a maximum amount, operation type, client request ID, and expiration. 6. Reject unknown tool names locally even if the server advertises them. 7. Record an auditable local summary of billable and destructive requests without logging credentials or sensitive prompts. ]]>

other

Warning
Location
scripts/authorize.py:343
Finding
Hostname and Agent-environment telemetry are transmitted without being necessary for media generation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:343-371`, `scripts/authorize.py:469-483` **Vulnerability Type**: Environment reconnaissance and device-identifying telemetry **Risk Level**: Medium ### Vulnerable Code The helper detects the Agent platform from process environment variables and reads the local hostname: ```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] ``` These values are 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, "package_slug": PACKAGE_SLUG, } if device_name: form["device_name"] = device_name status ...[truncated 1817 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make hostname collection explicitly opt-in. 2. Use a random or user-selected device label by default instead of the operating-system hostname. 3. Clearly disclose all transmitted metadata before authorization, including hostname, platform, package version, and stable installation identifier. 4. Provide command-line options such as `--device-name` and `--no-device-telemetry`. 5. Minimize repeated source attribution on business calls where it is not required for security or billing. 6. Establish retention limits and deletion controls for installation telemetry. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:1044
Finding
Windows bearer-token confidentiality relies on unverified inherited ACLs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:1044-1051`, `scripts/authorize.py:116-122` **Vulnerability Type**: Inadequate access-control enforcement for persistent credentials **Risk Level**: Medium ### Vulnerable Code The client reads the Windows credential without checking its effective ACL: ```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 likewise applies explicit restrictions only on POSIX: ```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 On POSIX systems, the implementation verifies ownership and exact `0700`/`0600` permissions before reading the credential. On Windows, it assumes the user profile's inherited ACL is sufficiently private but neither creates a restrictive ACL nor validates the actual effective permissions. Inherited ACLs can differ because of enterprise policy, migrated profiles, shared workstations, manually changed permissions, backup tools, or an unusually permissive parent directory. The documentation states that the current user must be the only principal granted access, but the implementation does not enforce that requirement. The stored bearer token carries broad generation, spending, artifact, task-reading, and task-cancellation au ...[truncated 871 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the token in Windows Credential Manager or encrypt it with DPAPI under the current user context. 2. If a file remains necessary, create an explicit DACL granting access only to the current user and required system principals. 3. Verify the effective ACL before every credential read and reject unexpectedly permissive files. 4. Avoid relying solely on inherited user-profile permissions. 5. Apply equivalent checks to the state directory, credential file, temporary files, and replacement operations. 6. Provide a migration routine that securely replaces existing file-based credentials. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:231
Finding
Server-selected upload URLs are not restricted to approved storage hosts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:231-265` **Vulnerability Type**: Unrestricted external upload destination **Risk Level**: Medium ### Vulnerable Code ```python def _complete_upload( result: dict[str, Any], *, mime_type: str, content: bytes, put_bytes: PutBytes, ) -> dict[str, str]: structured = result.get("structuredContent") instruction = structured.get("upload") if isinstance(structured, dict) else None if not isinstance(instruction, dict) or instruction.get("method") != "PUT": raise RuntimeError("Beatra upload instructions are invalid") url = instruction.get("url") headers = instruction.get("headers") if not isinstance(url, str) or not isinstance(headers, dict): raise RuntimeError("Beatra upload instructions are invalid") parsed = urllib.parse.urlsplit(url) if ( parsed.scheme != "https" or not parsed.hostname or parsed.username is not None or parsed.password is not None or parsed.fragment ): raise RuntimeError("Beatra upload instructions are invalid") if not all(isinstance(key, str) and isinstance(value, str) for key, value in headers.items()): raise RuntimeError("Beatra upload instructions are invalid") content_type = _header_value(headers, "Content-Type") content_length = _header_value(headers, "Content-Length") if content_type != mime_type or content_length != str(len(content)): raise RuntimeError("Beatra upload instructions are invalid") response = put_bytes(url, dict(headers), content) artifact_id = response.get("artifact_id") if not isinstance(artifact_id, str) or not artifact_id: raise RuntimeError("Beatra upload returned an invalid response") return {"type": "artifact", "artifact_id": artifact_id} ``` ### Technical Analysis The client correctly requires HTTPS, rejects embedded credentials and fragments, validates the upload method, ...[truncated 1525 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Allowlist exact documented storage domains or tightly controlled domain suffixes. 2. Resolve and validate the final destination while continuing to reject redirects. 3. Prefer a signed upload grant that cryptographically binds the destination host, object key, HTTP method, MIME type, byte length, expiration, and required headers. 4. Reject unexpected authorization, cookie, proxy, or forwarding headers in server-provided upload instructions. 5. Display the destination domain before uploading when it differs from the primary Beatra domain. 6. Restrict uploads to media MIME types and extensions required by this Skill. 7. Preserve the existing regular-file, no-follow, size, stability, MIME, and content-length checks. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (9)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill declares a simple media-generation purpose, yet its documented execution path requires shell execution, network access, local file access, and persistent state via a bundled client. That hidden capability expansion materially increases attack surface because a host may grant broad execution privileges for what appears to be a narrow content skill, enabling credential access, filesystem interaction, and remote operations beyond user expectations.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The description says the skill creates a used-car still, script, and video, but the referenced behavior includes OAuth/device auth, token storage, installation telemetry, self-update, uninstall flows, and arbitrary remote MCP invocation. This mismatch is dangerous because users and orchestrators may trust it as a narrow creative workflow while it actually introduces credential handling, persistence, code modification, and broader remote control primitives.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill states that the bundled client silently checks for updates and automatically installs newer releases without separate confirmation, replacing package-owned files. Even with signature verification, silent self-modification is a supply-chain and trust risk because behavior can change after approval, and users may not realize the installed package will mutate locally over time.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The manifest explicitly points to a local credential file containing bearer authentication material, but the user-facing metadata does not warn that local credentials will be accessed to contact a remote MCP endpoint. This creates a transparency and consent problem and increases the chance that a user enables the skill without understanding it will use locally stored tokens for network operations.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The document states that the client silently checks for updates by default and automatically installs newer versions during ordinary command use without separate confirmation. Even though later text describes integrity protections, this behavior still changes installed code implicitly during normal operations, creating integrity and availability risk if an update is faulty, unexpectedly disruptive, or if trust assumptions about the update channel fail.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The documentation states that the bundled client performs an outbound installation registration call and writes a local cache file, but it does not explicitly warn users that package metadata and a stable external installation reference are transmitted or that a file is created under ~/.beatra. Even though the data is described as non-secret and non-billable, silent telemetry and filesystem writes can create privacy, compliance, and transparency issues, especially in enterprise or regulated environments.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The client performs silent automatic self-updates and rewrites installed package files during normal execution, with failures intentionally suppressed in maybe_auto_update(). Even with checksum and manifest validation, this creates a software supply-chain risk: any compromise of the update channel, signing/discovery process, or publisher infrastructure can transparently alter executable code on the user's machine without explicit runtime consent.

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
95% confidence
Finding
The manifest references a concrete local credential file for device-bearer authentication, which means the skill depends on sensitive tokens stored on disk. Even if intended for legitimate MCP access, exposing a direct path to credentials enlarges the attack surface: a compromised or overly permissive skill/runtime could abuse those credentials to access remote services as the user.

Self-Modification

High
Category
Rogue Agent
Content
)
    update = subparsers.add_parser(
        "update",
        help="Check, install, or configure Beatra package self-updates",
    )
    update.add_argument(
        "--check",
Confidence
96% confidence
Finding
The skill includes a built-in self-update mechanism that can replace its own installed files, which is a form of self-modifying code. In the context of an agent skill, this is especially sensitive because execution behavior can change after deployment outside normal review flows; if the update infrastructure or upstream package metadata is compromised, malicious code could be delivered and persisted automatically.

Static analysis

No suspicious patterns detected.