Skip to content

security(plugins): add cryptographic signature verification#29

Open
DeryFerd wants to merge 1 commit into
anvie:mainfrom
DeryFerd:security/add-plugin-code-signing
Open

security(plugins): add cryptographic signature verification#29
DeryFerd wants to merge 1 commit into
anvie:mainfrom
DeryFerd:security/add-plugin-code-signing

Conversation

@DeryFerd
Copy link
Copy Markdown
Contributor

Context

Plugins run arbitrary Python code via setup.py with full system privileges. Right now, any zip file that passes basic validation (size limits, path traversal checks, file type whitelist) can be installed. There's no cryptographic verification that the plugin came from a trusted source or hasn't been tampered with.

This is the highest-risk attack surface I found during the security audit. A malicious plugin could exfiltrate data, backdoor the system, or escalate privileges. Supply chain attacks are real—we've seen them with npm, PyPI, and other package ecosystems.

What This PR Does

Adds GPG-based signature verification for plugins. When signature verification is enabled, plugins must include a detached GPG signature (.sig or .asc file) that's verified against a trusted keyring before installation.

New files:

  • backend/plugin_signer.py — Signature verification logic using GPG subprocess calls
  • unit_tests/test_plugin_signer.py — 18 test cases covering verification, key management, and edge cases

Modified files:

  • backend/zip_validator.py — Added optional signature verification step
  • backend/plugin_lifecycle.py — Integrated verification into install_plugin() workflow

How It Works

Setup (one-time):

  1. Generate a GPG key pair for plugin signing (or use an existing one)
  2. Export the public key to plugins/trusted_keys.asc
  3. Optionally set PLUGIN_SIGNATURE_REQUIRED=true in environment or database

Plugin installation:

  1. Developer signs their plugin: gpg --detach-sign --armor -o plugin.sig plugin.zip
  2. User installs plugin with signature file alongside the zip
  3. Evonic verifies the signature against the trusted keyring
  4. If verification passes, installation proceeds normally
  5. If verification fails, installation is blocked with a clear error message

Graceful fallback:

  • If GPG isn't installed, verification is skipped (logged as warning)
  • If no trusted keys are configured, verification is skipped
  • If PLUGIN_SIGNATURE_REQUIRED=false, verification is disabled
  • This means existing setups won't break, but new deployments can opt into stricter security

Why GPG?

GPG is widely available, well-understood, and already used by many package managers (apt, yum, Homebrew). It's not perfect, but it's a reasonable choice for this use case. The implementation is isolated enough that we could swap in a different signing mechanism later if needed.

Testing

All 18 unit tests pass. The tests mock GPG subprocess calls to avoid requiring GPG during CI, but I also manually tested with real GPG:

  1. Generated a test key pair
  2. Signed a dummy plugin zip
  3. Verified installation succeeds with valid signature
  4. Verified installation fails with tampered signature
  5. Verified installation fails with signature from untrusted key

What This Doesn't Do

  • Doesn't enforce signature verification by default (opt-in for now)
  • Doesn't provide a plugin marketplace or curated repository
  • Doesn't sandbox plugin execution (that's a separate, much larger problem)
  • Doesn't verify plugin dependencies or transitive trust

This PR is focused on one thing: preventing unsigned or tampered plugins from being installed when verification is enabled.

Migration Path

For existing deployments:

  1. No action required—verification is disabled by default
  2. To enable: install GPG, configure trusted keys, set PLUGIN_SIGNATURE_REQUIRED=true

For plugin developers:

  1. Sign your plugin zip with GPG
  2. Distribute the .sig file alongside the zip
  3. Document your public key fingerprint so users can verify it

- Add plugin_signer module with GPG-based signature verification
- Integrate signature verification into plugin install workflow
- Support for trusted keyring (plugins/trusted_keys.asc)
- Configurable via PLUGIN_SIGNATURE_REQUIRED setting
- Graceful fallback when GPG unavailable or keys not configured
- Comprehensive unit tests with 18 test cases
- Helper functions for plugin developers to sign plugins
jeffrysurya pushed a commit to jeffrysurya/evonic that referenced this pull request May 20, 2026
- Add IDs to Connect (#btn-connect) and Disconnect (#btn-disconnect) buttons
- In applyWorkplaceData(): toggle buttons based on status (connected -> hide Connect, show Disconnect)
- In refreshStatus(): update button state based on data.status
- In connectWorkplace()/disconnectWorkplace(): replace setTimeout(refreshStatus) with setTimeout(loadWorkplace)
- Fix SSE event name: home_status_changed -> workplace_status_changed
jeffrysurya pushed a commit to jeffrysurya/evonic that referenced this pull request May 20, 2026
…ent scans (anvie#29)

_find_agent_for_session() iterates ALL agents and calls has_session()
for each one — an O(N) scan with N SQL queries. This method is called
from 16 different call sites in the same mixin, and many of them are
invoked for the same session_id within a single request (e.g.,
get_session_with_details + get_summary + add_chat_message).

Add @functools.lru_cache(maxsize=256) so that repeated lookups for the
same session_id hit the cache instead of re-scanning every agent DB.

Cache info from synthetic test: 5 agents, 3 distinct session lookups
-> 2 cache hits, 3 misses (1 per unique session_id). For a real
request with 5+ calls to the same session, this eliminates 80%+ of
the redundant agent scans.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant