Back to skill

Security audit

RFP Cover Stills

Security checks for vulnerabilities and agentic risk

Overview

The skill is openly a remote Beatra image-generation workflow, but it requests and reuses broad account authority and can silently replace its own package files during normal use.

Review this before installing if you are comfortable linking a Beatra account to a local shared credential, granting broad Beatra media/account scopes, allowing default silent package self-updates, and sending optional local reference files to Beatra-controlled upload flows. Disable auto-updates with the documented command if you install it and want reviewed-code stability.

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:34
Finding
Excessive OAuth Scopes and Unrestricted MCP Tool Invocation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:34-37`; `scripts/mcp_client.py:1463-1480` **Vulnerability Type**: 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" ) ``` ```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 is declared as an RFP-cover image generator, but its authorization request includes unrelated permissions for video, music, speech, voice modification, generic wallet spending, and task cancellation. These permissions exceed the minimum capabilities needed to generate and optionally edit image covers. The bundled client compounds this issue by accepting any caller-supplied MCP tool name. It does not enforce a local allowlist corresponding to the Skill's documented operations. Consequently, any process or agent instruction able to invoke the bundled client can attempt unrelated operations using the shared full-scope bearer token. The credential is shared among Beatra Skills, increasing the potential blast radius if the client, instructions, or local environment are compromis ...[truncated 1285 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the broad scope with the minimum permissions required for this Skill: - Image generation and image editing. - Model-price lookup. - Required artifact upload and result retrieval. - Read-only task polling. - Narrow read-only wallet access where explicitly requested. 2. Remove video, music, speech, voice-write, generic wallet-spend, and task-cancel scopes unless a documented workflow requires them. 3. Use per-Skill credentials or audience-restricted capability tokens instead of one shared full-scope device token. 4. Add a strict local allowlist in `_run_command`, rejecting any tool outside the documented RFP-cover workflow. 5. Separate read-only and billable operations and require explicit user confirmation immediately before each billable operation. 6. Have the server enforce package-specific tool policies rather than relying exclusively on client-side restrictions. 7. Display the exact requested scopes to the user on the authorization page before approval. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Silent Automatic Retrieval and Replacement of Executable Skill Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1017`; `scripts/mcp_client.py:1527-1534`; `references/automatic-updates-and-safety.md:3-7` **Vulnerability Type**: Unattended remote payload retrieval and code replacement **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) ...[truncated 3454 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default. Automatic checks may remain available, but installation should require explicit informed user approval. 2. Separate `check` and `install` behavior so ordinary creative commands never modify executable package files. 3. Sign discovery metadata and package manifests with an offline-protected release key. 4. Pin the corresponding public key in the audited client and verify signatures before trusting version numbers, URLs, or hashes. 5. Use key rotation metadata with explicit trust transitions and rollback protection. 6. Display the proposed version, publisher identity, changelog, and changed executable files before installation. 7. Consider delegating updates to the host platform's trusted package manager rather than implementing self-replacement in the Skill. 8. Preserve the existing archive traversal, ownership, size, rollback, and redirect protections; they remain valuable defense-in-depth. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:231
Finding
Server-Supplied Upload URL Is Not Restricted to an Approved Host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:231-263` **Vulnerability Type**: Insufficient validation of externally supplied 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 upload grant is returned by the remote MCP service. The client verifies that t ...[truncated 2008 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Maintain an explicit allowlist of authorized upload hostnames or tightly controlled hostname suffixes. 2. Reject unexpected ports, IP-literal hosts, user information, fragments, and nonstandard URL forms. 3. Resolve the hostname and reject loopback, link-local, private, multicast, and other non-public address ranges where they are not explicitly required. 4. Bind upload grants cryptographically to the authenticated account, artifact identifier, destination host, MIME type, byte length, and expiration time. 5. Verify the upload grant's signature or audience locally before sending file bytes. 6. Require renewed user confirmation if an upload destination differs from the expected Beatra storage domain. 7. Avoid forwarding arbitrary response-provided headers; allow only the minimal headers required by the approved storage service. ]]>

other

Note
Location
scripts/authorize.py:339
Finding
Unnecessary Collection and Transmission of Hostname and Agent-Environment Telemetry<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:339-369`; `scripts/authorize.py:444-470`; `scripts/mcp_client.py:1150-1166`; `scripts/mcp_client.py:1354-1398` **Vulnerability Type**: Device and agent-environment telemetry beyond core functionality **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] ``` ```python external_reference = _installation_reference(state_dir) 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 ...[truncated 2374 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make telemetry opt-in rather than automatic. 2. Omit the hostname from authorization by default. 3. Present the exact telemetry fields and their purpose before transmitting them. 4. Allow users to supply a non-identifying display label instead of the system hostname. 5. Replace the long-lived installation reference with a per-package or periodically rotated pseudonymous identifier where persistent identity is not required. 6. Minimize platform detection to a generic capability value rather than naming the specific agent environment. 7. Provide a configuration option that disables registration telemetry without disabling the core creative functionality. 8. Define retention, deletion, and cross-service correlation policies for any device metadata retained by the service. ]]>
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (27)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes a bundled Python client, reads local files for inspection/upload, writes local state for updates and credentials, uses network access to remote services, and can modify package-owned files via self-update, yet it declares no permissions. This creates a deceptive trust boundary: a user selecting a simple image-cover skill would not reasonably expect shell execution, persistent local state, file handling, and networked update behavior, which increases the risk of unintended data access or code execution pathways.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared purpose is narrow—generate RFP cover images from seller-supplied facts—but the skill also encompasses authentication flows, persistent credential storage, arbitrary remote MCP tool invocation, file upload, telemetry/registration, uninstall behavior, and automatic self-updating code replacement. This mismatch is dangerous because it hides materially different behaviors from users and reviewers, making privilege escalation, data exfiltration, and supply-chain risk easier to smuggle in under an innocuous description.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The manifest presents the skill as a simple RFP cover generator, but it is wired to a remote MCP endpoint using bearer authentication and a local credential file. That mismatch expands the trust boundary and enables undisclosed network access and account-scoped operations that users would not reasonably expect from the described functionality.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
Using device-bearer authentication backed by a local credential file is not justified by the stated purpose of generating cover stills. If invoked, the skill may access authenticated remote resources or transmit user/project data to an external service under stored credentials, creating unnecessary exposure and the possibility of privilege misuse.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The file documents a full remote authorization and MCP access flow for Beatra, including persistent credential storage, browser-based device authorization, and remote tool invocation. That behavior is materially unrelated to a skill advertised as generating RFP cover graphics from seller-supplied tender facts, making the networked capability suspicious and capable of silently expanding the skill's privileges beyond its declared purpose.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The documentation introduces shared credential handling and storage of a device token for a skill whose stated function is local document-cover composition. Embedding account-linking and credential persistence into an unrelated media/layout skill creates an unjustified secret-handling surface and could enable unauthorized access to external services under the guise of a benign workflow.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The skill instructs the agent to open a browser, drive the user through device authorization, and poll a remote service until a token is issued, despite no apparent need for such access in RFP cover generation. This creates an unnecessary pathway to obtain and persist external account credentials while normalizing background approval flows the user may not associate with the advertised skill purpose.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The skill requests a very broad OAuth scope set including wallet spending, task control, artifact read/write, and media generation capabilities far beyond what an RFP cover image skill appears to need. Over-privileged tokens materially increase blast radius if the skill, its storage, or a downstream dependency is compromised, and the mismatch between declared purpose and requested authority is especially suspicious in this context.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
The script fingerprints the host agent platform from environment variables and captures a recognizable hostname, then persists that data to disk. For a skill whose stated purpose is generating procurement/RFP cover stills, this host-identification collection is not obviously necessary and creates unnecessary privacy and tracking risk if the local state is exposed or correlated across installations.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The script maintains a local inventory of installed skills including slug, platform, and absolute install path, which exceeds the stated function of producing cover graphics. This creates unnecessary local surveillance of user tooling and filesystem layout, which could become sensitive reconnaissance data for follow-on abuse if read by another component or leaked.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
The client contains a full self-update system that downloads manifests and archives from remote infrastructure and then replaces local installation files. Even with checksum and path validation, this is functionality unrelated to generating RFP cover stills and materially expands the trust boundary: a compromised vendor/CDN/account can push new code onto the host. In the context of a creative skill, hidden code-replacement capability is especially risky because users would not expect ongoing executable modification.

