Back to skill

Security audit

Blackboard One-Shot Clips

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly matches its blackboard-video purpose, but it asks for broad shared account permissions and silently updates its own code by default.

Review this before installing in sensitive environments. Use it only if you are comfortable granting a shared Beatra device token with broad generation and wallet-related permissions, sending limited host/install metadata to Beatra, and allowing package-owned files to update automatically unless you run `python3 scripts/mcp_client.py update --auto off`.

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
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/authorize.py:31
Finding
Authorization Requests Permissions Beyond the Skill's Functional Requirements<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:31-34` **Vulnerability Type**: Excessive OAuth device-token scope **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 broad authorization model is also explicitly documented in `references/installation-and-auth.md:72-74`: ```text One approval covers image, video, music, speech, upload, model, and task tools. ``` ### Technical Analysis The declared function of this Skill is to upload authorized blackboard photographs and generate one image-to-video clip for each photograph. However, the authorization request includes permissions for unrelated capabilities, including: - Music generation - Speech generation - Voice generation - Voice data read and write access - General image generation - Broad MCP tool access - Wallet spending - Artifact reading - Task cancellation Some permissions, such as video generation, artifact upload, task reads, and potentially wallet reads, are reasonably connected to the declared workflow. Music, speech, and voice permissions are not required for silent blackboard animation. The resulting bearer credential is also shared among Beatra Skill packages through `~/.beatra/credentials.json`. This increases the consequence of credential compromise because a single token grants access to substantially more functionality than this Skill needs. This violates the principle of least privilege. Even if the additional permissions are not used by the current code, obtaining them expands the available attack surface and the privileges exposed through token theft, malicious updates, or another process running under the same user. ### Attack Path 1. The user runs `scripts/authorize.py`. 2. The authorization helper requests the entire hardcoded `SCOPE`. 3. The user approves ...[truncated 1148 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the hardcoded full scope with the smallest package-specific scope required for: - Uploading the input photograph - Reading compatible model information - Generating an image-to-video result - Reading the resulting task - Cancelling a task only when cancellation is supported by the workflow - Reading wallet information only when explicitly requested 2. Remove music, speech, voice, and unrelated image-generation permissions. 3. Separate wallet-read and wallet-spend permissions if the service supports distinct scopes. 4. Issue a package-specific credential instead of sharing one full-scope token among every installed Beatra Skill. 5. Require explicit reauthorization if the package later introduces a capability requiring a new scope. 6. Present the exact requested scopes and their purposes to the user before authorization. 7. Add automated tests that compare the requested scope against an allowlist derived from the Skill's declared operations. ]]>

other

