Back to skill

Security audit

Xiaohongshu Wealth FAQ Talking

Security checks for vulnerabilities and agentic risk

Overview

This skill is mostly transparent about making paid Beatra media clips, but it deserves review because it stores a broad shared account token and silently updates its own package code by default.

Install only after accepting that Beatra will receive selected media/content for generation, paid calls can spend credits after approval cards, a broad shared Device Token will be stored in ~/.beatra, and package code silently auto-updates by default. Review the Beatra approval scopes and consider disabling automatic updates with the documented command after installation.

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
Default-Enabled Silent Remote Code Replacement<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1017, 1541-1544` **Vulnerability Type**: Silent retrieval and installation of remotely changeable executable code **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( install_root=resolved ...[truncated 2387 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Disable automatic installation by default and require explicit, informed user approval before replacing executable files. - Separate update checking from update installation; a background check may report availability but must not silently apply code. - Sign release metadata and archives with an offline release key, and pin the corresponding public key in the audited package. - Verify the version, package identity, channel, complete manifest, and archive against that signature. - Display the target version and changed files before installation. - Preserve the existing path, archive, checksum, size, rollback, and ownership validations as defense-in-depth. - Consider having the host platform perform updates outside the credential-bearing client process. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/authorize.py:31
Finding
OAuth Device Token Requests Capabilities Beyond the Declared Workflow<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:31-34` **Vulnerability Type**: Excessive authorization 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" ) ``` The existing-credential validation also requires an exact match with this complete scope: ```python or set(value["scope"].split()) != set(SCOPE.split()) ``` ### Technical Analysis The Skill's declared workflow needs public-note lookup, media upload, voice or speech generation, video generation, task inspection, and billing-related operations. The authorization request additionally includes capabilities such as `images:generate` and `music:generate`, which are not required to turn supplied still images and written answers into narrated FAQ videos. The exact-scope check prevents the helper from accepting a narrower credential, even if that credential contains all permissions actually required by the Skill. The token also combines read, write, cancellation, generation, and wallet-spending capabilities into one bearer credential. This materially increases the authority exposed if the credential is stolen, the package is maliciously updated, or another Beatra Skill sharing the same credential is compromised. ### Attack Path 1. The user runs `scripts/authorize.py`. 2. The authorization helper requests the complete hard-coded scope set. 3. The resulting bearer token is saved in `~/.beatra/credentials.json`. 4. A compromised local process, malicious package update, or compromised Skill obtains the token. 5. The attacker invokes generation or spending capabilities unrelated to this Skill's legitimate task, including unused image or music generation permissions. ### Impact Assessment Possession of the token can enable paid operations, artifact access ...[truncated 243 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Define a minimal scope set based strictly on the operations used by this Skill. - Remove `images:generate` and `music:generate` unless a documented workflow genuinely requires them. - Avoid requiring exact equality with an expansive scope set; instead, verify that the credential contains the minimum required permissions. - Separate wallet spending from read-only wallet access where supported. - Use per-Skill credentials or audience-restricted tokens rather than sharing one full-scope bearer token across all packages. - Require separate, explicit authorization before enabling materially different or paid capability families. - Document each requested permission and map it to a specific Skill operation on the approval page. ]]>

other

Note
Location
scripts/authorize.py:342
Finding
Hostname and Agent-Environment Data Are Collected and Transmitted<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:342-370, 446-462` **Vulnerability Type**: Environment reconnaissance and device telemetry beyond minimum functional requirements **Risk Level**: Low ### 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 included in the 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 ``` ### Technical Analysis The authorization helper inspects environment-variable signatures to identify the ag ...[truncated 1585 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not collect or transmit the hostname by default. - Use a random, non-identifying device alias if a console display name is needed. - Make installation and source-attribution telemetry opt-in and explain each transmitted field before authorization. - Minimize environment inspection to a user-supplied platform value or the generic value `unknown`. - Provide a documented setting to disable registration and per-call source telemetry. - Apply clear retention limits and avoid correlating telemetry with creative content unless strictly necessary. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:231
Finding
Server-Supplied Upload URL Is Not Restricted to Trusted Storage Hosts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:231-265` **Vulnerability Type**: Insufficient validation of remote upload destinations **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 URL is supplied by the remote MCP response. The client verifies HTTPS, the absence ...[truncated 1602 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Allowlist the exact documented object-storage hostnames used by Beatra. - Validate hostname suffixes with label boundaries and reject unexpected ports, IP literals, loopback, link-local, and private addresses. - Do not forward arbitrary response-provided headers; allow only the minimum required upload headers. - Bind the upload grant cryptographically to the destination, HTTP method, content digest, MIME type, size, expiration time, and artifact identity. - Calculate a local content digest and require it to match the signed grant before transmission. - Keep redirect rejection enabled for upload requests. - Display or log the validated destination domain without exposing signed query parameters. ]]>

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` **Vulnerability Type**: Missing credential-file access-control verification on Windows **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") ``` The authorization helper similarly applies restrictive modes 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, the MCP client verifies that the state directory is owned by the current user with mode `0700`, opens the credential with `O_NOFOLLOW`, and requires an owner-only `0600` regular file. On Windows, it directly reads the credential and assumes the user profile's inherited ACL is sufficiently private. That assumption may not hold on shared systems, migrated profiles, enterprise-managed endpoints, or directories with modified inheritance. The implementation neither creates an owner-only discretionary access-control list nor verifies that other users or groups cannot read the bearer token. It also does not perform an equivalent Windows reparse-point and file-owner validation. ### Attack Path 1. The Skill creates `~/.beatra/credentials.json` under a Windows profile directory with permissive or altered inherited ACLs. 2. A ...[truncated 649 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create the state directory and credential file with an explicit ACL granting access only to the current user and required system principals. - Disable unsafe ACL inheritance where appropriate. - Verify file ownership and effective read permissions before accepting the credential. - Reject reparse points, symbolic links, non-regular files, and unexpected path substitutions. - Use atomic creation and replacement while preserving the restrictive ACL. - Fail closed with a clear remediation message if confidentiality cannot be established. - Add automated tests covering shared profiles, permissive inherited ACLs, reparse points, and credential replacement. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (16)

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill exposes broad capabilities—environment access, file read/write, network, and shell execution—without declaring permissions or constraining them in the manifest. In this context, the instructions direct use of a bundled Python client, local file inspection/upload, credential handling, and updater behavior, so the undeclared capability surface materially increases the chance of hidden data access, command execution, or exfiltration beyond the user’s expectation.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared purpose is simple FAQ clip generation, but the skill also covers OAuth/device authorization, persistent credential storage, arbitrary MCP tool invocation via stdin-driven CLI, local file upload, telemetry/registration, uninstall state changes, and self-updating package replacement. This mismatch is dangerous because users and host systems may grant trust based on a narrow media-production description while the skill actually performs broader account, filesystem, and code-management actions.

