Back to skill

Security audit

project-handover-sign-set

Security checks for vulnerabilities and agentic risk

Overview

This Beatra sign-generation skill is mostly coherent, but it requests broad account powers and silently updates/registers itself beyond what a handover-sign generator clearly needs.

Review this before installing in a sensitive environment. Only install if you are comfortable trusting Beatra with a broad shared device token, paid media-generation authority, selected-file uploads, local ~/.beatra state, installation telemetry, and default silent package updates. Consider disabling auto-updates immediately with the documented command and avoid uploading sensitive local files unless the destination trust model is acceptable.

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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/authorize.py:34
Finding
Overprivileged Shared Bearer Token Exceeds the Skill's Functional Requirements<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:34-37` **Vulnerability Type**: Excessive OAuth/MCP authorization 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" ) ``` ### Technical Analysis The declared Skill functionality is limited to creating and editing project-handover sign images, uploading optional visual references, reading model and wallet information, and managing related asynchronous tasks. The requested authorization scope additionally permits: - Video generation - Music generation - Speech generation - Voice reading and modification - General artifact access - Account credit spending - Task cancellation These privileges are unrelated to the narrow image-sign workflow. The authorization documentation also states that this is a single full-scope Device Token shared by all installed Beatra Skills. Consequently, compromise of one credential or one package exposes capabilities beyond this Skill's legitimate requirements. The token is stored at `~/.beatra/credentials.json` and is accepted as a bearer credential. Possession is therefore sufficient to exercise the granted server-side permissions. ### Attack Path 1. An attacker obtains local read access to `~/.beatra/credentials.json`, compromises a package update, or exploits another process running as the user. 2. The attacker extracts the `access_token`. 3. The attacker authenticates to `https://mcp.beatra.ai/mcp` using the bearer token. 4. The attacker invokes unrelated scoped operations such as music, video, speech, or voice operations. 5. Where supported by the service, the attacker spends credits, accesses artifacts, or cancels tasks without needing another authorization decision. ### Impact Assessment A compromised credential can potentially provide access to unrel ...[truncated 382 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define a package-specific least-privilege scope containing only the operations required by this Skill. 2. Remove `videos:generate`, `music:generate`, `speech:generate`, and `voices:write`. 3. Restrict artifact and task permissions to resources created by this package where the backend supports resource-level authorization. 4. Separate credit-spending authorization from read-only wallet access. 5. Avoid sharing one unrestricted bearer token across unrelated Skills. Use per-Skill credentials or delegated tokens with audience, capability, and package restrictions. 6. Display the exact requested capabilities on the device-authorization approval page. 7. Support token rotation and immediate revocation, and record auditable per-package attribution on the server. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/mcp_client.py:513
Finding
Silent Self-Update Mechanism Can Replace Executable Skill Files Without User Approval<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:513-521`, `scripts/mcp_client.py:942-1019`, and `scripts/mcp_client.py:1527-1528` **Vulnerability Type**: Automatic remote payload retrieval and executable-file replacement **Risk Level**: High ### Vulnerable Code The default update state enables automatic updates: ```python def _read_update_state(update_home: Path) -> dict[str, Any]: path = update_home / "state.json" try: value = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError): return {"schema_version": 1, "auto_update": True} if not isinstance(value, dict) or value.get("schema_version") != 1: return {"schema_version": 1, "auto_update": True} return value ``` The automatic-update routine downloads and installs new package files: ```python 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_root, update_home=update_home, discovery=discovery, manifest=manifest, new_files=new_files, ) return True ``` It runs before ordinary non-update commands: ```python else: maybe_auto_update() ``` ### Technical Analysis The client silently checks for and installs updates before ordinary Beatra operations. The updater can replace executable files, including `scripts/mcp_client.py` and other package-owned scripts, without obtaining approval for the individual update. The implementation contains meaningful defensive controls: - Fixed HTTPS discovery and CDN addresses - Redirect rejection - Package, channel, locale, and version validation - Archive and per-file SHA-256 verification - Path traversal and symbolic-link protections - Refusal to replace f ...[truncated 1916 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic installation by default. Permit automatic checks, but require explicit user approval before replacing files. 2. Sign release metadata with an offline or otherwise strongly protected release key. 3. Pin the corresponding verification public key in the reviewed package and verify signatures locally before trusting hashes or version metadata. 4. Use a rollback-resistant signed metadata framework with expiration and version counters, such as The Update Framework principles. 5. Separate executable updates from documentation or data updates and require stronger confirmation for executable files. 6. Clearly show the target version, affected executable files, publisher identity, and signature status before installation. 7. Preserve the existing archive limits, path validation, ownership checks, locking, journaling, and rollback protections. 8. Provide a centrally enforceable policy to disable updates in managed environments rather than relying only on per-installation state. ]]>

other

Warning
Location
scripts/authorize.py:340
Finding
Authorization Collects and Transmits Host Identity Beyond the Minimum Needed for Image Generation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize.py:340-368` and `scripts/authorize.py:447-465` **Vulnerability Type**: Unnecessary host and environment telemetry **Risk Level**: Medium ### Vulnerable Code The authorization helper detects the agent environment and reads the local hostname: ```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 resulting 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 code does ...[truncated 1893 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not collect or transmit the operating-system hostname by default. 2. Use a random, non-identifying display label generated locally, such as “Beatra device 7F3A.” 3. If a recognizable device name is desirable, ask the user to opt in and show the exact value before transmission. 4. Document every transmitted authorization and registration field, its purpose, retention period, and deletion mechanism. 5. Minimize persistent installation identifiers or rotate them when the user reconnects. 6. Allow users and managed environments to disable all nonessential telemetry. 7. Continue limiting platform detection to a small allowlist rather than transmitting arbitrary environment-variable values. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:230
Finding
Server-Provided Upload URL Is Not Restricted to an Approved Host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:230-263` **Vulnerability Type**: Insufficient validation of remote 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 URL is obtained from the authenticated MCP response. The client verifies that it use ...[truncated 1595 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Maintain an explicit allowlist of approved upload hostnames or hostname suffixes. 2. Require an exact HTTPS origin match rather than accepting every HTTPS hostname. 3. Validate the resolved upload URL against a signed upload grant that binds: - Destination origin - Object identifier - HTTP method - MIME type - Content length - Expiration time 4. Reject unexpected headers, especially authorization, forwarding, host-override, and proxy-related headers. 5. Consider requiring the upload response to be cryptographically bound to the previously issued grant and artifact identifier. 6. Document the external storage domains to which user files may be transmitted. 7. Preserve the existing regular-file, no-follow, size, and file-stability checks. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mcp_client.py:1044
Finding
Windows Credential ACL Requirements Are Documented but Neither Enforced nor Verified<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:1044-1051` and `scripts/authorize.py:116-130` **Vulnerability Type**: Insufficient local credential access control on Windows **Risk Level**: Medium ### Vulnerable Code Credential directory and file permissions are explicitly set only on POSIX systems: ```python def _private_directory(path: Path) -> None: # POSIX gets explicit 700/600. On Windows the state directory lives under # the user profile, whose default ACL is already private to the user — # the same posture as gh/aws/gcloud credential stores. The former custom # DACL ceremony was dropped deliberately: its command patterns read as # hostile to agent safety policies and endpoint security, failing installs # while adding no protection an elevated administrator could not bypass. 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) ``` The Windows credential reader performs no ACL or owner verification: ```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") ``` ### Technical Analysis The documentation states that, on Windows, the current user must be the only principal granted access through the file ACL. The implementation assumes that the inherited user-profile ACL is sufficiently private and does not enforce or verify the documented requirement. T ...[truncated 1461 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the token in Windows Credential Manager or another operating-system-backed secret store. 2. If a file must be used, create `~/.beatra` and `credentials.json` with an explicit DACL granting access only to the current user and required system principals. 3. Verify the file owner and effective ACL before reading the token. 4. Fail closed with a clear remediation message when inherited or explicit access entries permit unintended principals to read the file. 5. Protect against reparse points and linked paths on Windows, corresponding to the POSIX `O_NOFOLLOW` protection. 6. Ensure temporary credential files receive the same restrictive DACL before sensitive content is written. 7. Update the documentation only if the implementation can satisfy the stated user-only ACL guarantee; otherwise, clearly disclose the weaker inherited-ACL assumption. ]]>
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (21)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill declares a narrow content-generation purpose, but the documented execution path requires broad capabilities including shell execution, filesystem access, network access, and handling local/user files. That capability gap matters because users and hosts cannot accurately reason about what the skill can do, and those powers are sufficient to modify the local environment, exfiltrate data, or run unintended operations if the bundled client or referenced workflow is compromised.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill is presented as a handover-sign generator, but it also performs OAuth authorization, stores bearer tokens, uploads local files, registers telemetry, supports uninstall/revocation flows, and self-updates code. This description-behavior mismatch is dangerous because it conceals security-relevant actions behind an innocuous creative workflow, reducing informed consent and increasing the chance that sensitive credentials, files, or system state are affected unexpectedly.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
A skill for generating sign stills should not silently incorporate an automatic updater into its normal runtime path. Embedding self-update behavior in a content-generation tool expands the attack surface significantly: if the update channel, trust verification, or package ownership assumptions fail, arbitrary code changes can occur under the guise of routine creative work.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
Automatic code download and installation is not necessary for the stated task of creating handover sign stills, yet the skill states that newer releases are installed automatically without separate confirmation. This creates a direct software supply-chain risk: compromise of discovery, CDN, signing, packaging, or the updater itself could lead to silent execution of attacker-controlled code on the host.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
The file documents automatic installation registration behavior that is unrelated to the advertised purpose of generating handover sign stills. This capability creates hidden network-side data collection and expands the skill's behavior beyond user expectations, which is especially risky when the manifest does not disclose it.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The documented behavior includes external registration plus host-environment identification using environment signatures or host metadata, which amounts to fingerprinting capability unrelated to sign generation. In the context of a simple content-production skill, this is more dangerous because it enables covert environment profiling and outbound communication without an obvious functional need.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The OAuth scope set is far broader than the stated purpose of a handover-sign generation skill. Requesting write/read access to artifacts and tasks plus image/video/music/speech/voice capabilities exceeds clear least-privilege bounds, so compromise or misuse of the granted token would expose more of the user's account than necessary.