Warning
Location
scripts/authorize.py:362
Finding
Authorization Collects and Transmits Unnecessary Host and Environment Identifiers<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:339-378, 426-437` **Vulnerability Type**: Unnecessary host reconnaissance and telemetry **Risk Level**: Medium ### Vulnerable Code The helper inspects process-environment signatures to identify the host agent: ```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 detected values are then included in the remote 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) ...[truncated 2595 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `socket.gethostname()` collection unless a documented functional requirement depends on it. 2. Use a generic user-selected device label instead of silently deriving the operating-system hostname. 3. Make platform and installation telemetry opt-in and disabled by default. 4. Clearly disclose every transmitted field before authorization, including: - Device name - Platform - Package slug and version - Stable installation reference 5. Avoid stable cross-session identifiers where aggregate, non-identifying telemetry is sufficient. 6. Do not persist host identifiers in `~/.beatra/host.json` unless the user consents. 7. If platform detection is technically necessary, limit it to a generic capability class and do not send the hostname. 8. Add a command-line option such as `--no-telemetry` and ensure creative operations remain fully functional when telemetry is disabled. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:969
Finding
Silent Automatic Updates Permit Post-Audit Remote Code Replacement<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:969-1019, 1518-1539` **Vulnerability Type**: Silent remote payload retrieval and installation **Risk Level**: High ### Vulnerable Code Automatic updates are enabled when the state does not explicitly disable them. A higher remote version is downloaded and applied without separate confirmation: ```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"] ...[truncated 4467 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic updates by default. 2. Require explicit, informed user approval before downloading or installing a new version. 3. Verify every release with an asymmetric digital signature using a public key pinned in the reviewed package. 4. Keep the signing key separate from the CDN and release-hosting credentials. 5. Consider threshold signatures or an offline release-signing process for production packages. 6. Bind the signature to the package name, channel, locale, version, manifest digest, and archive digest. 7. Reject unsigned releases even when their SHA-256 checksums match. 8. Provide the user with the current version, target version, release notes, and changed-file list before installation. 9. Maintain an auditable local update log containing the verified signer identity and release digest. 10. Preserve the existing redirect rejection, path validation, archive limits, ownership checks, transaction journal, and rollback controls. 11. Consider distributing updates through the host platform's trusted package-management mechanism instead of implementing a silent in-package updater. 12. For high-assurance deployments, pin the installed package version and require a separate administrative update workflow. ]]>
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 (20)

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill exposes broad capabilities including environment access, filesystem read/write, networking, and shell execution without declaring permissions or surfacing them to the user. That creates a transparency and consent gap: a user invoking a simple blackboard-video skill would not reasonably expect code paths that can access local state, invoke commands, and communicate remotely.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The documented behavior substantially exceeds the declared purpose by including OAuth auth flows, persistent credential storage, generic remote MCP invocation, uploads, telemetry/registration, self-update, and uninstall logic. This mismatch is dangerous because it can mislead users and reviewers into granting trust to a narrow media-processing skill that in practice has much broader system and network reach.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill includes an automatic self-update and download path unrelated to its primary media-generation purpose. Any mechanism that downloads and replaces local package files expands the attack surface significantly; if the update channel, signing, or supply chain is compromised, the skill becomes a vehicle for arbitrary code delivery.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The manifest presents a focused blackboard clip generator, while the body documents remote software update, download, and local file replacement operations. Even if intended for maintenance, embedding these behaviors in a content-production skill undermines principle of least astonishment and increases the chance users will authorize risky functionality without informed consent.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The requested OAuth scope is far broader than the skill's stated purpose of turning blackboard photos into short clips. In addition to artifacts/images/videos, it asks for music generation, speech generation, voice read/write, wallet spending, and task cancellation, creating unnecessary privilege that could be abused if the token or backend integration is compromised.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
The script detects host platform details and device hostname, persists them locally, and transmits platform/device metadata during authorization. That data is not clearly necessary for blackboard clip generation and increases fingerprinting and inventory visibility of the user's environment.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
The authorization flow records a local inventory of installed skills including slug, platform, and resolved install path. This exceeds what is needed to authorize a single blackboard media skill and creates a local map of other installed tooling that could aid follow-on targeting or leak operational details.

Description-Behavior Mismatch

High
Confidence
92% confidence
Finding
The skill client contains a full self-update mechanism that downloads manifests and archives, validates them, and replaces local package files. Even though there are several integrity checks, this introduces code modification capability unrelated to blackboard clip generation, greatly expanding attack surface and enabling remote code changes if the update channel, signing root, or package publisher is compromised.

Description-Behavior Mismatch

Medium
Confidence
88% confidence
Finding
The client records installation telemetry and local skill inventory, including platform, install path, timestamps, and registration state, which is not necessary for producing blackboard clips. This creates privacy and environment-enumeration risk, and can aid tracking or later targeting if local state or backend telemetry is misused or breached.

Context-Inappropriate Capability

Medium
Confidence
86% confidence
Finding
The client fingerprints its host by inspecting environment variables and host.json to classify the agent platform. For a blackboard-video skill, this is unnecessary contextual collection that can be used for environment profiling, tracking, or adaptive payload behavior, making the skill more suspicious and more dangerous than its stated purpose suggests.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The uninstall script reaches outside the skill’s stated blackboard-video purpose and interacts with a shared Beatra authorization service. Even if intended for lifecycle management, this gives the skill package authority over shared device credentials and cross-skill state, which expands trust boundaries and creates unnecessary security sensitivity for a content-generation skill.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
This code performs remote token revocation and local manipulation of shared cross-skill state under ~/.beatra. A skill uninstall path that can revoke a shared device token can disrupt other installed skills or be abused by a compromised package to affect account connectivity beyond this skill’s legitimate function.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill states that newer versions install automatically without separate confirmation, but this is not surfaced as an upfront warning near activation. Silent installation of new code materially changes trust assumptions over time and can introduce new behavior after the user originally consented to a much narrower feature set.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The document explicitly states that the client performs silent update checks and automatically installs newer versions without separate confirmation. Even with integrity checks and rollback protections, this is a security-relevant behavior because it allows software modification during ordinary commands without clear, explicit user consent at the time of installation or first run, increasing supply-chain and unexpected system-change risk.

Missing User Warnings

Medium
Confidence
83% confidence
Finding
The documentation states that the client automatically performs a network registration call on first use and writes a local cache file, but it does not give an explicit user-facing warning or opt-in notice about this telemetry-like behavior. Even though the data described is limited and non-secret, undisclosed automatic network activity and filesystem writes can violate user expectations, create privacy/compliance concerns, and be risky in restricted or sensitive environments.

Missing User Warnings

Low
Confidence
71% confidence
Finding
The script writes host metadata to host.json without explicit user-facing disclosure. While the data is limited, undisclosed persistence of environment metadata can violate user expectations and weakens transparency around what the authorization helper stores locally.

Missing User Warnings

Low
Confidence
76% confidence
Finding
The script records the resolved installation path in skills.json without user-facing notice. Absolute install paths can reveal usernames, directory structure, or deployment layout, which is unnecessary for the stated blackboard clip functionality and reduces privacy transparency.

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
84% confidence
Finding
Referencing credentials.json as part of the state this skill uninstall can delete indicates the package is aware of and operates on shared credential material. Even though the code is trying to clean up, allowing a skill package to handle credential files directly increases the blast radius if the package is modified, replaced, or invoked unexpectedly.

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
95% confidence
Finding
The function reads an access token from ~/.beatra/credentials.json and uses it for a bearer-authenticated revocation request. Direct credential access by a skill package is dangerous because any tampering with this script, or reuse of this pattern elsewhere, could exfiltrate or misuse the token; the risk is heightened because the token is shared across skills rather than limited to this one package.

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
Exposing self-update as an explicit CLI capability confirms that the skill can modify its own installed code outside the platform's normal review/update flow. In the context of a simple media-generation skill, self-modification is unjustified and materially increases the chance of supply-chain compromise, persistence, or unauthorized behavior changes after installation.

Static analysis

No suspicious patterns detected.