Plugin Signing For Developers
Signing matters because a .w3dp archive is executable plugin code. A valid signature gives users and registry maintainers a way to confirm that a package really came from you and that the manifest was not modified after you built it.
WPrint 3D already warns when someone tries to install an unsigned plugin. Signing does not replace good release notes or code review, but it does establish publisher continuity across releases.
Prerequisites
A full
wprint3d-coresource checkout.Your plugin directory inside that checkout.
openssl.A private key that stays outside the plugin directory and out of git.
What A Signed Package Carries
When you sign a package, WPrint 3D embeds this metadata in plugin.json -> signature inside the .w3dp archive:
algorithmkeyIdpublicKeypublicKeySha256value
That makes the archive self-describing enough for users and registry maintainers to inspect and verify it locally.
Recommended Flow
1. Generate one long-lived signing key
Use one stable key per publisher or team, not one key per release.
Recommended command from the repo root:
./plugin.sh keygen --output ../plugin-signing/acme-plugin.pem
That creates both the private key and the matching .pub.pem file, then reminds you about backups and storage.
Equivalent raw openssl flow:
mkdir -p ../plugin-signing
openssl genpkey \
-algorithm RSA \
-aes-256-cbc \
-pkeyopt rsa_keygen_bits:4096 \
-out ../plugin-signing/acme-plugin.pem
Optional: extract the public key now for your own records.
openssl pkey \
-in ../plugin-signing/acme-plugin.pem \
-pubout \
-out ../plugin-signing/acme-plugin.pub.pem
2. Back the private key up before you rely on it
Keep at least one encrypted backup outside your workstation.
Record which plugin releases depend on that key.
Do not assume the embedded public key inside a
.w3dpis enough to recover your signing identity later. It is not.
If the private key is lost permanently, you can still publish future releases, but you cannot prove signer continuity with the previous ones.
To restore from backup later:
cp ../backups/acme-plugin.pem ../plugin-signing/acme-plugin.pem
chmod 600 ../plugin-signing/acme-plugin.pem
openssl pkey \
-in ../plugin-signing/acme-plugin.pem \
-pubout \
-out ../plugin-signing/acme-plugin.pub.pem
3. Build and sign the package
Interactive release flow:
./plugin.sh pack examples/plugins/hello-world --wizard
Non-interactive release flow:
./plugin.sh pack examples/plugins/hello-world \
--signing-key ../plugin-signing/acme-plugin.pem
If the key has a passphrase, prefer a temporary passphrase file over putting secrets in shell history:
./plugin.sh pack examples/plugins/hello-world \
--signing-key ../plugin-signing/acme-plugin.pem \
--passphrase-file ../secrets/acme-plugin.passphrase
Equivalent helper script:
python3 scripts/plugin_sign.py \
examples/plugins/hello-world \
--private-key ../plugin-signing/acme-plugin.pem
That script will:
run
php artisan plugin:packsign the manifest
write the package to
<plugin>/builds/<plugin-dir>.w3dpverify the resulting archive
extract the embedded public key next to the package as
<plugin>.pub.pem
Every signed .w3dp embeds the signer public key automatically in plugin.json -> signature.publicKey.
4. Inspect the signed archive
Shell wrapper:
./plugin.sh verify examples/plugins/hello-world/builds/hello-world.w3dp
Python helper:
python3 scripts/plugin_verify_signature.py inspect \
examples/plugins/hello-world/builds/hello-world.w3dp
Example output:
Package: /.../examples/plugins/hello-world/builds/hello-world.w3dp
Plugin: wprint3d.hello-world @ 0.1.0
Algorithm: openssl-sha256
Key ID: 8d6c...
Embedded public key: yes
Public key SHA-256: 7f4a...
Key ID matches embedded key: yes
5. Verify the package before release
Local integrity check:
./plugin.sh verify \
examples/plugins/hello-world/builds/hello-world.w3dp
Fail closed unless this WPrint 3D instance already trusts the signer:
./plugin.sh verify \
examples/plugins/hello-world/builds/hello-world.w3dp \
--require-trusted
Continuity check against the previous release:
python3 scripts/plugin_verify_signature.py verify \
examples/plugins/hello-world/builds/hello-world.w3dp \
--previous-package tmp/hello-world-0.0.9.w3dp
That should only pass if the new release is signed correctly and uses the same public key as the previous release.
6. Publish the release artifact
If you are hosting releases on GitHub, upload the signed package to a release before opening the registry PR:
php artisan plugin:publish \
examples/plugins/hello-world/builds/hello-world.w3dp \
--repo=acme/hello-world \
--token="$GITHUB_TOKEN"
Publishing To The Public Registry
The current public-registry process is still PR-driven.
First release
Keep the plugin in its own repository.
Build and sign the
.w3dp.Verify the package locally.
Upload the signed package to an immutable release URL.
Open a PR to the public registry with:
the plugin repository URL
the release/package URL
the plugin ID and version
the public-key SHA-256 fingerprint
the embedded public key PEM in the PR description
Wait for the registry maintainers to review and reach out.
Subsequent releases
Keep using the same signing key unless you are intentionally rotating it.
Run:
python3 scripts/plugin_verify_signature.py verify new-release.w3dp \ --previous-package previous-release.w3dp
Open the PR with:
the plugin repository URL
the new package URL
the new version
the verification output
If the key changed, explain why in the PR description and expect manual follow-up.
Best Practices
Keep the private key outside the plugin directory.
Do not commit private keys, generated
.pemfiles, or passphrase files.Use one stable signing identity across releases.
Use a passphrase for keys that leave your workstation.
Prefer
./plugin.sh keygenand./plugin.sh pack --wizardover ad-hoc release snippets so your local flow matches the backend packer.Rotate keys only for compromise, team ownership change, or a documented security event.
If you rotate, tell registry maintainers before they discover it from a failed continuity check.
Treat unsigned releases as test artifacts, not public releases.
Troubleshooting
Package is unsigned
You packed without --signing-key. Rebuild through plugin_sign.py, ./plugin.sh pack ... --signing-key=..., or ./plugin.sh pack ... --wizard.
Verified: no
The archive was modified after signing, the wrong key was used, or the manifest serialization changed outside the packer. Rebuild from clean source and re-sign.
Matches previous release key: no
You changed signing keys. Either revert to the previous key or prepare a documented key-rotation explanation for the registry PR.