Description-Behavior Mismatch

Medium
Confidence
85% confidence
Finding
The skill extends beyond FAQ-video creation into wallet balance checks, billing ledger access, and package-management concerns. While these may be operationally related, they broaden the data and action surface into financial/account metadata and system maintenance functions that are not obvious from the skill’s stated purpose.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The skill includes automatic update logic that downloads and replaces package-owned files without separate confirmation. Any mechanism that can modify executable package content at runtime materially increases supply-chain and remote-code-execution risk, especially when embedded inside a skill whose advertised purpose is unrelated to software maintenance.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The manifest’s changelog references balance and ledger retrieval capabilities that are unrelated to the declared purpose of generating spoken Xiaohongshu FAQ clips. This mismatch is a strong indicator of hidden or over-broad financial data access, making the skill more dangerous because users would not reasonably expect account or ledger interaction from a media-generation workflow.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
Read-only access to balance and ledger data is context-inappropriate for a skill that only claims to transform public note questions into short talking videos. Even without write access, exposing financial account metadata can leak sensitive information and creates an unexpected privacy and data-minimization violation.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The authorization scope is far broader than the skill’s stated purpose of creating spoken FAQ clips. It requests wallet spending, task management, voice management, music generation, and broad artifact access, so if the token is misused or the skill is compromised, the resulting account access exceeds what users would reasonably expect for this workflow.

Context-Inappropriate Capability

Medium
Confidence
79% confidence
Finding
The code fingerprints the host environment and captures the device hostname, then persists that metadata during authorization. For a FAQ video generation skill, this collection is not clearly necessary and can expose identifying information about the user’s machine and agent environment, increasing privacy risk and enabling downstream profiling.

Context-Inappropriate Capability

Medium
Confidence
83% confidence
Finding
The skill writes a device-local inventory of installed skills and absolute installation paths, which is unrelated to producing short spoken FAQ videos. This creates unnecessary local surveillance data about other installed packages and filesystem layout, which can aid profiling, correlation, or later abuse if local state is accessed by another component.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The client contains a full self-update mechanism that downloads manifests and archives from remote infrastructure and replaces local package files on disk, which exceeds the stated purpose of generating FAQ talking clips. Even though the updater includes several integrity and path-safety checks, it still gives the vendor a remote code modification channel into the installed skill, increasing supply-chain risk and making future behavior changes possible without clear user review at execution time.

Description-Behavior Mismatch

Medium
Confidence
90% confidence
Finding
The script records installation telemetry and maintains a local skill inventory unrelated to producing spoken FAQ clips. This expands data collection beyond least-privilege expectations for the skill and can expose behavioral metadata about installed skills, usage timing, platform, and installation identifiers to the vendor backend.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The code fingerprints the host environment using environment variables and a persisted host.json value, then transmits the platform with business and registration calls. For a FAQ talking-clip skill, this collection is not obviously necessary and increases tracking capability and environment profiling, which may aid targeted behavior changes or backend-side segmentation.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The document states that the client performs silent update checks by default and automatically installs higher versions without separate confirmation. Even with integrity checks and pinned sources, unattended self-update changes executable/package files without explicit user consent at update time, which creates supply-chain and trust-boundary risk if the update channel, signing/checksum process, or release pipeline is ever compromised.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill explicitly states that a lookup is prepaid and that each additional page incurs another charge, but it does not require an explicit user confirmation immediately before the charged `beatra.social.execute` call. That creates a real risk of unintended spend, especially because the workflow is operationally framed as a normal lookup step and could be triggered by an advisor who has not affirmatively accepted the live price.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The maybe_auto_update() path performs silent background update checks and can replace installation files before executing normal commands, without a user-facing warning at the point of execution. In a skill whose advertised function is simple media generation, silent code replacement materially raises trust and supply-chain concerns because users may execute substantially changed code without informed consent.

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 CLI includes self-update functionality that can modify the package's own installed code. Self-modification is especially risky in a skill advertised for FAQ clip creation because it introduces functionality unrelated to the user-facing purpose and creates a durable remote change mechanism if the update channel is compromised or abused.

Static analysis

No suspicious patterns detected.