Description-Behavior Mismatch

Medium
Confidence
90% confidence
Finding
The upload path can exfiltrate any readable local regular file up to 100 MB to a remote service, while the stated skill purpose is only to turn seller-supplied tender facts into cover stills. That mismatch increases the chance of abuse, accidental overcollection, or coercing an agent/user into uploading sensitive local files unrelated to the task.

Description-Behavior Mismatch

Medium
Confidence
87% confidence
Finding
The code records local skill inventory and installation telemetry, including install path, package metadata, platform, and timestamps, beyond what is needed to generate an RFP cover image. This creates unnecessary local tracking and outbound metadata disclosure that users would not infer from the advertised creative function.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
The skill fingerprints the host agent/platform via environment variables and persisted host metadata, then transmits that context with requests. For an RFP cover generation skill, this is not functionally necessary and increases privacy risk while giving the remote service environmental intelligence that could support targeting or differential behavior.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
This uninstall script manages shared OAuth device state and can revoke a device token, which is unrelated to the advertised purpose of generating RFP cover images. Even if framed as cleanup logic, bundling shared-account credential handling into a graphics/document skill expands privilege and creates a pathway to disrupt other installed skills or alter authentication state on the host.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The code performs a network call to revoke an OAuth device token during uninstall, despite the skill's stated business function being document-cover generation. This is dangerous because uninstalling a seemingly low-risk content skill triggers remote authentication-side effects, which violates least privilege and can unexpectedly disable access or interfere with shared platform connectivity.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The documentation states that the client silently checks for updates and installs newer versions automatically without separate confirmation. Even with integrity checks and rollback protections, this is system-modifying behavior that can change executable code unexpectedly, increasing supply-chain and change-management risk if users are not clearly warned and given explicit control.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The authorization flow asks for broad, high-impact scopes but the user-facing output in this script only instructs the user to open the page and click Allow, without enumerating the powers being granted. This weakens informed consent and makes it easier for an over-privileged or misleading skill to obtain access inconsistent with user expectations.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
maybe_auto_update() performs silent best-effort update checks and local package replacement during normal execution, without a user-facing prompt in that path. Silent code changes at runtime undermine auditability and user consent, and they are especially suspicious in a skill whose declared purpose is simple document-cover generation.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The registration path sends installation metadata to the remote service as best-effort telemetry without user-facing disclosure in this code path. While not directly code-execution dangerous, it weakens transparency and may leak operational metadata inconsistent with the skill’s narrow business purpose.

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
92% confidence
Finding
Referencing a local credential file in the skill manifest is sensitive because it normalizes access to reusable authentication material. In the context of a skill that only claims to produce document-cover graphics, credential usage is disproportionate and could allow unauthorized authenticated requests, account misuse, or leakage of token-bearing data if the integration is abused or misconfigured.

