Back to skill

Security audit

course-narration-studio

Security checks for vulnerabilities and agentic risk

Overview

This is a real course-narration integration, but it needs Review because it uses a broad shared Beatra credential and silently self-updates installed package files by default.

Install only if you trust Beatra with a broad shared device authorization and trust its release channel to update this skill. Consider running `python3 scripts/mcp_client.py update --auto off` after installation, review the Beatra approval page carefully, and avoid uploading sensitive narrator samples unless you are comfortable sending them through Beatra’s upload flow.

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:31
Finding
Overprivileged Shared Device Token and Unrestricted MCP Tool Invocation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:31-35`; `scripts/mcp_client.py:1458-1472` **Vulnerability Type**: Excessive authorization scope and missing client-side tool allowlist **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 command dispatcher accepts an arbitrary 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}, ) ``` The CLI exposes that unrestricted parameter: ```python call = subparsers.add_parser("call", help="Call one tool with a JSON object on stdin") call.add_argument("tool_name") ``` ### Technical Analysis The Skill's declared function is course narration. Its legitimate operations include text-to-speech generation, voice listing or cloning, narrator-sample upload, relevant artifact access, task polling, and billing reads. The requested bearer-token scope additionally authorizes unrelated image, video, and music generation, general wallet spending, task cancellation, and broad artifact/task access. The documentation identifies this as a shared, full-scope device token used by multiple Beatra Skills. The local client ...[truncated 1714 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Issue a package-specific token limited to the exact narration operations required by this Skill. 2. Remove unrelated image, video, and music generation scopes. 3. Separate wallet reads from spending authority. Grant spending only for explicitly supported speech or voice operations. 4. Restrict task and artifact access to resources created by this package or installation. 5. Add a hardcoded client-side allowlist of accepted tool names and reject every other `tool_name`. 6. Require explicit user confirmation immediately before each billable tool call. 7. Use separate tokens for separate Skills instead of sharing one full-account bearer token. 8. Where the backend supports it, bind authorization to the package slug, installation identifier, operation class, and resource ownership. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Silent Remote Replacement of Executable Skill Files Without Independent Signature Verification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1019`; update sources at `scripts/mcp_client.py:31-32` **Vulnerability Type**: Default-enabled remote code update channel **Risk Level**: High ### Vulnerable Code The release locations are remotely hosted: ```python PACKAGE_DISCOVERY_URL = "https://beatra.ai/skills/course-narration-studio/channels/clawhub/install.json" PACKAGE_CDN_BASE_TEMPLATE = "https://cdn.beatra.ai/agent-packages/course-narration-studio/channels/clawhub/v{version}" ``` Automatic updates are enabled unless the user has explicitly disabled them: ```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 Fa ...[truncated 3052 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default. Update checks may remain automatic, but replacement should require explicit user approval. 2. Display the current version, proposed version, publisher identity, and changed files before installation. 3. Sign release manifests with an offline or hardware-protected publisher key. 4. Pin the corresponding public verification key in the audited package and verify the signature before trusting any remote hash. 5. Consider transparency-log verification or reproducible-build attestations. 6. Separate update checking from execution so routine business commands never silently alter executable files. 7. Retain the existing redirect rejection, path validation, size limits, checksum checks, ownership checks, atomic replacement, and rollback protections. 8. Record update events in a user-visible local audit log without including credentials or private content. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:237
Finding
MCP-Controlled Upload Grant Can Send Private Media to Any HTTPS Host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:237-258` **Vulnerability Type**: Insufficient validation of sensitive 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) ``` ### Technical Analysis The upload path validates that: - The destination uses HTTPS. - A hostname exists. - The URL contains no embedded username or password. - The URL contains no fragment. - The content type and content length match the local file. - Redirects are rejected by the HTTP implementation. It does not constrain the destination hostname to ...[truncated 1636 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Allowlist the exact Beatra object-storage hostnames permitted for uploads. 2. Reject IP-literal destinations, local addresses, private network ranges, and unexpected ports unless explicitly required. 3. Cryptographically authenticate upload grants and bind each grant to: - Destination hostname and path. - HTTP method. - Artifact identifier. - MIME type. - Exact byte length. - Short expiration time. 4. Display the destination service and sensitivity warning before uploading voice samples. 5. Preserve redirect rejection and existing regular-file, symlink, size, stability, MIME, and length checks. 6. Consider encrypting sensitive voice samples for an intended Beatra-controlled recipient before storage upload. ]]>

other

Note
Location
scripts/authorize.py:347
Finding
Hostname, Agent Platform, and Stable Installation Telemetry Are Collected and Transmitted<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:347-371`; transmission at `scripts/authorize.py:454-469` **Vulnerability Type**: Environment reconnaissance and persistent installation telemetry **Risk Level**: Low ### Vulnerable Code The helper detects the agent environment from process environment variables: ```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" ``` It also collects the local hostname: ```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 collected values are included 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: ...[truncated 2185 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make hostname collection opt-in rather than automatic. 2. Default to a generic device label or ask the user to choose a label. 3. Show the exact telemetry fields and recipients before opening the authorization page. 4. Provide independent controls for: - Hostname transmission. - Agent-platform transmission. - Installation registration. - Per-call source attribution. 5. Avoid transmitting stable identifiers when a short-lived or rotating identifier is sufficient. 6. Document retention, correlation, and deletion behavior for installation telemetry. 7. Continue limiting environment inspection; do not expand it to IP addresses, interface enumeration, user names, process lists, or unrelated environment-variable values. ]]>
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
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (21)

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill declares no permissions, yet its documented workflow requires shell execution, local file access, network access, environment access, and package modification via the bundled client. This creates a transparency and consent problem: users and downstream policy systems cannot accurately assess the real trust boundary before execution.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill presents itself as a narration tool, but its behavior includes authentication flows, credential storage, arbitrary remote tool access via an MCP client, local file uploads, telemetry/registration, uninstall logic, and self-updating package replacement. This mismatch can cause users to authorize far broader system and account access than expected, increasing the risk of credential compromise, privacy loss, or unauthorized system changes.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The skill includes silent self-update and in-place package replacement behavior unrelated to its core narration function. Even with integrity checks described, silent code replacement expands the attack surface and trust dependency to future remote content, enabling unexpected behavior changes without the user's contemporaneous approval.

