Back to skill

Security audit

TikTok Creator Brief Stills

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its TikTok brief-still purpose, but it asks for broad account powers and silently self-updates executable files by default.

Review this before installing if you are comfortable granting Beatra a shared local credential with broad media and wallet capabilities. Disable automatic updates with `python3 scripts/mcp_client.py update --auto off` if you want to avoid silent code replacement, and only authorize paid lookup or image stages after checking the displayed live credit cost and exact operation.

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:29
Finding
Overprivileged Device Authorization Grants Unrelated Media Capabilities<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py`, lines 29–34 **Vulnerability Type**: Excessive OAuth/device-token privileges **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" ) ``` ### Technical Analysis The Skill is declared as a TikTok creator lookup and collaboration brief still generator. Its legitimate workflow requires public creator lookup, image operations, approved asset uploads, task access, and billing-related functionality. The requested authorization scope additionally grants: - `videos:generate` - `music:generate` - `speech:generate` - `voices:read` - `voices:write` These capabilities are unrelated to producing collaboration brief stills. The broad `mcp:tools` scope and `wallet:spend` permission further increase the impact because `scripts/mcp_client.py` exposes a generic tool-call interface that can submit arbitrary tool names and JSON arguments to the service. Although the token is stored with restrictive permissions on POSIX systems, secure storage does not correct excessive authorization. Any process or future compromised package code capable of using the credential can exercise all privileges represented by the token. ### Attack Path 1. The user runs `scripts/authorize.py`. 2. The authorization request asks the user to approve the complete `SCOPE` value. 3. Beatra issues a bearer token containing both necessary and unrelated media capabilities. 4. The token is saved in `~/.beatra/credentials.json`. 5. A local process, compromised future update, or other component with access to the credential invokes the bundled generic MCP client. 6. The caller submits unrelated video, music, speech, or voice operations. 7. Those operations can consume account credits or access voice-related resources outside the Skill’s declared pu ...[truncated 566 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define a package-specific, least-privilege authorization scope. 2. Remove unrelated capabilities, including: - `videos:generate` - `music:generate` - `speech:generate` - `voices:read` - `voices:write` 3. Replace broad `mcp:tools` access with a server-enforced allowlist for only the operations used by this Skill. 4. Restrict task, artifact, and wallet permissions to resources created by this package where the service supports resource-scoped authorization. 5. Avoid sharing one full-scope token among unrelated Skills. Issue separate package-scoped credentials. 6. Display the exact requested capabilities on the approval page so users can make an informed authorization decision. 7. Add automated tests that fail when the requested scope includes capabilities not present in the Skill’s documented operation inventory. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Default Silent Self-Update Creates a Remote Code Retrieval and Execution Channel<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py`, lines 969–1019 and 1540–1542 **Related Documentation**: `SKILL.md`, lines 231–251; `references/automatic-updates-and-safety.md`, lines 3–7 **Vulnerability Type**: Automatic retrieval and replacement of 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 ...[truncated 3275 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default. Ordinary business commands should not silently replace executable code. 2. Make update checks notification-only unless the user explicitly authorizes installation. 3. Require confirmation that clearly identifies the current version, target version, source, and signer. 4. Sign release metadata and archives using an asymmetric signing key whose public key is pinned in the reviewed client. 5. Protect the update-signing key separately from the web and CDN infrastructure. 6. Verify threshold signatures or use a framework such as TUF where practical to protect against repository compromise and rollback/freeze attacks. 7. Keep update installation separate from credential-bearing and paid-operation processes. 8. After replacement, verify installed file hashes against signed metadata before allowing execution. 9. Preserve the existing path, archive limits, symlink defenses, ownership checks, rollback journal, and redirect rejection because those controls remain valuable. ]]>

other