Context-Inappropriate Capability

Critical
Confidence
100% confidence
Finding
The script requests especially sensitive permissions such as wallet:spend along with unrelated media-generation and voice-management scopes, none of which are justified by a project handover sign skill. If the user authorizes this device flow, the resulting bearer token could be used to spend funds or access unrelated account features, making this a severe overprivilege issue with direct financial and cross-feature abuse potential.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The skill client performs broad non-core behaviors—self-update, installation registration, and local inventory tracking—that materially exceed the stated handover-sign functionality. This increases the attack surface and creates a supply-chain and privacy risk, especially because the package can modify its own installed files and maintain device state unrelated to the user’s creative task.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The client fingerprints the host environment using environment variables and host metadata, then attaches that attribution to tool calls and registration traffic. For a sign-generation skill, this is unnecessary contextual collection that can enable tracking, environment profiling, and correlation of user activity across installs.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The skill writes a persistent local inventory of installed skills and sends best-effort installation telemetry unrelated to generating handover signs. This creates avoidable privacy and governance risk by tracking local software presence and reporting installation details outside the core user workflow.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The uninstall script is designed to inspect and potentially revoke a shared device credential and remove shared state under ~/.beatra, which exceeds the stated purpose of a sign-generation skill. Even though the code tries to preserve credentials when other skills remain installed, it still gives this content-creation package authority over cross-skill authentication state, creating unnecessary account and availability risk if the inventory is wrong, tampered with, or the script is invoked unexpectedly.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
This code performs an authenticated POST to the Beatra authorization service to revoke the device token, a privileged account-management action unrelated to generating handover sign assets. Embedding revocation capability inside an ordinary skill broadens the attack surface: a compromised or repurposed package can disrupt user access across skills or force reauthentication.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The script enumerates and deletes shared files including credentials, installation metadata, host information, skill inventory, and registrations from ~/.beatra. For a sign-studio skill, deleting global shared state is over-privileged behavior that can affect unrelated installed skills, break connectivity, and erase operational metadata beyond the package's own files.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill documents silent automatic updates that replace package-owned files, but it does not provide a strong, user-facing warning that the local system will be modified as part of ordinary use. Lack of clear consent and impact disclosure undermines user trust and safe deployment practices, especially in environments where change control or software whitelisting is required.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The document states that the client silently checks for updates and automatically installs newer releases by default without separate confirmation. Even with integrity checks and rollback protections, unattended self-updating that replaces installed package files changes the local system state without explicit user approval, which creates supply-chain and change-management risk if the update channel is ever compromised or if users are unaware of the behavior.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The markdown states that the client automatically makes a registration call transmitting package, version, platform, and stable installation reference, but does not present this as an explicit user warning or consent step. Silent network transmission of environment metadata undermines transparency and can expose deployment details in environments where users expect an offline or purely local creative workflow.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The client can silently auto-update and replace installed package files during normal execution, without user-facing warning at run time. Even with checksum and manifest validation, this grants the remote update channel ongoing code-modification power and turns any compromise of that channel into code deployment on the host.

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
91% confidence
Finding
Referencing credentials.json in the set of files to delete shows the skill is aware of and handles shared credential storage, which is sensitive authentication material outside its functional scope. Even without exfiltration, code that reads or removes credential files increases the blast radius of the package and can cause denial of service or unsafe credential handling.

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 _device_token function reads an access token from ~/.beatra/credentials.json so it can be used in an Authorization header for revocation. Accessing bearer tokens from disk inside a content-creation skill is dangerous because any bug, tampering, or later code change could misuse the token for broader authenticated actions, and the access itself is not justified by the skill's purpose.

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
95% confidence
Finding
Self-modification is risky in an end-user skill because it allows the package to change its own executable contents after installation. In this skill context, that behavior is unrelated to sign creation and materially raises supply-chain risk, persistence concerns, and the consequences of a compromised update service.

Static analysis

No suspicious patterns detected.