molecule-sdk-python/pr-description-draft.md
molecule-ai[bot] 4467f8ad89
feat(security): add plugin content integrity verification (SHA256) (#3)
* feat(security): add plugin content integrity verification (SHA256)

SDK-side follow-up to molecule-core PR #1019 (pinned-ref supply-chain fix).

Changes:
- verify_plugin_sha256(plugin_dir, expected_sha) — content-addressed manifest
  hash over sorted (relpath, SHA256(content)) pairs; plugin.yaml excluded
  from its own hash to avoid circular dependency
- _walk_files(root) / _sha256_file(path) — internal helpers
- install_plugin() calls verify_sha256 after atomic rename; on mismatch
  deletes plugin dir and raises ValueError before setup.sh runs
- PLUGIN_YAML_SCHEMA gains optional sha256 field (64-char lowercase hex)
- validate_manifest() validates sha256 format when present

Tests (12 new):
- sha256_file correctness, walk_files ordering, verify_* (match/mismatch/invalid)
- install_plugin sha256 verified: setup.sh runs
- install_plugin sha256 mismatch: raises ValueError, setup.sh NOT run
- install_plugin no sha256: backward-compat, skips verification
- validate_manifest sha256: valid/invalid/non-hex/absent

Pre-existing: 4 async tests in test_sdk.py fail without pytest-asyncio
(not related to this change).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(tests): add pytest-asyncio markers to async adaptor tests

The 4 tests using async def were failing because pytest-asyncio was not
installed and pytest.ini set asyncio_mode=auto (which requires it). Add
@pytest.mark.asyncio to each async test and add pytest-asyncio as a
test optional dependency so CI gets the right extras when installing.

Fixes: 4 FAILED tests in test_sdk.py

* feat(cli): add verify-sha256 command to molecule_agent

Add `python -m molecule_agent verify-sha256 <plugin-dir>` CLI that
computes the content-integrity SHA256 for a plugin directory (the same
manifest hash that verify_plugin_sha256() uses internally). Plugin authors
can run this to generate the hash to put in plugin.yaml's sha256 field.

Also:
- Re-export verify_plugin_sha256 and compute_plugin_sha256 from the
  molecule_agent package root so `from molecule_agent import
  compute_plugin_sha256` works.
- Update CLAUDE.md to document the CLI and content integrity flow.
- Write pr-description-draft.md as a backup for when GH_TOKEN recovers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Molecule AI SDK-Dev <sdk-dev@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 01:00:35 +00:00

3.8 KiB

PR Description Draft — Plugin Content Integrity (SHA256)

File: pr-description-draft.md in the SDK repo, to be pasted into GitHub when token recovers.


feat(security): add plugin content integrity verification (SHA256)

Problem

When a workspace installs a plugin via GET /workspaces/:id/plugins/:name/download, the platform can pin the tarball to a specific Git ref (PR #1019, molecule-core). However, the SDK had no content-integrity check: once a tarball was served under a valid pinned ref, the SDK would extract it and run setup.sh without verifying the unpacked content matched the declared SHA256 in plugin.yaml.

A supply-chain attacker who compromised the plugin registry or the GitHub source could serve a tampered tarball under a valid pinned ref. The install would proceed, setup.sh would run with plugin author credentials, and the attacker's payload would execute.

Solution

Add a content-addressed manifest hash to plugin.yaml and verify it before running setup.sh.

Manifest format: SHA256 of the canonical JSON of sorted((relative_path, SHA256(file_content)) for all files except plugin.yaml itself). plugin.yaml is excluded from its own hash because it contains the hash — otherwise the bootstrap is circular.

Why this works: Even if an attacker replaces a file, they cannot compute the matching manifest hash without knowing the excluded set. The platform pins the tarball by Git ref; the SDK verifies the tarball's unpacked content integrity before execution.

Changes

File Change
molecule_agent/client.py Added verify_plugin_sha256(), _walk_files(), _sha256_file(), integrated into install_plugin() before setup.sh runs
molecule_agent/__main__.py Added CLI: python -m molecule_agent verify-sha256 <plugin-dir> to compute the hash for a plugin directory
molecule_plugin/manifest.py Added sha256 field to PLUGIN_YAML_SCHEMA, validation in validate_manifest()
molecule_agent/__init__.py Re-export verify_plugin_sha256 and compute_plugin_sha256
tests/test_remote_agent.py 12 new tests covering all sha256 paths, including integration with install_plugin()
known-issues.md Updated KI-006 with resolution
CLAUDE.md Added content integrity section documenting the verify-sha256 CLI

API / Schema

plugin.yaml additions:

name: my-plugin
version: "1.0"
sha256: a3f5b8c9d1e2...  # 64 lowercase hex chars; generate with: python -m molecule_agent verify-sha256 <plugin-dir>

Generate the hash for a local plugin directory:

python -m molecule_agent verify-sha256 ./my-plugin
# Outputs: "Computed SHA256: <64-char hash>"
# Copy the hash into plugin.yaml under the sha256 field.

Security notes

  • The hash excludes plugin.yaml itself to avoid circular dependency. This means plugin.yaml can be modified freely as long as the new hash is recomputed and stored.
  • setup.sh is only executed after verify_plugin_sha256() succeeds. If verification fails, the staging directory is cleaned up and setup.sh is never called.
  • _safe_extract_tar() (tar-slip protection) and verify_plugin_sha256() (content integrity) address two separate concerns and are applied in sequence.

Test results

tests/test_remote_agent.py: 57 passed (12 new sha256 tests)
tests/test_sdk.py: 50 passed
tests/test_validators.py: 36 passed
Total: 143 passed

Migration path for existing plugins

Plugin authors who want to pin their plugin must:

  1. Run python -m molecule_agent verify-sha256 <plugin-dir> on the final directory
  2. Add the hash to plugin.yaml under the sha256 field
  3. Commit and push; CI will verify the hash remains correct

Existing plugins without a sha256 field are unaffected (verification is skipped with a warning log).


Draft — will submit via GitHub API when auth token recovers.