Warning
Location
scripts/authorize.py:361
Finding
Machine Hostname Is Collected, Persisted, and Transmitted During Authorization<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py`, lines 361–369 and 456–468 **Vulnerability Type**: Host identifier collection and external transmission **Risk Level**: Medium ### Vulnerable Code Hostname collection: ```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] ``` Transmission in the 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, created = post_form(DEVICE_AUTHORIZATION_URL, form) ``` The same value is also persisted in `~/.beatra/host.json` through `write_host_config()`. ### Technical Analysis The code reads the operating-system hostname and sends it to `https://api.beatra.ai/oauth/device_authorization` as `device_name`. A recognizable device label may improve account-console usability, but the hostname is not required for TikTok creator lookup or image generation. Hostnames can contain identifying or sensitive contextual information, including: - A person’s name. - An employer or organization name. - Internal asset inventory identifiers. - Project, customer, or environment names. - Internal naming conventions. The value can be correlated with the stable external installation reference, package slug, package version, account, and detected agent platform. The reviewed documentation describes the installation reference and platform telemetry but does not clearly disclose ...[truncated 1171 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not read or transmit the OS hostname by default. 2. Use a generic device label such as `Beatra Skill Device` or a random local alias. 3. Allow the user to enter or approve a device label before transmission. 4. Clearly disclose every telemetry field, its purpose, destination, and retention period. 5. Store only the selected pseudonymous label rather than the original hostname. 6. Provide a setting to disable device-name telemetry independently from authorization. 7. Minimize correlation by avoiding linkage between a raw hostname and the stable installation identifier unless explicitly required and approved. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:230
Finding
Server-Supplied Upload URL Accepts Any HTTPS Host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py`, lines 230–261 **Vulnerability Type**: Insufficient destination validation for local 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 workflow first obtains upload instructions from the authenticated MCP s ...[truncated 1895 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Maintain an explicit allowlist of approved Beatra upload domains and storage-provider host patterns. 2. Validate domain boundaries correctly; do not use unsafe suffix checks that would accept names such as `trusted.example.attacker.test`. 3. Reject unexpected ports and IP-literal destinations unless explicitly required. 4. Bind the upload URL, expected host, MIME type, length, artifact request, and expiration in a server-signed upload grant. 5. Verify that the returned `artifact_id` corresponds to the same signed grant. 6. Consider showing the destination domain before transmission when it differs from the canonical Beatra service. 7. Continue rejecting redirects and retain the existing regular-file, symlink, file-stability, content-length, and MIME validation controls. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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 (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill invokes a bundled Python client, performs file inspection and uploads, makes network calls, and supports updates, but it declares no explicit permissions. That creates a transparency and consent gap: a user may approve a seemingly simple content-generation skill without realizing it can read local files, write package-owned files, access the network, and execute shell commands through the wrapper workflow.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The documented purpose is limited to reading a TikTok profile and generating brief stills, but the skill also describes authentication flows, persistent credential storage, arbitrary MCP tool invocation, telemetry/registration, local file upload, and automatic software update/install behavior. This mismatch is dangerous because users may grant trust and provide inputs under a much narrower mental model than the skill's real capabilities, increasing the risk of unintended data exposure, credential misuse, or unreviewed code changes.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill states that the bundled client silently checks for and automatically installs newer releases without separate confirmation. Even with signature verification, silent auto-update materially expands the trust boundary by allowing code behavior to change after initial review, which can introduce supply-chain risk or unanticipated new capabilities in an environment that also handles credentials, files, and paid remote operations.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The document states that the client silently checks for and automatically installs newer releases by default before normal commands, without separate confirmation. Even with integrity checks and rollback protections, modifying installed files automatically can violate user expectations, expand the trust boundary to remote update infrastructure, and create security and operational risk if the update channel is ever compromised or if users are unaware that execution may change their local installation.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The instructions explicitly direct the agent to perform a prepaid social lookup via `beatra.social.execute` and note that each execute and page incurs a charge, but they do not require a clear user-facing warning or explicit confirmation immediately before the paid action. In an agent context, this can cause unauthorized or surprising spend, especially when a user provides a handle or URL expecting passive analysis rather than a billable external lookup.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The client performs automatic silent self-updates during normal execution, which modifies installed package files without an interactive prompt or explicit run-time notice. Although there are integrity checks and host restrictions, this still creates a remote code update channel that can change behavior on the next invocation; compromise of the update source, signing pipeline, or delivery account would propagate code changes automatically to installed clients.

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
This skill contains built-in self-modification capability via the update command and auto-update path, allowing the package to replace its own installed files. Self-updating code materially increases supply-chain risk because execution of future logic becomes dependent on remote infrastructure and update metadata, even if current transport, checksum, and path validations are careful.

Static analysis

No suspicious patterns detected.