Credential Access

High
Category
Privilege Escalation
Content
- `~/.beatra/installation.json` contains one stable, non-secret installation
  reference.
- `~/.beatra/credentials.json` contains the single Device Token.

On POSIX systems the directory must be mode `0700` and both files mode `0600`.
On Windows the current user must be the only principal granted access through
Confidence
91% confidence
Finding
The documentation directs creation and use of a persistent credentials file containing a Device Token. In the context of a skill that should only generate tender-cover graphics, introducing long-lived credential storage unnecessarily exposes users to token theft, account misuse, or covert reuse by other components sharing the same environment.

Credential Access

High
Category
Privilege Escalation
Content
4. polls every 5 seconds for up to 15 minutes while the user signs in (or
   creates their account) and selects Allow;
5. atomically saves the returned Device Token to
   `~/.beatra/credentials.json` without printing an HTTP response body;
6. validates the new credential with the same non-billable MCP request and
   prints Ready only after it succeeds.
Confidence
93% confidence
Finding
Saving a returned Device Token to a local credentials file establishes persistent access to a remote service. For a skill advertised as a document-cover generator, this is an unjustified credential acquisition and persistence step that could be abused for continued remote operations without the user expecting that level of access.

Credential Access

High
Category
Privilege Escalation
Content
scope = _required_string(polled, "scope")
            if set(scope.split()) != set(SCOPE.split()):
                raise RuntimeError("Beatra authorization returned an unsupported scope")
            credential_path = state_dir / "credentials.json"
            _atomic_json(
                credential_path,
                {
Confidence
70% confidence
Finding
The script stores a bearer access token in plaintext JSON under ~/.beatra/credentials.json. Although file permissions are restricted, a plaintext long-lived token with broad scopes represents a high-value local secret; if another local process, backup system, or compromise reads it, the attacker gains the full over-scoped Beatra capabilities granted to this skill.

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 script explicitly targets credentials.json as part of managed state, indicating that this skill package is aware of and able to remove shared credential material. In the context of a tender-cover generation skill, access to credential storage is unjustified and increases the risk of account disruption, credential misuse, or destructive cleanup beyond the user's expectation.

Static analysis

No suspicious patterns detected.