Intent-Code Divergence

Low
Confidence
88% confidence
Finding
Labeling the mechanism as 'safe automatic updates' may reassure users while the text also states that newer releases install without separate confirmation. This framing is risky because it downplays the security significance of unattended code changes and may reduce user scrutiny over a privileged operation.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The documentation describes a bundled client that silently checks for and automatically installs updates before ordinary commands, which is unrelated to a course narration skill's stated purpose. Even with integrity checks and fixed sources, silent self-updating introduces system-modifying behavior and supply-chain risk into a context where users would not reasonably expect code replacement.

Context-Inappropriate Capability

High
Confidence
95% confidence
Finding
The documented ability to install updates and replace local package files is unnecessary for generating course narration and materially expands the skill's capability beyond its declared function. Any mechanism that can replace installed files can be abused through compromise, misconfiguration, or future logic flaws to alter the local environment.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The authorization flow requests a very broad OAuth scope set, including images, videos, music, task control, artifact access, and wallet spending, even though the skill is described as a course narration audio tool. This violates least privilege and turns any compromise, misuse, or backend-side action performed through this credential into a much higher-impact event than necessary.

Context-Inappropriate Capability

Critical
Confidence
95% confidence
Finding
The requested scopes include voices:read, voices:write, tasks:read, tasks:cancel, and broad artifact permissions that are not clearly justified by a narration-focused skill. These permissions may expose or alter unrelated account resources and operational state beyond what is needed to generate audio.

Context-Inappropriate Capability

High
Confidence
95% confidence
Finding
The requested scopes include voices:read, voices:write, tasks:read, tasks:cancel, and broad artifact permissions that are not clearly justified by a narration-focused skill. These permissions may expose or alter unrelated account resources and operational state beyond what is needed to generate audio.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The requested scopes include voices:read, voices:write, tasks:read, tasks:cancel, and broad artifact permissions that are not clearly justified by a narration-focused skill. These permissions may expose or alter unrelated account resources and operational state beyond what is needed to generate audio.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The client contains a full remote self-update and package replacement mechanism that downloads code and overwrites installed files, functionality unrelated to course narration. Even though it includes integrity checks and path validation, it materially expands the trust boundary: compromise of the discovery/manifest/signing pipeline or unintended invocation can lead to arbitrary code replacement on the host.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The skill records local inventory and transmits installation registration telemetry that is not necessary for generating narration audio. This creates additional data collection and persistence about installed skills, paths, and host platform context, increasing privacy and tracking risk beyond the stated purpose of the skill.

Context-Inappropriate Capability

Medium
Confidence
86% confidence
Finding
The skill inspects environment variables and host metadata to identify the surrounding agent platform for attribution and telemetry. In a narration-audio skill, this is unrelated to core functionality and increases fingerprinting of the execution environment, which can aid profiling or selective behavior based on host type.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
This uninstall script manages shared device authorization state and remote token revocation, which is materially unrelated to a course narration/audio skill’s stated purpose. Even if intended as lifecycle management, the capability expands the trust boundary by giving the package power over shared credentials used by other skills, so compromise or misuse could disrupt unrelated functionality or remove access unexpectedly.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The code performs authenticated POST requests to revoke a device token and coordinates deletion of shared credential state from ~/.beatra. In the context of a course narration skill, this is an unjustified destructive capability: if triggered inappropriately, it can sever platform access for the user or other installed skills, creating denial of service and enabling tampering with shared authentication state.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill description does not prominently disclose that it can install newer releases automatically without separate confirmation, despite this being a system-modifying behavior. Omitting that warning up front undermines informed consent and may cause users to invoke a content-production skill without realizing it can change local software.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
Enabling automatic file replacement by default without separate confirmation creates a transparency and consent failure: ordinary commands may trigger system changes the user did not knowingly authorize. In a benign course-production tool, this mismatch between expected behavior and actual behavior makes the feature more dangerous because users are less likely to scrutinize it.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The document states that the bundled client automatically performs an installation registration call and writes a local cache file on first use, but it does not present this as a user-consent, privacy, or filesystem-modifying behavior requiring explicit notice. Even if the data is described as non-secret and non-billable, this is still telemetry-like collection and persistence that can surprise users, create compliance issues, and weaken trust if performed silently.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The client can silently auto-update itself and modify installed package files during ordinary execution without a user-facing warning at the moment it happens. Silent code replacement is dangerous because it changes the software supply chain behavior of the skill and can introduce new code on future runs without informed approval.

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
93% confidence
Finding
The function reads an access token from ~/.beatra/credentials.json so the skill can perform remote revocation of shared device authorization. Accessing shared credentials from within a content-generation skill violates least privilege and creates a path for credential misuse, service disruption, or later modification to exfiltrate tokens without the user expecting such behavior from this type of skill.

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
Self-modification is present as a first-class capability via the update command and supporting updater code. In the context of a lesson-narration skill, the ability to rewrite its own installed code is unnecessary and significantly increases supply-chain and persistence risk if the update path or upstream infrastructure is ever compromised.

Static analysis

No suspicious patterns detected.