T03 · Remote Payload Retrieval and Execution
Error
- Location
- scripts/mcp_client.py:969
- Finding
- Default Silent Self-Update Allows Remote Replacement of Executable Skill Files Without Publisher Signature Verification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mcp_client.py:31-32, 302-330, 469-491, 969-1019, 1543`; `SKILL.md:219-239`; `references/automatic-updates-and-safety.md:1-19` **Vulnerability Type**: Remote payload retrieval and execution **Risk Level**: High ### Vulnerable Code ```python PACKAGE_DISCOVERY_URL = "https://beatra.ai/skills/wealth-product-talking/channels/clawhub/install.json" PACKAGE_CDN_BASE_TEMPLATE = "https://cdn.beatra.ai/agent-packages/wealth-product-talking/channels/clawhub/v{version}" ``` The release URLs and hashes are accepted from a mutable discovery document: ```python def _release_urls(discovery: dict[str, Any]) -> tuple[str, str]: version = discovery.get("version") archive = discovery.get("archive") manifest = discovery.get("manifest") base_url = discovery.get("base_url") expected_base = PACKAGE_CDN_BASE_TEMPLATE.format(version=version) expected_archive = f"{PACKAGE_SLUG}-skill-{version}.zip" if ( discovery.get("schema_version") != 1 or discovery.get("package") != PACKAGE_SLUG or discovery.get("channel") != PACKAGE_CHANNEL or discovery.get("locale") != PACKAGE_LOCALE or not isinstance(version, str) or archive != expected_archive or manifest != "skill-manifest.json" or base_url != expected_base or not isinstance(discovery.get("archive_sha256"), str) or _SHA256.fullmatch(discovery["archive_sha256"]) is None or not isinstance(discovery.get("manifest_sha256"), str) or _SHA256.fullmatch(discovery["manifest_sha256"]) is None ): raise RuntimeError("Beatra update discovery is invalid") parsed = urllib.parse.urlsplit(expected_base) if ( parsed.scheme != "https" or parsed.hostname != "cdn.beatra.ai" or parsed.username is not None or parsed.password is not None or parsed.query or parsed.fragment ): raise RuntimeError("Beatra up ...[truncated 6791 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Disable automatic installation by default** - Default `auto_update` to `False`. - Permit automatic update checks, but require explicit user approval before replacing files. - Clearly display the current version, target version, affected files, and publisher identity. 2. **Add cryptographic publisher authentication** - Embed or securely provision an offline-controlled publisher public key. - Sign release metadata containing at least the package slug, channel, locale, version, expiry, manifest hash, archive hash, and file hashes. - Verify the signature locally before trusting any hash or release URL. - Reject expired, unsigned, incorrectly signed, or revoked metadata. 3. **Use a signed update framework** - Consider a design based on TUF or another framework supporting root-key rotation, metadata expiry, rollback resistance, and separation of repository roles. - Keep trusted root metadata independent from the mutable discovery service. 4. **Preserve existing hardening** - Retain redirect rejection, fixed-origin restrictions, downgrade prevention, archive traversal defenses, size limits, package ownership checks, locking, staging, rollback, and recovery journals. 5. **Protect sensitive client components** - Require stronger verification or explicit approval when replacing `scripts/mcp_client.py`, authorization code, or credential-handling code. - Report update success and the verified signer instead of performing completely silent executable replacement. 6. **Improve auditability** - Record the prior and new version, verified signer identity, manifest digest, update time, and changed files in a user-readable local log that excludes credentials and private content. ]]>
