# Plugin signing and packaging (curator guide) ADR-013 Phase 1. How a plugin becomes a signed, verifiable artifact and how a site trusts it. The signature proves an artifact is EXACTLY what a curator reviewed and signed - it does not prove the code is safe. Human review before signing is the actual safety control; the signature makes that review's verdict tamper-evident all the way to the point of execution. Requires the `cryptography` package (already a dependency). ## One-time: create the publisher key pair ``` flask plugin keygen --out ./keys --name curator ``` Writes `keys/curator.key` (PRIVATE) and `keys/curator.pub` (public). - Keep the `.key` OFFLINE with the curator. It is the only thing that can sign a trusted artifact. Never put it on the plugin shelf or in the repo. - Distribute the `.pub` with each site's deployed config and pin it (below). - Rotation: generate a new pair, pin BOTH public keys on sites for an overlap window (`verify` accepts any trusted key), then retire the old one. ## Per plugin: review, then pack 1. Review the plugin's source. This is the security gate - read what it does. 2. Validate and package in one step: ``` flask plugin pack printers --key ./keys/curator.key --publisher west-jefferson ``` `pack` refuses to sign a directory that does not validate (manifest schema, name/dir match, core_version, dependencies on disk). On success it writes `printers-.shopdbplugin` - a zip of the plugin plus: - `PROVENANCE.json`: name, version, publisher, created, and a sorted `{file: sha256}` map of every packaged file. - `PROVENANCE.sig`: a detached ed25519 signature over the exact `PROVENANCE.json` bytes. 3. Publish the artifact to the shelf (a SharePoint-synced or copied folder). Transport is untrusted; the signature is what makes it safe. ## Verify an artifact ``` flask plugin validate dist/printers-1.0.0.shopdbplugin --pubkey ./keys/curator.pub ``` Checks, fail-closed: signature against the trusted key(s), every file's hash, no unexpected files, manifest schema, and that the plugin's `core_version` admits this framework's contract version. Any changed byte in any file fails the hash check; a signature from an untrusted key fails the signature check. ## Pin trusted keys on a site Set `PLUGIN_TRUSTED_KEYS` to one or more public-key PEM paths, separated by the OS path separator (`:` on Linux, `;` on Windows), in the site's environment: ``` PLUGIN_TRUSTED_KEYS=/etc/shopdb/keys/curator.pub:/etc/shopdb/keys/curator-next.pub ``` Keys are read only from this deployed config, never from the shelf - a folder an attacker could write must not also carry the keys that authenticate it. With no keys set, `validate` on an artifact fails closed (unverifiable). ## What is NOT in this phase Verification is available but not yet enforced automatically at plugin load or migrate time, and there is no `adopt` command that pulls from a shelf yet. Those land in Phase 2 (verify-at-load, verify-at-migrate, the signed shelf index). For now, verify artifacts manually with `validate` before installing.